Search code examples
javajavafxfxml

How to set cell's indentation in TreeView in Javafx


I am working on a TreeView which represents a robot controlling program, each TreeCell represents a statement, and a TreeCell can be nested in an other one. Like in programming, statements can be nested in if or for statements.

Here I have created a simple demo, filled with some random blocks.

Demo Screenshot

To customize the rendering of TreeCell, I have create a class extending TreeCell:

public class TreeDataCell extends TreeCell<TreeData> {

    public void updateItem(TreeData item, boolean empty) {
        super.updateItem(item, empty);

        setText(null);
        if (item == null || empty) {
            setGraphic(null);
        } else {
            setGraphic(getCellGraphic(item));
        }
    }

    private Group getCellGraphic(TreeData data) {

        Group grp = new Group();

        VBox vbox = new VBox();
        vbox.setMinWidth(100);
        vbox.setMaxWidth(200);
        vbox.setBorder(new Border(new BorderStroke(
                Color.LIGHTGRAY.darker(), 
                BorderStrokeStyle.SOLID, 
                new CornerRadii(10.0), 
                new BorderWidths(2.0))));
        vbox.setBackground(new Background(new BackgroundFill(Color.LIGHTGRAY, new CornerRadii(10.0), null)));
        vbox.setEffect(new DropShadow(2.0, 3.0, 3.0, Color.DIMGRAY));

        Region header = new Region();
        header.setPrefHeight(5.0);

        Region footer = new Region();
        footer.setPrefHeight(5.0);

        Label labTitle = new Label();
        labTitle.setFont(new Font("San Serif", 20));
        labTitle.setText(data.getTitle());

        Label labDesc = null;
        if (data.getDescription() != null) {
            labDesc = new Label();
            labDesc.setWrapText(true);
            labDesc.setText(data.getDescription());
        }

        vbox.getChildren().addAll(header, labTitle);
        if (labDesc != null) {
            vbox.getChildren().add(labDesc);
        }
        vbox.getChildren().add(footer);
        grp.getChildren().add(vbox);

        return grp;
    }
}

The TreeData is a simple class containing 2 Strings:

public class TreeData {
    private String title;
    private String desc;
    /* getters + setters */
}

As you can see, the indentation between two levels are too small, and we can barely see statement nesting.

I am hard coding all the styles in Java, since I haven't learnt FXML+CSS yet.

I'd like to know if it is possible to set the size of indentation in Java? I cannot find any API for this purpose. In addition, is it possible to draw lines between parent node and its children like JTree in Swing ?

Thank you.


Solution

  • Regarding having lines like in JTree, there is no built in way to do that as of JavaFX 11. There is a feature request (JDK-8090579) but there doesn't seem to be any plans to implement it. You may be able to implement it yourself but I'm not sure how.

    As to modifying the indent of the TreeCells, the easiest way is by using CSS.

    As documented in the JavaFX CSS Reference Guide, TreeCell has a CSS property named -fx-indent whose value is a <size>. You can set this property by using a stylesheet or inline it via the style property. An example using inline styles:

    public class TreeDataCell extends TreeCell<TreeData> {
    
        public TreeDataCell() {
            setStyle("-fx-indent: <size>;");
        }
    
    }
    

    However, since you are currently not using CSS or FXML, there is another option that is purely code: Modifying the indent property of TreeCellSkin. This class became public API in JavaFX 9. There may be equivalent internal API in JavaFX 8 but I'm not sure.

    By default, the Skin of a TreeCell will be an instance of TreeCellSkin. This means you can get this skin and set the indent value as needed. You have to be careful, though, as the skin is lazily created; it won't necessarily be available until the TreeView is actually part of a showing window.

    If you only want to set the property once, one way is to intercept the skin inside createDefaultSkin():

    public class TreeDataCell extends TreeCell<TreeData> {
    
        @Override
        protected Skin<?> createDefaultSkin() {
            TreeCellSkin<?> skin = (TreeCellSkin<?>) super.createDefaultSkin();
            skin.setIndent(/* your value */);
            return skin;
        }
    
    }
    

    You could also extend TreeCellSkin and customize it. Just remember to override createDefaultSkin() and return you custom skin implementation.