如何在 TreeView 中禁用或修改 TreeCell 的填充

Posted

技术标签:

【中文标题】如何在 TreeView 中禁用或修改 TreeCell 的填充【英文标题】:How to disable or modify padding of TreeCell in TreeView 【发布时间】: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 你对我的回答提出质疑是对的,因为它确实不起作用。 【参考方案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 方法,私有的,仍然必须被调用。 是的,当然 - 其他都是必要的,只是那个不是:) 您可以考虑编辑您的答案并将其替换为直接调用。

以上是关于如何在 TreeView 中禁用或修改 TreeCell 的填充的主要内容,如果未能解决你的问题,请参考以下文章

如何在 WinForms 中禁用 TreeView 的节点重命名?

QML:如何在 TreeView 和 TableView 禁用边框?

如何在双击时禁用WPF TreeView中树项的展开/折叠

在 XAML 中的 TreeView 中未选择任何内容时禁用 TreeView 上下文菜单项

Kendo UI TreeView动态启用/禁用dragAndDrop事件

滚动时禁用拖放