Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to disable or modify padding of TreeCell in TreeView

Environment: JDK 7u75, Windows 8.1 x64, JavaFX2.2

Sample code:

public class TreeViewSample extends Application {

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws MalformedURLException {
        primaryStage.setTitle("Tree View Sample");

        TreeItem<String> rootItem = new TreeItem<String>("RootNode");

        for (int i = 1; i < 5; i++) {
            TreeItem<String> item = new TreeItem<String>("SubNode" + i);
            rootItem.getChildren().add(item);
            item.getChildren().add(new TreeItem<String>("SubSubNode" + i + "" + i));
        }

        rootItem.getChildren().add(new TreeItem<String>("SecondRootNode"));
        TreeView<String> tree = new TreeView<String>(rootItem);
        StackPane root = new StackPane();
        root.getChildren().add(tree);
        Scene scene = new Scene(root, 300, 250);
        scene.getStylesheets().add((new File("../css/styletest.css").toURI()).toURL().toString());

        primaryStage.setScene(scene);
        primaryStage.show();
    }
}

CSS:

.tree-cell {
    -fx-skin: "com.sun.javafx.scene.control.skin.TreeCellSkin";
    -fx-background-color: -fx-control-inner-background;
    -fx-padding: 0.25em; /* 3 */
    -fx-text-fill: -fx-text-inner-color;
    -fx-indent: 10;
}

.tree-cell .label {
    -fx-padding: 0.0em 0.0em 0.0em 0.25em; /* 0 0 0 3 */
}

.tree-cell .tree-disclosure-node {
    -fx-padding: 4 2 4 8;
    -fx-background-color: transparent;
}

.tree-cell .tree-disclosure-node .arrow {
    -fx-background-color: -fx-mark-color;
    -fx-padding: 0.333333em; /* 4 */
    -fx-shape: "M 0 -4 L 8 0 L 0 4 z";
}

was

We need to delete all space and arrows for all nodes. Modified CSS:

.tree-cell {
    -fx-skin: "com.sun.javafx.scene.control.skin.TreeCellSkin";
    -fx-background-color: -fx-control-inner-background;
    -fx-padding: 0px; 
    -fx-text-fill: -fx-text-inner-color;
    -fx-indent: 0px;
}

.tree-cell .label {
    -fx-padding: 0.0em 0.0em 0.0em 0.0em;
}

.tree-cell .tree-disclosure-node {
    -fx-padding: 0px;
    -fx-background-color: transparent;
}

.tree-cell .tree-disclosure-node .arrow {
    -fx-background-color: -fx-mark-color;
    -fx-padding: 0.0em;
}

enter image description here

As you see there is no padding and indent for all nodes except leaves.

And the question - How to delete\modify this padding?

like image 326
Malex Avatar asked Feb 17 '15 17:02

Malex


2 Answers

In this case, the JavaFX developers made the standard length of 18 and did not provide an opportunity to change it. Here is their comment:

 /*
 * This is rather hacky - but it is a quick workaround to resolve the
 * issue that we don't know maximum width of a disclosure node for a given
 * TreeView. If we don't know the maximum width, we have no way to ensure
 * consistent indentation for a given TreeView.
 *
 * To work around this, we create a single WeakHashMap to store a max
 * disclosureNode width per TreeView. We use WeakHashMap to help prevent
 * any memory leaks.
 *
 * RT-19656 identifies a related issue, which is that we may not provide
 * indentation to any TreeItems because we have not yet encountered a cell
 * which has a disclosureNode. Once we scroll and encounter one, indentation
 * happens in a displeasing way.
 */

But if it's really necessary, then we override the TreeCellSkin class and the layoutChildren method using reflection. But this is not safe:

  final double defaultDisclosureWidth = maxDisclosureWidthMap.containsKey(tree) ?
        maxDisclosureWidthMap.get(tree) : 18;   // RT-19656: default width of default disclosure node 

on

  final double defaultDisclosureWidth =  0;   

Full class:

public class CustomTreeCellSkin<T> extends TreeCellSkin<T> {
    public CustomTreeCellSkin(TreeCell control) {
        super(control);
    }

    private boolean disclosureNodeDirty = true;


