如何使用自定义对象在 JavaFX 中填充 ListView?

2022-09-01 03:12:24

我对Java,JavaFX和一般的编程有点陌生,我有一个问题让我的大脑崩溃。

在我查找的大多数有关填充ListView的教程中(更具体地说,使用ObservableArrayList),最简单的方法是从ObservableList of Strings制作它,如下所示:

ObservableList<String> wordsList = FXCollections.observableArrayList("First word","Second word", "Third word", "Etc."); 
ListView<String> listViewOfStrings = new ListView<>(wordsList);

但我不想使用字符串。我想使用我制作的名为Words的自定义对象:

ObservableList<Word> wordsList = FXCollections.observableArrayList();
wordsList.add(new Word("First Word", "Definition of First Word");
wordsList.add(new Word("Second Word", "Definition of Second Word");
wordsList.add(new Word("Third Word", "Definition of Third Word");
ListView<Word> listViewOfWords = new ListView<>(wordsList);

每个 Word 对象只有 2 个属性:wordString(单词的字符串)和定义(另一个字符串是单词的定义)。我为两者都有getter和setters。

您可以看到这是怎么回事 - 代码编译和工作,但是当我在应用程序中显示它时,它不是在ListView中显示每个单词的标题,而是将Word对象本身显示为字符串!

Image showing my application and its ListView

具体来说,我的问题是,有没有一种简单的方法来重写它:

ListView<Word> listViewOfWords = new ListView<>(wordsList);

以这种方式,它不是直接从wordsList中获取Words,而是访问我的可观察ArrayList的每个Word中的wordString属性?

需要明确的是,这不是针对android的,单词列表最终会被更改,保存和加载,所以我不能只是制作另一个数组来保存单词Strings。我已经在网上做了一些研究,似乎有一个叫做“细胞工厂”的东西,但对于一个看似如此简单的问题来说,它似乎不必要地复杂,正如我之前所说,在编程方面,我有点新手。

任何人都可以帮忙吗?这是我第一次来这里,所以如果我没有包含足够的代码或者我做错了什么,我很抱歉。


答案 1

解决方案方法

我建议使用细胞工厂来解决这个问题。

listViewOfWords.setCellFactory(param -> new ListCell<Word>() {
    @Override
    protected void updateItem(Word item, boolean empty) {
        super.updateItem(item, empty);

        if (empty || item == null || item.getWord() == null) {
            setText(null);
        } else {
            setText(item.getWord());
        }
    }
});

示例应用程序

add image

import javafx.application.Application;
import javafx.collections.*;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.stage.Stage;

public class CellFactories extends Application {    
    @Override
    public void start(Stage stage) {
        ObservableList<Word> wordsList = FXCollections.observableArrayList();
        wordsList.add(new Word("First Word", "Definition of First Word"));
        wordsList.add(new Word("Second Word", "Definition of Second Word"));
        wordsList.add(new Word("Third Word", "Definition of Third Word"));
        ListView<Word> listViewOfWords = new ListView<>(wordsList);
        listViewOfWords.setCellFactory(param -> new ListCell<Word>() {
            @Override
            protected void updateItem(Word item, boolean empty) {
                super.updateItem(item, empty);

                if (empty || item == null || item.getWord() == null) {
                    setText(null);
                } else {
                    setText(item.getWord());
                }
            }
        });
        stage.setScene(new Scene(listViewOfWords));
        stage.show();
    }

    public static class Word {
        private final String word;
        private final String definition;

        public Word(String word, String definition) {
            this.word = word;
            this.definition = definition;
        }

        public String getWord() {
            return word;
        }

        public String getDefinition() {
            return definition;
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}

实施说明

尽管您可以在 Word 类中重写 toString 以提供针对在 ListView 中表示的单词的字符串表示形式,但我建议在 ListView 中提供一个单元格工厂,以便从单词对象中提取视图数据并在 ListView 中表示该对象。使用这种方法,您可以分离关注点,因为您不会将Word对象的图形视图与其文本toString方法联系起来;因此 toString 可以继续具有不同的输出(例如,有关 Word 字段的完整信息,其中包含单词名称和用于调试目的的描述)。此外,单元工厂更灵活,因为您可以应用各种图形节点来创建数据的可视化表示形式,而不仅仅是直接文本字符串(如果您希望这样做)。

另外,顺便说一句,我建议通过删除它们的 setter 来使 Word 对象不可变。如果您确实需要修改单词对象本身,那么处理该单词的最佳方法是为对象字段公开可观察属性。如果您还希望 UI 随着对象的可观察属性的更改而更新,则需要通过侦听对关联项的更改来使列表单元格了解对关联项的更改(在这种情况下,提取器会稍微复杂一些)。请注意,包含单词的列表已经是可观察的,ListView 将负责处理对该列表的更改,但是如果您修改了单词定义,例如在显示的单词对象中,则如果没有在 ListView 单元工厂中具有适当的侦听器逻辑,则列表视图将无法选取对定义的更改(或者, 优选地,提取器)。


答案 2

我敢打赌,ListView在Word上调用toString()方法。如果你还没有重写它,它使用默认的toString()方法,它只是输出...不太有用的信息。覆盖它以输出一个漂亮的格式化字符串,你应该很高兴!


推荐