不同 class 字段的 JavaFX TreeItem css 样式

JavaFX TreeItem css style for different class field

我用自己的树填充了 TreeView。在 class 节点中,我有一个字段 "type",它是 NodeType 之一。问题是我想为每种类型的 NodeType 设置样式,例如"type1" 文字颜色应为绿色,"type2" 文字颜色应为红色。我是 javaFX 的新手。我找到了 james-d ( https://github.com/james-d/heterogeneous-tree-example ) 的解决方案,但在这个例子中 css 样式取决于 class 名称,我如何为 class 字段制作它?

View of TreeView

我的理解是,您想要一个 TreeCell,其样式取决于 TreeCellTreeItem 中包含的 NodeNodeType。全部来自 CSS。我说得对吗?

假设我是正确的,我可以想到两种方法来实现这一点;如果已知的 NodeType 数量较少,这两种方法效果最好。第一个涉及 PseudoClass 的使用,第二个使用与 JavaFX Chart API.

相同的策略

第一个选项

创建自定义 TreeCell 以使用您的 Node 类型(即适当指定通用签名)。在此自定义 TreeCell 中,您可以根据需要声明尽可能多的 PseudoClass static final 字段;每个NodeType一个。然后你观察 TreeCell 当前显示的任何 NodeNodeType 并相应地更新 PseudoClass 状态。

这是一个示例,假设 NodeType 是一个 enum,它有两个常量:HAPPYSAD

public class CustomTreeCell<T extends Node> extends TreeCell<T> {

    private static final PseudoClass HAPPY = PseudoClass.getPseudoClass("happy");
    private static final PseudoClass SAD = PseudoClass.getPseudoClass("sad");

    // this listener will activate/deactivate the appropriate PseudoClass states
    private final ChangeListener<NodeType> listener = (obs, oldVal, newVal) -> {
        pseudoClassStateChanged(HAPPY, newVal == NodeType.HAPPY);
        pseudoClassStateChanged(SAD, newVal == NodeType.SAD);
    };

    // use a weak listener to avoid a memory leak
    private final WeakChangeListener<NodeType> weakListener = /* wrap listener */;

    public CustomTreeCell() {
        getStyleClass().add("custom-tree-cell");
        itemProperty().addListener((obs, oldVal, newVal) -> {
            if (oldVal != null) {
                oldVal.nodeTypeProperty().removeListener(weakListener);
            }
            if (newVal != null) {
                newVal.nodeTypeProperty().addListener(weakListener);
                // need to "observe" the initial NodeType of the new Node item.
                // You could call the listener manually to avoid code duplication
                pseudoClassStateChanged(HAPPY, newVal.getNodeType() == NodeType.HAPPY);
                pseudoClassStateChanged(SAD, newVal.getNodeType() == NodeType.SAD);
            } else {
                // no item in this cell so deactivate all PseudoClass's
                pseudoClassStateChanged(HAPPY, false);
                pseudoClassStateChanged(SAD, false);
            }
        });
    }
}

然后在您的 CSS 文件中您可以使用:

.custom-tree-cell:happy {
    /* style when happy */
}

.custom-tree-cell:sad {
    /* style when sad */
}

第二个选项

做 JavaFX Chart API 在处理多系列数据时做的事情。它所做的是根据列表中系列的索引动态更新节点的 style class(例如 .line-chart-series-data-<index> <-- 可能不是 确切地 this)。

/*
 * Create a custom TreeCell like in the first option but
 * without any of the PseudoClass code. This listener should
 * be added/removed from the Node item just like weakListener
 * is above.
 */
ChangeListener<NodeType> listener = (obs, oldVal, newVal) -> {
    // You have to make sure you keep "cell", "indexed-cell", and "tree-cell"
    // in order to keep the basic modena styling.
    if (newVal == NodeType.HAPPY) {
        getStyleClass().setAll("cell", "indexed-cell", "tree-cell", "custom-tree-cell-happy");
    } else if (newVal == NodeType.HAPPY) {
        getStyleClass().setAll("cell", "indexed-cell", "tree-cell", "custom-tree-cell-sad");
    } else {
        getStyleClass().setAll("cell", "indexed-cell", "tree-cell"); // revert to regular TreeCell style
    }
};

然后在CSS:

.custom-tree-cell-happy {
    /* styles */
}

.custom-tree-cell-sad {
   /* styles */
}

这两个选项实际上仅在存在一小组已知类型时才可行。当你有 10+ NodeType 之类的东西时,它可能变得无法维护。如果 NodeType 的数量在运行时是动态的,这几乎是不可能的。

使用 NodeType 或一些中间 class/data 结构可能更容易,知道文本应该是什么颜色并根据 NodeType 以编程方式设置颜色。

注意:我把答案中的代码快速打出来了,没有测试。我的代码中可能存在编译器错误、运行时异常或逻辑错误。


编辑

我想到了其他事情。我上面的代码假定 NodeType 保存在 属性 中并且可以在运行时更改。如果 NodeType 对于每个 Node 都是静态的(不变的),那么代码可以大大简化。您可以简单地重写 javafx.scene.control.Cell:

中声明的以下方法,而不是使用任何侦听器

protected void updateItem(Node item, boolean empty)

每次在单元格上设置新项目时都会调用此方法。但是,请阅读 documentation,因为重写此方法需要开发人员进行某些操作(例如调用超级实现)。