Search code examples
javajavafxtreeviewjavafx-8treetableview

TreeView with Two Columns JavaFX 8


I need to have an expandable nestable key value list in JavaFX 8. This is similar in functionality to a TreeTableView, but it is oriented vertically, not horizontally. In other words, the rows would be the headers not the columns for a X-Y inverted axis table. Due to the limitation of TreeTableView being horizontal, I started exploring the options of TreeView. TreeView works nicely but only has one column where I need a key value pair in each row. What I really need is something like below:

example

Note the foo and bar can be unique.

How can I add a second column to a TreeView?


Solution

  • This is an idea based on a custom cell factory for TreeView that creates row-like cells. A custom cell is created which contains 2 labels in a grid and some resizing behavior settings.

    enter image description here

    public class CustomTree extends Application {
    
        @Override
        public void start(Stage stage) throws Exception {
            TreeView<String[]> tree = new TreeView<>();
    
            tree.setCellFactory(cell -> new TreeCell<String[]>() {
    
                private GridPane pane = new GridPane();
                private Label keyLabel = new Label();
                private Label valueLabel = new Label();
                private Border keyBorder = new Border(
                        new BorderStroke(Color.BLACK, BorderStrokeStyle.SOLID, null, new BorderWidths(0.5, 0.5, 0.5, 1)));
                private Border valueBorder = new Border(
                        new BorderStroke(Color.BLACK, BorderStrokeStyle.SOLID, null, new BorderWidths(0.5, 1, 0.5, 0.5)));
    
                {
                    pane.getChildren().addAll(keyLabel, valueLabel);
                    GridPane.setConstraints(keyLabel, 0, 0);
                    GridPane.setConstraints(valueLabel, 1, 0);
    
                    ColumnConstraints column1 = new ColumnConstraints();
                    column1.setPercentWidth(50);
                    ColumnConstraints column2 = new ColumnConstraints();
                    column2.setPercentWidth(50);
                    pane.getColumnConstraints().addAll(column1, column2);
    
                    keyLabel.setBorder(keyBorder);
                    valueLabel.setBorder(valueBorder);
                    keyLabel.setMaxWidth(Double.MAX_VALUE);
                    valueLabel.setMaxWidth(Double.MAX_VALUE);
    
                    setPadding(Insets.EMPTY);
                }
    
                @Override
                public void updateItem(String[] name, boolean empty) {
                    super.updateItem(name, empty);
                    if (empty || name == null) {
                        setText(null);
                        setGraphic(null);
                    } else {
                        keyLabel.setText(name[0]);
                        valueLabel.setText(name[1]);
                        setGraphic(pane);
                    }
                }
            });
    
            TreeItem<String[]> root = new TreeItem<>(new String[] { "", "" });
            TreeItem<String[]> sub1 = new TreeItem<>(new String[] { "Foo", "Bar" });
            TreeItem<String[]> sub2 = new TreeItem<>(new String[] { "Foo", "Bar" });
            TreeItem<String[]> sub3 = new TreeItem<>(new String[] { "Foo", "Bar" });
            TreeItem<String[]> sub4 = new TreeItem<>(new String[] { "Foo", "" });
            TreeItem<String[]> sub5 = new TreeItem<>(new String[] { "Foo", "Bar" });
            TreeItem<String[]> sub41 = new TreeItem<>(new String[] { "Foo", "Bar" });
            TreeItem<String[]> sub42 = new TreeItem<>(new String[] { "Foo", "Bar" });
            root.getChildren().addAll(sub1, sub2, sub3, sub4, sub5);
            sub4.getChildren().addAll(sub41, sub42);
    
            tree.setRoot(root);
    
            Scene scene = new Scene(tree);
            stage.setScene(scene);
            stage.show();
        }
    
        public static void main(String[] args) {
            Application.launch(args);
        }
    }
    

    Note that the root must also use this cell type, though it's possible to hide the root completely using tree.setShowRoot(false);. Two shortcomings of this approach are that each cell behaves as one unit - there is no concept of columns, and that the values of the cell are given as an array, thus losing type safety. You can expand on this example yourself.

    It could be possible, though a lot more work, to use a (generic) TableView or TreeTableView approach.