用于 TreeTableView 的 JavaFX 自定义单元格渲染器

JavaFX Custom Cell Renderer for TreeTableView

我正在尝试在 JavaFX 中实现一个 TreeTableView,其中包含 'MyData' 个对象,并且有两列。第一列应包含一个字符串;这很简单:

column1.setCellValueFactory((TreeTableColumn.CellDataFeatures<MyData, String> entry) 
-> new ReadOnlyStringWrapper(entry.getValue().getValue().toString()));

对于第二列,我需要在 MyData 对象中使用一些更复杂的数据,并且我想基本上呈现一系列描述该数据的图标。所以,我尝试创建一个自定义单元格渲染器:

MyCellRenderer extends TreeTableCell<MyData, MyData> {
    @Override
    protected void updateItem(MyData item, boolean empty) {
        super.updateItem(item, empty);
        if (item == null || empty) {
            setGraphic(null);
            setText(null);
        } else {
            // building some ContentPane with an HBox of Images here..
            setGraphic(contentPane);
        }
    }
}

然后设置列CellFactory和CellValueFactory如下:

column2.setCellValueFactory((TreeTableColumn.CellDataFeatures<MyData, MyData> entry) 
-> new ReadOnlyObjectWrapper(entry));

column2.setCellFactory(param -> new MyCellRenderer());

但是我在运行时得到这个异常:

Exception in thread "JavaFX Application Thread" java.lang.ClassCastException: javafx.scene.control.TreeTableColumn$CellDataFeatures cannot be cast to MyData

恐怕我并不真正理解所有这些 类 的不同泛型类型的含义,而且我也不确定 "ReadOnlyObjectWrapper"。我只是尝试 copy/paste 并从第一列的设置中对其进行调整。

如果有人能对我有所启发,我将不胜感激。不幸的是,关于 TreeTableView 的 oracle 文档并没有涉及那么多细节,它们只是展示了简单的示例。

谢谢

你需要

column2.setCellValueFactory((TreeTableColumn.CellDataFeatures<MyData, MyData> entry) 
    -> new ReadOnlyObjectWrapper<>(entry.getValue().getValue()));

请注意,如果您不使用原始类型(即使用 ReadOnlyObjectWrapper<>ReadOnlyObjectWrapper<MyData>,而不仅仅是 ReadOnlyObjectWrapper),编译器会通知您错误,这比试图破译运行时出了什么问题要好得多。

如您所见,单元格值工厂的参数类型是 TreeTableColumn.CellDataFeatures(参见 docs)。这只是行值的包装器,您将从中提取单元格中显示的数据;这个包装器只包含行本身的树项(通过 getValue() 获得),以及单元格值工厂附加到的列 (getTreeTableColumn()) 和 table该列所属的 (getTreeTableView())。

我认为,后两者旨在让您能够编写通用的、可重用的单元格值工厂,您可能希望根据列或 table 对其进行自定义随附的。 (这方面的用例对我来说很难想象,但我怀疑他们有一些机会......)

包含行的 TreeItem(用 entry.getValue() 得到)当然包含行值本身(用 getValue() 得到,这就是为什么你最终entry.getValue().getValue()),以及其他 TreeItem 特定信息(是否展开、选择等)。

您正在将类型为 TreeTableColumn.CellDataFeatures<MyData, MyData>entry 作为初始值传递给新的 ReadOnlyObjectWraper - 原始类型 - 需要 MyData 在运行时而不是 TreeTableColumn.CellDataFeatures<MyData, MyData>。如您所见,泛型类型不匹配。

尝试更改

new ReadOnlyObjectWrapper(entry) 

new ReadOnlyObjectWrapper<>(entry.getValue().getValue())

两次 getValue() 调用的原因是第一个 entry.getValue() return 是 TreeItem<MyData> 而第二个 getValue() return 是实际 MyData 个实例。

这一切都假设您的 table 声明为 TreeTableView<MyData> 并且您的列声明为 TreeTableColumn<MyData, MyData>


编辑:既然你说你并不真正理解所有的通用签名,这里有一个简短的解释。

TreeTableView<S>

这里的STreeTableView显示的对象类型。又名,模型 class。一个例子是 Person class 这将使 S 成为 Person.

TreeTableColumn<S, T>

这里的STreeTableView中的S是一样的,该列注定是其中的一部分。 T 是列中 TreeTableCell 将显示的对象类型。这通常是 S 类型的 属性 中包含的值。例如 StringProperty 的名称 Person 会使 T 成为 String.

TreeTableCell<S, T>

ST 与单元格所属的 TreeTableColumn 相同。

现在,对于值回调:

Callback<TreeTableColumn.CellDataFeatures<S, T>, ObservableValue<T>>

同样,ST 表示 Callback 所属的 TreeTableColumn 的相同类型。这个 Callback return 是一个包含类型 TObservableValue 以便 TreeTableCell 可以观察变化的值并相应地更新 UI .在您的情况下,由于您要显示的类型不在 属性 中,您 return 一个新的 ReadOnlyObjectWrapper 来满足 API 要求。如果我继续使用上面给出的名称 StringProperty 示例,您可能会得到如下结果:

TreeTableView<Person> table = ..;
TreeTableColumn<Person, String> column = ...;
column.setCellValueFactory(dataFeatures -> {
    // This could all be done in one line but I figured I'd
    // make it explicit to show all the types used.
    TreeItem<Person> item = dataFeatures.getValue();
    Person person = item.getValue();
    return person.nameProperty(); // returns StringProperty which is an
                                  // ObservableStringValue which in turn
                                  // is an ObservableValue<String>
});