如何将 JavaFX TableView 与 java 记录一起使用?

问题描述 投票:0回答:1

记录Java 16的新功能。定义于JEP 395:记录

假设您有这样的记录。

public record Person(String last, String first, int age)
{
    public Person()
    {
        this("", "", 0);
    }
}

这是最后一堂课。它会自动生成 getter 方法first()、last() 和age()。

现在这是 JavaFX 中的 TableView。

/**************************************************
*   Author: Morrison
*   Date:  10 Nov 202021
**************************************************/

import javafx.application.Application;
import javafx.application.Platform;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.scene.control.TableView;
import javafx.scene.control.TableColumn;
import javafx.scene.control.cell.PropertyValueFactory;

public class TV extends Application
{
    public TV()
    {
    }

    @Override
    public void init()
    {
    }

    @Override
    public void start(Stage primary)
    {
        BorderPane root = new BorderPane();
        TableView<Person> table = new TableView<>();
        root.setCenter(table);

        TableColumn<Person, String> lastColumn = new TableColumn<>("Last");
        lastColumn.setCellValueFactory(new PropertyValueFactory<Person,
            String>("last"));

        TableColumn<Person, String> firstColumn = new TableColumn<>("First");
        firstColumn.setCellValueFactory(new PropertyValueFactory<Person,
            String>("first"));

        TableColumn<Person, Integer> ageColumn = new TableColumn<>("Age");
        ageColumn.setCellValueFactory(new PropertyValueFactory<Person,
            Integer>("age"));

        table.getColumns().add(lastColumn);
        table.getColumns().add(firstColumn);
        table.getColumns().add(ageColumn);
        table.getItems().add(new Person("Smith", "Justin", 41));
        table.getItems().add(new Person("Smith", "Sheila", 42));
        table.getItems().add(new Person("Morrison", "Paul", 58));
        table.getItems().add(new Person("Tyx", "Kylee", 40));
        table.getItems().add(new Person("Lincoln", "Abraham", 200));
        Scene s = new Scene(root, 500, 500);
        primary.setTitle("TableView Demo");
        primary.setScene(s);
        primary.show();
    }

    @Override
    public void stop()
    {
    }
}

问题就出在这里。

TableColumn<Person, String> lastColumn = new TableColumn<>("Last");
        lastColumn.setCellValueFactory(new PropertyValueFactory<Person,
            String>("last"));
        

TableView 需要名称为

getFirst
getLast
getAge
的方法来完成其工作。除了做这种可怕的事情之外还有其他解决方法吗?

public record Person(String last, String first, int age)
{
    public Person()
    {
        this("", "", 0);
    }
    public String getLast()
    {
        return last;
    }
    public String getFirst()
    {
        return first;
    }
    public int getAge()
    {
        return age;
    }
}
javafx tableview java-record
1个回答
9
投票

解决方案

使用 lambda 单元值工厂而不是

PropertyValueFactory

有关两者之间差异的一些解释,请参阅:

为什么这有效

正如您所注意到的,问题在于记录访问器不遵循标准

JavaBean 属性命名约定,而这正是 PropertyValueFactory

 所期望的。例如,一条记录使用 
first()
 而不是 
getFirst()
 作为访问器,这使得它与 
PropertyValueFactory
 不兼容。

您是否应该应用“做这件可怕的事情”的解决方法,向记录添加额外的 get 方法,这样您就可以利用

PropertyValueFactory

TableView
 进行交互? -> 
绝对不是,还有更好的方法:-)

解决这个问题需要定义您自己的自定义单元工厂,而不是使用

PropertyValueFactory

最好使用 lambda(或用于非常复杂的单元格值工厂的自定义类)来完成此操作。使用 lambda 单元工厂具有

PropertyValueFactory

 所没有的类型安全和编译时检查的优点(有关更多信息,请参阅前面引用的答案)。

定义 lambda 而不是 PropertyValueFactories 的示例

SimpleStringProperty

记录

String

 字段的 lambda 单元工厂定义的示例用法:

TableColumn<Person, String> lastColumn = new TableColumn<>("Last"); lastColumn.setCellValueFactory( p -> new SimpleStringProperty(p.getValue().last()) );
为了清楚起见,我们可以明确该 

p

 变量的类型:对 
TableColumn.CellDataFeatures
 对象的引用。

