【问题标题】:JavaFX Custom Cell Renderer for TreeTableViewTreeTableView 的 JavaFX 自定义单元格渲染器
【发布时间】:2018-03-29 18:20:22
【问题描述】:

我正在尝试在 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());

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

线程“JavaFX 应用程序线程”中的异常 java.lang.ClassCastException: javafx.scene.control.TreeTableColumn$CellDataFeatures 不能强制转换 到我的数据

恐怕我并不真正理解所有这些类的不同泛型类型的含义,而且我也不确定“ReadOnlyObjectWrapper”。我只是尝试从第一列的设置中复制/粘贴和调整它。

如果有人能照亮我,我将非常感激。不幸的是,关于 TreeTableView 的 oracle 文档没有详细介绍,它们只是展示了简单的示例。

谢谢

【问题讨论】:

    标签: javafx treetableview


    【解决方案1】:

    您将entry(属于TreeTableColumn.CellDataFeatures&lt;MyData, MyData&gt; 类型)作为初始值传递给新ReadOnlyObjectWraper - 一个原始类型- 在运行时期望MyData 类型而不是@987654325 @。如您所见,泛型类型不匹配。

    尝试改变

    new ReadOnlyObjectWrapper(entry) 
    

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

    两次getValue() 调用的原因是因为第一个entry.getValue() 返回一个TreeItem&lt;MyData&gt;,第二个getValue() 返回实际的MyData 实例。

    这一切都假设您的表被声明为TreeTableView&lt;MyData&gt;,而您的列被声明为TreeTableColumn&lt;MyData, MyData&gt;


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

    TreeTableView<S>
    

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

    TreeTableColumn<S, T>
    

    这里的S 与该列注定要成为其中一部分的TreeTableView 中的S 相同。 T 是列中的TreeTableCell 将显示的对象类型。这通常是包含在 S 类型的属性中的值。例如 StringProperty 表示 Person 的名称,这将使 T 成为 String

    TreeTableCell<S, T>
    

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

    现在,对于值回调:

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

    同样,ST 表示Callback 所属的TreeTableColumn 的相同类型。此Callback 返回一个包含T 类型的ObservableValue,以便TreeTableCell 可以观察值的变化并相应地更新UI。在您的情况下,由于您要显示的类型未保存在属性中,因此您返回一个新的 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>
    });
    

    【讨论】:

      【解决方案2】:

      你需要

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

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

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

      我相信后两者旨在使您能够编写通用的、可重用的单元格值工厂,您可能希望根据它们所附加的列或表对其进行自定义。 (我很难想象这方面的用例,但我怀疑它们有一些场合......)

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

      【讨论】: