【问题标题】:How to disable or modify padding of TreeCell in TreeView如何在 TreeView 中禁用或修改 TreeCell 的填充
【发布时间】:2015-04-18 11:55:27
【问题描述】:

环境:JDK 7u75、Windows 8.1 x64、JavaFX2.2

示例代码:

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

我们需要删除所有节点的所有空格和箭头。 修改后的 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;
}

如您所见,除叶子外的所有节点都没有填充和缩进。

还有一个问题 - 如何删除\修改此填充?

【问题讨论】:

  • @jmtalarn 这是java,不是html
  • @DarkMental(和 OP)想知道为什么你想要一棵看起来不像树的树?
  • 我认为不可能完全删除叶节点的缩进。查看TreeCellSkin.layoutChildren 的源代码,有一个填充(disclosureWidth)被考虑在内。此填充是最大公开宽度,如果存在,否则它是硬编码的默认值18。我相信这可以防止删除左侧的剩余空间,尽管我不完全理解代码。由于在不是叶子时进行的计算,这似乎不会影响非叶子节点。 @kleopatra 你对我的回答提出质疑是对的,因为它确实不起作用。

标签: java css javafx treeview


【解决方案1】:

虽然我非常欣赏上述解决方案,但它太复杂了,尤其是因为它使用反射,并且必须明确允许使用 JPMS 反射。

由于defaultDisclosureWidth 后备值是硬编码的,我们可以通过填充来否定它。

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

使用-fx-indent 属性设置您想要的任何填充。

简单验证:

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

【讨论】:

    【解决方案2】:

    在这种情况下,JavaFX 开发人员将标准长度设为 18,并且没有提供更改它的机会。这是他们的评论:

     /*
     * 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.
     */
    

    但如果真的有必要,那么我们使用反射覆盖 TreeCellSkin 类和 layoutChildren 方法。但这并不安全:

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

      final double defaultDisclosureWidth =  0;   
    

    全班:

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

    在这种情况下的css中,你需要指定我们重写的类:

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

    对不起我的英语。 =)

    【讨论】:

    • 不错的技巧 :) 只是挑剔一点:treeItem 可通过单元格公开访问,因此无需反射
    • @kleopatra 我没有注意到 treeItem。但是 updateDisclosureNode 方法,私有的,仍然必须被调用。
    • 是的,当然 - 其他都是必要的,只是那个不是:) 您可以考虑编辑您的答案并将其替换为直接调用。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-17
    • 1970-01-01
    • 2017-04-18
    • 1970-01-01
    • 1970-01-01
    • 2012-12-09
    相关资源
    最近更新 更多