lastColumn.setCellValueFactory( ( TableColumn.CellDataFeatures < Person, String > p ) -> new SimpleStringProperty (p.getValue().last()) );
有必要将记录字段包装在属性或绑定中,因为单元格值工厂实现需要可观察的值作为输入。

ReadOnlyStringWrapper

您可以使用

ReadOnlyStringWrapper

 代替 SimpleStringProperty
,如下所示:

lastColumn.setCellValueFactory( p -> new ReadOnlyStringWrapper(p.getValue().last()).getReadOnlyProperty() );
经过快速测试,效果很好。对于不可变记录,这可能是一个更好的方法,但我还没有彻底测试它以确保安全,因此,为了安全起见,我在本示例的其余部分中使用了简单的读写属性。

对于其他类型的数据

同样,对于

int

 字段:

TableColumn<Person, Integer> ageColumn = new TableColumn<>("Age"); ageColumn.setCellValueFactory( p -> new SimpleIntegerProperty(p.getValue().age()).asObject() );
这里解释了将 

asObject()

 放在 lambda 末尾的必要性,以防您好奇(但这只是 JavaFX 框架使用 java 泛型的一个奇怪的方面,不值得花太多钱调查时间,只需添加 
asObject()
 调用并继续 IMO):

  • 为什么 asObject 允许将 SimpleLongProperty 与 setCellValueProperty 一起使用?
同样,如果您的记录包含其他对象(或其他记录),那么您可以为

SimpleObjectProperty<MyType>

 定义单元格值工厂。

注意: 这种 lambda 单元工厂定义方法和上面定义的模式也适用于标准(非记录)类。这里没有什么特别需要记录的地方。唯一需要注意的是在 lambda 中的 getValue()

 调用之后注意使用正确的访问器名称。例如,使用 
first()
 而不是通常在类上定义的标准 
getFirst()
 调用来支持标准 Java Bean 命名模式。这样做的真正好处是,如果您定义错误的访问器名称,您将收到编译器错误,并在尝试运行代码之前知道确切的问题和位置。

示例代码

基于问题中的代码的完整可执行示例。

Person.java

public record Person(String last, String first, int age) {}

RecordTableViewer.java

import javafx.application.Application; import javafx.beans.property.*; import javafx.scene.Scene; import javafx.scene.control.*; import javafx.stage.Stage; public class RecordTableViewer extends Application { @Override public void start(Stage stage) { TableView<Person> table = new TableView<>(); TableColumn<Person, String> lastColumn = new TableColumn<>("Last"); lastColumn.setCellValueFactory( p -> new SimpleStringProperty(p.getValue().last()) ); TableColumn<Person, String> firstColumn = new TableColumn<>("First"); firstColumn.setCellValueFactory( p -> new SimpleStringProperty(p.getValue().first()) ); TableColumn<Person, Integer> ageColumn = new TableColumn<>("Age"); ageColumn.setCellValueFactory( p -> new SimpleIntegerProperty(p.getValue().age()).asObject() ); //noinspection unchecked table.getColumns().addAll(lastColumn, firstColumn, ageColumn); table.getItems().addAll( new Person("Smith", "Justin", 41), new Person("Smith", "Sheila", 42), new Person("Morrison", "Paul", 58), new Person("Tyx", "Kylee", 40), new Person("Lincoln", "Abraham", 200) ); stage.setScene(new Scene(table, 200, 200)); stage.show(); } }
是否应该“固定”记录

PropertyValueFactory

记录字段访问器遵循自己的访问命名约定,

fieldname()

,就像 Java Bean 那样,
getFieldname()

可能会针对

PropertyValueFactory

 提出增强请求,以更改其在核心框架中的实现,以便它也可以识别记录访问器命名标准。

但是,我不认为更新

PropertyValueFactory

 来识别记录字段访问器是一个好主意。

更好的解决方案是不更新

PropertyValueFactory

 以获取记录支持,而只允许使用本答案中概述的类型安全自定义单元格值方法。

我相信这一点是因为 kleopatra 在评论中提供的解释:

自定义 valueFactory 绝对是可行的方法:) 即使实现与 PropertyValueFactory 等效的东西对某些人来说可能很有吸引力 - 但是:这将是一个坏主意,看看“数据未显示在”类型的问题的绝对数量表”由于拼写错误..

© www.soinside.com 2019 - 2024. All rights reserved.