Как я могу заполнить ListView в JavaFX с помощью пользовательских объектов?

Я немного новичок в 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 (строка слова) и определение (еще одна строка, являющаяся определением слова). У меня есть геттеры и сеттеры для обоих.

Вы можете видеть, где это going-, код компилируется и работает, но когда я отображаю его в своем приложении, а не отображает заголовки каждого слова в ListView, он отображает сам объект Word в виде строки!

Image showing my application and its ListView

Мой вопрос здесь, в частности, есть ли простой способ переписать это:

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

Таким образом, что вместо того, чтобы брать слова непосредственно из wordsList, он обращается к свойству wordString в каждом слове моего observableArrayList?

Просто чтобы быть понятным, это не для Android, и список слов будет изменен, сохранен и загружен в конце концов, поэтому я не могу просто создать другой массив для хранения wordStrings. Я провел небольшое исследование в Интернете, и, кажется, есть такая вещь, как "Сотовые фабрики", но она кажется излишне сложной для того, что кажется такой простой проблемой, и, как я уже говорил, я немного новичок, когда дело доходит до программирования.

Кто-нибудь может помочь? Это мой первый раз здесь, поэтому я извиняюсь, если я не включил достаточно моего кода, или я сделал что-то не так.

Ответы

Ответ 1

Подход к решению

Я советую использовать cell factory для решения этой проблемы.

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

Пример приложения

добавить изображение

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

Замечания по реализации

Хотя вы можете переопределить toString в своем классе Word, чтобы предоставить строковое представление слова, предназначенного для представления в вашем ListView, я бы рекомендовал предоставить ячейку factory в ListView для извлечения данных вида из объекта слова и представление его в вашем ListView. Используя этот подход, вы получаете разделение проблем, поскольку вы не привязываете графическое представление своего объекта Word к нему текстовым методом toString; поэтому toString может продолжать иметь другой вывод (например, полную информацию о полях Word с именем слова и описанием для целей отладки). Кроме того, ячейка factory более гибкая, так как вы можете применять различные графические узлы для создания визуального представления ваших данных за пределы простой текстовой строки (если вы хотите это сделать).

Кроме того, в качестве стороннего я рекомендую сделать объекты Word неизменяемыми объектами, удалив их сеттеры. Если вам действительно нужно изменить сами слова-объекты, тогда лучший способ справиться с ними - открыть видимые свойства для полей объекта. Если вы также хотите, чтобы ваш пользовательский интерфейс обновлялся по мере изменения наблюдаемых свойств ваших объектов, вам необходимо, чтобы ваши ячейки списка знали об изменениях связанных элементов, прослушивая их изменения (что довольно сложно в этом дело). Обратите внимание, что список, содержащий слова, уже наблюдается, и ListView позаботится об обработке изменений в этом списке, но если вы изменили определение слова для экземпляра в отображаемом объекте слова, тогда ваше представление списка не подберет изменения в определение без соответствующей логики слушателя в ячейке ListView factory.

Ответ 2

Я бы поставил вам никель, который ListView вызывает метод toString() в Word. Если вы не переопределили его, он использует метод toString() по умолчанию, который просто выводит эту... не очень полезную информацию. Переопределите его, чтобы вывести красиво отформатированную строку, и вам должно быть хорошо идти!

Ответ 3

В настоящее время ваш ListView отображает Word toString(). Чтобы исправить вашу проблему, просто добавьте следующий метод в свой класс Word (это только пример):

@Override
public String toString(){
    return (this.word + " --- Definition: " + this.definition);
}