cssjavafxtreetableview

Setting a style class only for first-level nodes in JavaFX treeTableView


I have a restaurant menu with dishes and categories implemented as a treeTableView in javaFX. enter image description here

I want to make the the category rows appear different with CSS but I just can't find a way to filter them out and apply a class. Moving the images a bit to the left would also be nice. I also had no luck using a rowFactory. I've seen this answer but I don't understand it.

This is how I fill the table. I've left out the column- and cellfactories.

private void fillDishes(List<Dish> dishes){
    root.getChildren().clear();
    Map<String,TreeItem<Dish>> categoryMap = new HashMap<>();

    for (Category c: allCats) {
        TreeItem<Dish> newCat = new TreeItem<>(new Dish(c.getName(),null,null,null));
        //newCat.getGraphic().getStyleClass().add("category");
        categoryMap.put(c.getName(),newCat);
        root.getChildren().add(newCat);
    }
    for (Dish d: dishes) {
        categoryMap.get(d.getCategory()).getChildren().add(new TreeItem<>(d));
    }
}

Solution

  • TreeTableView uses the rowFactory to create the TreeTableRows. At some time later it assigns a TreeItem to a TreeTableRow. This may happen again with different TreeItems for the same row. For this reason you need to handle changes those changes which can be done by adding a ChangeHandler to the TreeTableRow.treeItem property. If a new TreeItem is assigned to the row, you can check for top-level nodes by checking the children of the (invisible) root item for the row item.

    I prefer the approach that does not require searching the child list though. It's possible to compare the parent of the item with the root.

    public static class Item {
    
        private final String value1;
        private final String value2;
    
        public Item(String value1, String value2) {
            this.value1 = value1;
            this.value2 = value2;
        }
    
        public String getValue1() {
            return value1;
        }
    
        public String getValue2() {
            return value2;
        }
    
    }
    
    @Override
    public void start(Stage primaryStage) {
        final TreeItem<Item> root = new TreeItem<>(null);
        TreeTableView<Item> ttv = new TreeTableView<>(root);
        ttv.setShowRoot(false);
    
        TreeTableColumn<Item, String> column1 = new TreeTableColumn<>();
        column1.setCellValueFactory(new TreeItemPropertyValueFactory<>("value1"));
    
        TreeTableColumn<Item, String> column2 = new TreeTableColumn<>();
        column2.setCellValueFactory(new TreeItemPropertyValueFactory<>("value2"));
    
        ttv.getColumns().addAll(column1, column2);
    
        final PseudoClass topNode = PseudoClass.getPseudoClass("top-node");
        ttv.setRowFactory(t -> {
            final TreeTableRow<Item> row = new TreeTableRow<>();
    
            // every time the TreeItem changes, check, if the new item is a
            // child of the root and set the pseudoclass accordingly
            row.treeItemProperty().addListener((o, oldValue, newValue) -> {
                boolean tn = false;
                if (newValue != null) {
                    tn = newValue.getParent() == root;
                }
                row.pseudoClassStateChanged(topNode, tn);
            });
            return row;
        });
    
        // fill tree structure
        TreeItem<Item> c1 = new TreeItem<>(new Item("category 1", null));
        c1.getChildren().addAll(
                new TreeItem<>(new Item("sub1.1", "foo")),
                new TreeItem<>(new Item("sub1.2", "bar")));
    
        TreeItem<Item> c2 = new TreeItem<>(new Item("category 2", null));
        c2.getChildren().addAll(
                new TreeItem<>(new Item("sub2.1", "answer")),
                new TreeItem<>(new Item("sub2.2", "42")));
    
        root.getChildren().addAll(c1, c2);
    
        Scene scene = new Scene(ttv);
        scene.getStylesheets().add("style.css");
    
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    

    style.css

    .tree-table-row-cell:top-node {
        -fx-background: orange;
    }
    

    Moving the images a bit to the left would also be nice.

    Usually you do this from a custom TreeTableCell returned by a TreeTableColumn.cellFactory. Depending on the behavior you want to implement setting fitWidth/fitHeight may be sufficient, but in other cases dynamically modifying those values based on the cell size may be required.