【问题标题】:How do you use a JavaFX TableView with java records?如何将 JavaFX TableView 与 java 记录一起使用?
【发布时间】:2022-01-07 12:51:42
【问题描述】:

Java 记录是 Java15 的一项新功能。假设您有这样的记录。

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 期望名称为 getFirstgetLastgetAge 的方法来完成其工作。除了做这种可怕的事情之外,还有其他解决方法吗?

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;
    }
}

【问题讨论】:

  • 这是基本功能;检查使用 Java 8 或更高版本编写的 TableView 上的任何合理教程。只需为每一列实现cellValueFactory

标签: javafx tableview java-record


【解决方案1】:

解决方案

使用 lambda 单元格值工厂而不是 PropertyValueFactory

关于两者区别的一些解释,见:

为什么会这样

正如您所注意到的,问题是记录访问器不遵循标准的 java bean 属性命名约定,这是 PropertyValueFactory 所期望的。例如,记录使用first() 而不是getFirst() 作为访问器,这使得它与PropertyValueFactory 不兼容。

您是否应该应用“做这件可怕的事情”的解决方法,向记录添加额外的 get 方法,以便您可以使用 PropertyValueFactoryTableView 进行交互? -> 绝对不行,有更好的办法:-)

修复它需要定义您自己的自定义单元工厂,而不是使用PropertyValueFactory

最好使用 lambda(或用于非常复杂的单元格值工厂的自定义类)来完成。使用 lambda 单元工厂具有 PropertyValueFactory 所没有的类型安全和编译时检查的优势(有关更多信息,请参阅先前参考的答案)。

定义 lambdas 而不是 PropertyValueFactories 的示例

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

TableColumn<Person, String> lastColumn = new TableColumn<>("Last");
lastColumn.setCellValueFactory(
        p -> new SimpleStringProperty(p.getValue().last())
);

有必要将记录字段包装在属性或绑定中,因为单元格值工厂实现需要一个可观察值作为输入。

您可以使用 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):

同样,如果您的记录包含其他对象(或其他记录),那么您可以为SimpleObjectProperty&lt;MyType&gt; 定义一个单元格值工厂。

注意:这种用于 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 在 cmets 中提供的解释:

自定义 valueFactory 绝对是要走的路 :) 即使它可能看起来对某些人来说很有吸引力来实现与 PropertyValueFactory 的等价物 - 但是:这将是一个坏主意,看看“数据不是”类型的问题的绝对数量由于拼写错误,显示在表格中..

【讨论】:

  • 如果有时间,我会整理一个具体的例子。
  • cell -&gt; cell.getValue()::first 等等,如果我没有犯错的话。请添加一些代码,否则您的答案最好作为评论给出。
  • @Joop 实际上类似于data -&gt; new SimpleStringProperty(data.getValue().first()),因为它需要返回一个ObservableValue
  • 已编辑以添加完整示例并为不同的记录字段类型提供示例 lambda 定义。
  • 自定义 valueFactory 绝对是要走的路 :) 即使对某些人来说实现与 PropertyValueFactory 等效的东西可能看起来很有吸引力 - 但是:这将是一个坏主意,看看问题的绝对数量由于拼写错误,类型为“数据未显示在表格中”..
猜你喜欢
  • 2017-04-20
  • 1970-01-01
  • 2021-06-16
  • 1970-01-01
  • 2013-04-15
  • 2021-10-17
  • 2018-08-06
  • 2021-05-09
  • 1970-01-01
相关资源
最近更新 更多