String and FlowPane (string?) In a tableView?

I am currently trying to implement the following:

  • TableView with an ObservableList as a dataset with two columns, each of which contains rows (player names). This part is quite simple.

  • After clicking a player (name), a custom FlowPane should be entered below the selected player. If another player is clicked, the flow panel should disappear and be entered below the player currently pressed.

The code below implements TableView (minus part of the mouse listener). Please help me let FlowPane cover the whole line. I guess I need a RowFactory, but I don’t know how to make it work for my purposes :)

Also, it appears that both of my columns now show the same data. Confusion :) Is there a way to tell one column to use half the data set and the other column to the other half? I obviously don’t want my data to show twice.

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; } } 

EDIT:

I have currently implemented James_D ExpandingTableRow, which works neatly while the FlowPane is displayed below the selected TableRow. I also managed to change my data structures so that each column now shows different players, rather than the same in each column.

However, the created FlowPane should actually depend on the actual player (cell) that is clicked inside the row. In the James example: if you select FirstName or LastName (even for the same line), another FlowPane will be created. The FlowPane should display in the same way - under the selected line - but this is another new FlowPane depending on whether FirstName was clicked or LastName was pressed. How can i do this?

I looked at using:

 table.getSelectionModel().setCellSelectionEnabled(true); 

But this seems to disable James_d's solution.

+5
source share
1 answer

This solution only works in Java 9 and later.

The display of a row is controlled using TableRow , and the actual layout of this row is performed using its skin (a TableRowSkin ). Therefore, to manage this, you need a subclass of TableRow that sets a custom skin.

The implementation of the strings is quite simple: in this example, I added a property for the "additional content" that will be displayed when a row is selected. It also overrides the createDefaultSkin() method to specify a custom skin implementation.

 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); } } 

The implementation of the skin should do the work with the layout. He must override the methods that calculate the height, taking into account the height of the additional content, if necessary, and he needs to redefine the layoutChildren() method to place additional content, if necessary. Finally, it must manage additional content, add or remove additional content if the selected state of the line changes (or if the additional content itself changes).

 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); } } } 

Finally, a test (using a regular example from Oracle or its version):

 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); } } 

As you can see, it may take a little refinement of style and size to prepare this finished product, but it shows an approach that will work.

enter image description here

+5
source

Source: https://habr.com/ru/post/1275767/


All Articles