1. 引言

1.1. 概述

JavaFX 是一个强大的工具,用于在不同平台上构建应用程序用户界面。它不仅提供了UI组件,还提供了诸如属性和可观察集合等有用工具。

ListView 组件对于管理集合非常方便。也就是说,我们不需要定义 DataModel 或显式更新 ListView 元素。一旦 ObservableList 中发生更改,它就会反映在 ListView 控件中。

然而,这种做法需要一种方式来在JavaFX ListView 中显示自定义项目。这篇教程将描述如何设置域对象在 ListView 中的显示方式。

1.2. JavaFX API

在Java 8、9和10中,开始使用JavaFX库时无需额外设置。从JDK 11开始,该项目将从JDK中移除,并且需要在pom.xml文件中添加以下依赖:

<dependencies>
    <dependency>
        <groupId>org.openjfx</groupId>
        <artifactId>javafx-controls</artifactId>
        <version>${javafx.version}</version>
    </dependency>
    <dependency>
        <groupId>org.openjfx</groupId>
        <artifactId>javafx-fxml</artifactId>
        <version>${javafx.version}</version>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-maven-plugin</artifactId>
            <version>${javafx-maven-plugin.version}</version>
            <configuration>
                <mainClass>Main</mainClass>
            </configuration>
        </plugin>
    </plugins>
</build>

2. 单元格工厂

2.1. 默认行为

默认情况下,JavaFX的 ListView 使用 toString() 方法来显示对象。

因此,一个明显的做法是重写它:

public class Person {
    String firstName;
    String lastName;

    @Override
    public String toString() {
        return firstName + " " + lastName;
    }
}

这种方法对学习和概念示例来说是可以的。但是,这不是最佳方法。

首先,我们的领域类承担了显示实现。因此,这种方法违背了单一责任原则。

其次,其他子系统可能也会使用 *toString()*。例如,我们使用 toString() 方法记录对象的状态。日志可能需要的字段比 ListView 的项目更多。因此,在这种情况下,单个 toString() 实现无法满足所有模块的需求。

2.2. 自定义对象在ListView中的单元格工厂

让我们考虑一种更好的方式来在JavaFX ListView 中显示自定义对象。

ListView 中的每个项目都由一个 ListCell 类的实例显示。ListCell 有一个名为 text 的属性。单元格显示其 text 值。

因此,要定制 ListCell 实例中的文本,我们需要更新其 text 属性。在哪里可以做到呢?ListCell 有一个名为 updateItem 的方法。当单元格显示项目时,它会调用 updateItem。当单元格更改时,也会运行 updateItem 方法。所以我们应该继承自默认的 ListCell 类并实现自己的版本。在这个实现中,我们需要重写 updateItem 方法。

但是,我们如何让 ListView 使用我们的自定义实现而不是默认实现?

ListView 可能有一个单元格工厂。默认情况下,单元格工厂为 null。我们需要设置它以自定义 ListView 显示对象的方式。

让我们通过一个例子说明单元格工厂:

public class PersonCellFactory implements Callback<ListView<Person>, ListCell<Person>> {
    @Override
    public ListCell<Person> call(ListView<Person> param) {
        return new ListCell<>(){
            @Override
            public void updateItem(Person person, boolean empty) {
                super.updateItem(person, empty);
                if (empty || person == null) {
                    setText(null);
                } else {
                    setText(person.getFirstName() + " " + person.getLastName());
                }
            }
        };
    }
}

单元格工厂应该实现JavaFX的回调。JavaFX中的 Callback 接口类似于标准Java的 Function 接口。但由于历史原因,JavaFX使用了 Callback 接口。

我们应该调用 updateItem 方法的默认实现。这个实现触发了默认操作,如将单元格连接到对象并为空列表显示行。

updateItem 方法的默认实现也会调用 setText。然后,它设置将在单元格中显示的文本。

2.3. 使用自定义控件在JavaFX ListView中显示自定义项目

ListCell 提供了使用自定义小部件作为内容的机会。要在自定义小部件中显示我们的领域对象,我们只需要使用 setGraphics() 而不是 *setCell()*。

假设我们必须将每一行显示为 CheckBox。让我们看看相应的单元格工厂:

public class CheckboxCellFactory implements Callback<ListView<Person>, ListCell<Person>> {
    @Override
    public ListCell<Person> call(ListView<Person> param) {
        return new ListCell<>(){
            @Override
            public void updateItem(Person person, boolean empty) {
                super.updateItem(person, empty);
                if (empty) {
                    setText(null);
                    setGraphic(null);
                } else if (person != null) {
                    setText(null);
                    setGraphic(new CheckBox(person.getFirstName() + " " + person.getLastName()));
                } else {
                    setText("null");
                    setGraphic(null);
                }
            }
        };
    }
}

在这个例子中,我们将 text 属性设置为 null。如果 textgraphic 属性都存在,文本将显示在小部件旁边。

当然,我们可以根据自定义元素数据设置 CheckBox 的回调逻辑和其他属性。这需要一些编码,就像设置小部件文本一样。

3. 总结

在这篇文章中,我们探讨了在JavaFX ListView 中显示自定义项目的途径。我们看到 ListView 提供了一种相当灵活的方式来设置它。我们甚至可以在 ListView 的单元格中显示自定义小部件。

如往常一样,示例代码可在GitHub上找到。