JavaFx - String и FlowPane (row?) Внутри tableView?
В настоящее время я пытаюсь реализовать следующее:
TableView с ObservableList в качестве набора данных, с двумя столбцами, каждый из которых содержит Strings (имена игроков). Эта часть достаточно проста.
После того, как игрок (имя) нажал, под выбранным игроком должна быть введена пользовательская FlowPane. Если щелкнуть другого игрока, потоковая панель должна исчезнуть и появиться под текущим игроком, на которого нажали.
Приведенный ниже код реализует TableView (за исключением части слушателя мыши). Пожалуйста, помогите мне позволить FlowPane охватить весь ряд. Я предполагаю, что мне нужен RowFactory, но я понятия не имею, как заставить его работать для моих целей:)
Кроме того, по-видимому, обе мои колонки теперь показывают те же данные. Непонятно:) Есть ли способ указать одному столбцу использовать половину набора данных, а другой - другую половину? Я явно не хочу, чтобы мои данные отображались дважды.
public class main extends Application
{
public static void main(String[] args)
{
launch(args);
}
@Override
public void start(Stage stage) throws Exception
{
try
{
FlowPane f = new FlowPane();
Scene scene = new Scene(f, 300, 200);
Player p1 = new Player("player 1 ");
Player p2 = new Player("player 2 ");
Player p3 = new Player("player 3 ");
ArrayList<Object> players = new ArrayList<>();
players.add(p1);
players.add(p2);
players.add(p3);
ObservableList<Object> observableList = FXCollections.observableArrayList(players);
TableView<Object> table = createTableView(observableList, 300, 200);
f.getChildren().add(table);
injectFlowPane(table);
stage.setScene(scene);
stage.show();
}
catch (Exception e)
{
e.printStackTrace();
}
}
public TableView<Object> createTableView(ObservableList<Object> items, double width, double height)
{
TableView<Object> table = new TableView<>();
table.setItems(items);
table.getColumns().add(createTableColumn(width / 2));
table.getColumns().add(createTableColumn(width / 2));
table.setMinSize(width, height);
table.setPrefSize(width, height);
table.setMaxSize(width, height);
return table;
}
private TableColumn<Object, Object> createTableColumn(double width)
{
TableColumn<Object, Object> tableColumn = new TableColumn<>();
tableColumn.setCellFactory(
new Callback<TableColumn<Object, Object>, TableCell<Object, Object>>() {
@Override
public TableCell<Object, Object> call(TableColumn<Object, Object> arg0)
{
return new PlayerCell();
}
});
tableColumn.setCellValueFactory(cellDataFeatures -> {
Object item = cellDataFeatures.getValue();
return new SimpleObjectProperty<>(item);
});
tableColumn.setMinWidth(width);
return tableColumn;
}
private void injectFlowPane(TableView<Object> table)
{
FlowPane f = new FlowPane();
f.setMinSize(50, 50);
f.setBackground(new Background(new BackgroundFill(Color.DARKGREEN, CornerRadii.EMPTY, Insets.EMPTY)));
table.getItems().add(1, f);
}
}
public class PlayerCell extends TableCell<Object, Object>
{
@Override
protected void updateItem(Object item, boolean empty)
{
super.updateItem(item, false);
// if (empty)
if (item != null)
{
if (item instanceof Player)
{
setText(((Player) item).getName());
setGraphic(null);
}
else if (item instanceof FlowPane)
{
setGraphic((FlowPane) item);
}
else
{
setText("N/A");
setGraphic(null);
}
}
else
{
setText(null);
setGraphic(null);
}
}
}
public class Player
{
private String name;
public Player(String name)
{
this.name = name;
}
public String getName()
{
return name;
}
}
РЕДАКТИРОВАТЬ:
Я теперь реализовал ExpandingTableRow от James_D, который работает аккуратно, поскольку показывает FlowPane под выбранным TableRow. Мне также удалось изменить свои структуры данных так, чтобы в каждом столбце теперь отображались разные игроки, а не одни и те же игроки в каждом столбце.
Однако создаваемая FlowPane должна зависеть от реального игрока (ячейки), по которому щелкают строки. В примере с Джеймсом: другая FlowPane будет создана, если будет выбрано FirstName или LastName (даже для той же строки). FlowPane должна отображаться одинаково - под выбранной строкой - но это другая, новая FlowPane, в зависимости от того, нажали ли FirstName или LastName. Как мне это сделать?
Я смотрел на использование:
table.getSelectionModel().setCellSelectionEnabled(true);
Но на самом деле это, кажется, отключает решение James_d.
1 ответ
Это решение работает только в Java 9 и более поздних версиях.
Отображение строки управляется TableRow
и фактическое расположение этой строки выполняется ее кожей (TableRowSkin
). Таким образом, чтобы управлять этим, вам нужен подкласс TableRow
это устанавливает пользовательский скин.
Реализация строки довольно проста: в этом примере я добавил свойство для "дополнительного содержимого", которое будет отображаться при выборе строки. Это также отменяет createDefaultSkin()
метод для указания пользовательской реализации скина.
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.Node;
import javafx.scene.control.Skin;
import javafx.scene.control.TableRow;
public class ExpandingTableRow<T> extends TableRow<T> {
private final ObjectProperty<Node> selectedRowContent = new SimpleObjectProperty<>();
public final ObjectProperty<Node> selectedRowContentProperty() {
return this.selectedRowContent;
}
public final Node getSelectedRowContent() {
return this.selectedRowContentProperty().get();
}
public final void setSelectedRowContent(final Node selectedRowContent) {
this.selectedRowContentProperty().set(selectedRowContent);
}
public ExpandingTableRow(Node selectedRowContent) {
super();
setSelectedRowContent(selectedRowContent);
}
public ExpandingTableRow() {
this(null);
}
@Override
protected Skin<?> createDefaultSkin() {
return new ExpandingTableRowSkin<T>(this);
}
}
Реализация скина должна выполнить работу по макету. Он должен переопределить методы, которые вычисляют высоту, учитывая высоту дополнительного содержимого, если это необходимо, и он должен переопределить layoutChildren()
метод, чтобы разместить дополнительный контент, если это необходимо. Наконец, он должен управлять дополнительным контентом, добавляя или удаляя дополнительный контент, если изменяется выбранное состояние строки (или если сам дополнительный контент изменяется).
import javafx.scene.control.skin.TableRowSkin;
public class ExpandingTableRowSkin<T> extends TableRowSkin<T> {
private ExpandingTableRow<T> row;
public ExpandingTableRowSkin(ExpandingTableRow<T> row) {
super(row);
this.row = row;
row.selectedRowContentProperty().addListener((obs, oldContent, newContent) -> {
if (oldContent != null) {
getChildren().remove(oldContent);
}
if (newContent != null && row.isSelected()) {
getChildren().add(newContent);
}
if (row.getTableView() != null) {
row.getTableView().requestLayout();
}
});
row.selectedProperty().addListener((obs, wasSelected, isNowSelected) -> {
if (isNowSelected && row.getSelectedRowContent() != null
&& !getChildren().contains(row.getSelectedRowContent())) {
getChildren().add(row.getSelectedRowContent());
} else {
getChildren().remove(row.getSelectedRowContent());
}
});
}
@Override
protected double computeMaxHeight(double width, double topInset, double rightInset, double bottomInset,
double leftInset) {
if (row.isSelected() && row.getSelectedRowContent() != null) {
return super.computeMaxHeight(width, topInset, rightInset, bottomInset, leftInset)
+ row.getSelectedRowContent().maxHeight(width);
}
return super.computeMaxHeight(width, topInset, rightInset, bottomInset, leftInset);
}
@Override
protected double computeMinHeight(double width, double topInset, double rightInset, double bottomInset,
double leftInset) {
if (row.isSelected() && row.getSelectedRowContent() != null) {
return super.computeMinHeight(width, topInset, rightInset, bottomInset, leftInset)
+ row.getSelectedRowContent().minHeight(width);
}
return super.computeMinHeight(width, topInset, rightInset, bottomInset, leftInset);
}
@Override
protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset,
double leftInset) {
if (row.isSelected() && row.getSelectedRowContent() != null) {
return super.computePrefHeight(width, topInset, rightInset, bottomInset, leftInset)
+ row.getSelectedRowContent().prefHeight(width);
}
return super.computePrefHeight(width, topInset, rightInset, bottomInset, leftInset);
}
@Override
protected void layoutChildren(double x, double y, double w, double h) {
if (row.isSelected()) {
double rowHeight = super.computePrefHeight(w, snappedTopInset(), snappedRightInset(), snappedBottomInset(),
snappedLeftInset());
super.layoutChildren(x, y, w, rowHeight);
row.getSelectedRowContent().resizeRelocate(x, y + rowHeight, w, h - rowHeight);
} else {
super.layoutChildren(x, y, w, h);
}
}
}
Наконец, тест (с использованием обычного примера от Oracle или его версии):
import java.util.function.Function;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.scene.layout.FlowPane;
import javafx.stage.Stage;
public class ExpandingTableRowTest extends Application {
@Override
public void start(Stage primaryStage) {
TableView<Person> table = new TableView<>();
table.getColumns().add(column("First Name", Person::firstNameProperty));
table.getColumns().add(column("Last Name", Person::lastNameProperty));
table.setRowFactory(tv -> {
Label label = new Label();
FlowPane flowPane = new FlowPane(label);
TableRow<Person> row = new ExpandingTableRow<>(flowPane) {
@Override
protected void updateItem(Person person, boolean empty) {
super.updateItem(person, empty);
if (empty) {
label.setText(null);
} else {
label.setText(String.format("Some additional information about %s %s here",
person.getFirstName(), person.getLastName()));
}
}
};
return row;
});
table.getItems().addAll(
new Person("Jacob", "Smith"),
new Person("Isabella", "Johnson"),
new Person("Ethan", "Williams"),
new Person("Emma", "Jones"),
new Person("Michael", "Brown")
);
Scene scene = new Scene(table);
primaryStage.setScene(scene);
primaryStage.show();
}
private static <S, T> TableColumn<S, T> column(String title, Function<S, ObservableValue<T>> property) {
TableColumn<S, T> col = new TableColumn<>(title);
col.setCellValueFactory(cellData -> property.apply(cellData.getValue()));
return col;
}
public static class Person {
private final StringProperty firstName = new SimpleStringProperty();
private final StringProperty lastName = new SimpleStringProperty();
public Person(String firstName, String lastName) {
setFirstName(firstName);
setLastName(lastName);
}
public final StringProperty firstNameProperty() {
return this.firstName;
}
public final String getFirstName() {
return this.firstNameProperty().get();
}
public final void setFirstName(final String firstName) {
this.firstNameProperty().set(firstName);
}
public final StringProperty lastNameProperty() {
return this.lastName;
}
public final String getLastName() {
return this.lastNameProperty().get();
}
public final void setLastName(final String lastName) {
this.lastNameProperty().set(lastName);
}
}
public static void main(String[] args) {
launch(args);
}
}
Как вы можете видеть, может потребоваться небольшая доработка стиля и размеров, чтобы подготовить производство, но это показывает подход, который будет работать.