    @Override
    protected void layoutChildren(double x, double y, double w, double h) {
        try {

            TreeView<T> tree = getSkinnable().getTreeView();
            if (tree == null) return;

            if (disclosureNodeDirty) {
                Method method =TreeCellSkin.class.getDeclaredMethod("updateDisclosureNode");
                method.setAccessible(true);
                method.invoke(this);
                disclosureNodeDirty=false;
            }

            Node disclosureNode = getSkinnable().getDisclosureNode();

            TreeItem<?> treeItem = getSkinnable().getTreeItem();

            int level = tree.getTreeItemLevel(treeItem);
            if (!tree.isShowRoot()) level--;
            double leftMargin = getIndent() * level;

            x += leftMargin;

            // position the disclosure node so that it is at the proper indent
            boolean disclosureVisible = disclosureNode != null && treeItem != null && !treeItem.isLeaf();
            Field field = TreeCellSkin.class.getDeclaredField("maxDisclosureWidthMap");
            field.setAccessible(true);
            Map<TreeView<?>, Double> maxDisclosureWidthMap = (Map<TreeView<?>, Double>) field.get(this);
            final double defaultDisclosureWidth =  0;   // RT-19656: default width of default disclosure node
            double disclosureWidth = defaultDisclosureWidth;

            if (disclosureVisible) {
                if (disclosureNode == null || disclosureNode.getScene() == null) {
                    updateChildren();
                }
         if (disclosureNode != null) {
                    disclosureWidth = disclosureNode.prefWidth(h);
                    if (disclosureWidth > defaultDisclosureWidth) {
                        maxDisclosureWidthMap.put(tree, disclosureWidth);
                    }

                    double ph = disclosureNode.prefHeight(disclosureWidth);

                    disclosureNode.resize(disclosureWidth, ph);
                    positionInArea(disclosureNode, x, y,
                            disclosureWidth, ph, /*baseline ignored*/0,
                            HPos.CENTER, VPos.CENTER);
                }
            }

            // determine starting point of the graphic or cell node, and the
            // remaining width available to them
            final int padding = treeItem != null && treeItem.getGraphic() == null ? 0 : 3;
            x += disclosureWidth + padding;
            w -= (leftMargin + disclosureWidth + padding);

            // Rather ugly fix for RT-38519, where graphics are disappearing in
            // certain circumstances
            Node graphic = getSkinnable().getGraphic();
            if (graphic != null && !getChildren().contains(graphic)) {
                getChildren().add(graphic);
            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
        layoutLabelInArea(x, y, w, h);
    }


}

In css in this case, you need to specify our overridden class:

.tree-cell {
    -fx-skin: "CustomTreeCellSkin";
    -fx-background-color: -fx-control-inner-background;
    -fx-padding: 0px;
    -fx-text-fill: -fx-text-inner-color;
    -fx-indent: 0px;
}

.tree-cell .label {
    -fx-padding: 0.0em 0.0em 0.0em 0.0em;
}

.tree-cell .tree-disclosure-node {
    -fx-padding: 0px;
    -fx-background-color: transparent;
}

.tree-cell .tree-disclosure-node .arrow {
    -fx-background-color: -fx-mark-color;
    -fx-padding: 0.0em;
}

Sorry for my English. =)

like image 168
Sergey Libedinskiy Avatar answered Nov 13 '22 10:11

Sergey Libedinskiy


While I really appreciate aforementioned solution it's too complicated, esp because it uses reflection and with JPMS reflection usage have to be allowed explicitly.

Since defaultDisclosureWidth fallback value is hardcoded, we could just negate it by padding.

public static class CustomTreeCell extends TreeCell<Foo> {

    static final PseudoClass LEAF = PseudoClass.getPseudoClass("leaf");

    @Override
    protected void updateItem(Foo item, boolean empty) {
        super.updateItem(item, empty);

        // ...
        
        pseudoClassStateChanged(LEAF, getTreeItem().isLeaf());
    }
}
.tree-cell {
    -fx-background-insets: 0;
    -fx-padding: 0;
    -fx-indent:  0;
}

.tree-cell:leaf {
    -fx-padding: 0 0 0 -18px;
}

.tree-cell .tree-disclosure-node,
.tree-cell .arrow {
    -fx-min-width:  0;
    -fx-pref-width: 0;
    -fx-max-width:  0;

    -fx-min-height:  0;
    -fx-pref-height: 0;
    -fx-max-height:  0;
}

Use -fx-indent property to set whatever padding you want.

Simple verification:

TreeItem<Foo> nodeRoot = new TreeItem<>(new Foo("root"));

TreeItem<Foo> nodeTest1 = new TreeItem<>(new Foo("test1"));
nodeRoot.getChildren().add(nodeTest1);

TreeItem<Foo> nodeTest2 = new TreeItem<>(new Foo("test2"));
nodeTest1.getChildren().add(nodeTest2);

TreeItem<Foo> nodeTest3 = new TreeItem<>(new Foo("test3"));
nodeTest2.getChildren().add(nodeTest3);

TreeItem<Foo> nodeTest4 = new TreeItem<>(new Foo("test4"));
nodeTest3.getChildren().add(nodeTest4);

enter image description here

like image 2
enzo Avatar answered Nov 13 '22 11:11

enzo