JavaFX, список в ObservableList в ListView

Итак, моя проблема в том, что у меня есть сериализованный ArrayList, и я должен обновить его в своем графическом интерфейсе, чтобы динамически отображать его содержимое в ListView. Сериализация и десериализация отлично работают с использованием интерфейса DAO, но графический интерфейс не обновляет мой ListView.

Этот класс содержит мое взаимодействие с данными (в основном сохранение, загрузка...):

public class Medienverwaltung implements Serializable, IDAO{

    private static final long serialVersionUID = 1L;
    private List<Medium> medienliste;
    public ObservableList<Medium> obList;   //public for test-reasons

    public Medienverwaltung(){
        medienliste = new ArrayList<Medium>();
        obList = FXCollections.observableArrayList(medienliste);
    }

    //[...]

    public List<Medium> getMedienliste(){
        return this.medienliste;
    }
    //[...]
}

Вот мой фрагмент реализации графического интерфейса:

public class HauptFenster extends Application{

    private Medienverwaltung medienverwaltung;

    @Override
    public void start(Stage stage) throws Exception{
        medienverwaltung = new Medienverwaltung();

        VBox root = new VBox();
        ListView<String> showliste = new ListView<String>();
        MenuBar menuBar = createMenuBar(stage);
        root.getChildren().add(menuBar);
        root.getChildren().add(showliste);

        //Make Listener and refresh the shown list!
        medienverwaltung.obList.addListener(new ListChangeListener<Medium>(){
            @Override
            public void onChanged(ListChangeListener.Change<? extends Medium> change) {
                showliste.getItems().clear();
                for(Medium medium : medienverwaltung.obList){
                    //toString() is overwritten and works, too
                    showliste.getItems().add(medium.toString());
                }
            }
        });
        // this adds a Medium object to the Arraylist in Medienverwaltung
        medienverwaltung.aufnehmen(new Bild("Foto12", 2017, "Zuhause"));

        stage.setTitle("Medien Verwaltung");
        stage.setScene(new Scene(root, 800, 400) );
        stage.show();   
    }
    //[...]

Я также устал менять весь ArrayList из класса «Medienverwaltung» на ObservableList, чтобы остался только один List, который работает для графического интерфейса, но не для сериализации и десериализации, как я догадался раньше. (и попробовал несколько других реализаций)

Кто-нибудь знает, как изменить мой код, чтобы он работал? И мой второй вопрос: как лучше всего использовать трехуровневую архитектуру?

Ниже приведена ссылка на Fabians Answer и ответ на мой комментарий к этому

Обновление №1.1 (дополнение для объяснения)

public interface IDAO {
    // Save method
    void speichern(List<Medium> liste) throws PersistenzException;
    // Load method
    List<Medium> laden() throws PersistenzException;
}

А вот и мой конкретный метод сохранения:

@Override
public void speichern(List<Medium> medienliste) throws PersistenzException{
    File sfile = new File("medienliste.dat");

    try(FileOutputStream fos = new FileOutputStream(sfile); ObjectOutputStream oos = new ObjectOutputStream(fos)){
        oos.writeObject(medienliste);
        System.out.println("Serialisierung erfolgreich!");
    }catch(IOException e){
        e.printStackTrace();
        System.out.println("Serialisierung fehlgeschlagen!");
    }
}

Обновление №1.2 (дополнение для объяснения)

//[...]  section of my GUI for saving
MenuItem speichern = new MenuItem("Speichern");
    speichern.setOnAction(new EventHandler<ActionEvent>(){
        @Override
        public void handle(ActionEvent e){
            try{
        //Before:    medienverwaltung.speichern(medienverwaltung.getMedienliste()); -> doesn't work because of serializing an ObservableList
                medienverwaltung.speichern(medienverwaltung.getBackingList());
            }catch(PersistenzException pe){
                pe.printStackTrace();
            }
        }
    });
//[...]

Но, как я предполагаю, это не лучший способ получить доступ к обратному списку таким образом.

Обновление №2:

чтобы строго соблюдать принцип инкапсуляции, я добавил перегруженный метод в класс Medienverwaltung:

public void speichern() throws PersistenzException{
    speichern(backingList);
}

Так что теперь мой графический интерфейс вызывает только speichern(). Это фактически вызывает метод сохранения с помощью списка резервных копий, который более недоступен извне. Я надеюсь, что это не плохой стиль кодирования ^^

Кстати: Если вы читаете это и у вас похожая проблема, не используйте ObservableArrayList для синхронизации с обычным List, это не сработает! Вместо этого используйте ObservableList.


person Che of Doom    schedule 05.08.2017    source источник


Ответы (1)


Скройте резервный список (medienliste) от других классов, удалив геттер. Если вы измените этот список с помощью ObservableList, ListView (или любой другой объект, который добавил прослушиватель в список) будет правильно обновлен.

Кроме того, если Medium не расширяет Node, вы можете просто использовать этот тип объекта в качестве элементов ListView, поскольку ячейки устанавливают текст в результат метода toString, вызываемого по умолчанию для связанного элемента.

public class Medienverwaltung implements Serializable, IDAO{

    private static final long serialVersionUID = 1L;
    private List<Medium> backingList;

    // transient field not persisted
    private transient ObservableList<Medium> medienliste;

    public Medienverwaltung(){
        backingList = new ArrayList<Medium>();
        medienliste = FXCollections.observableArrayList(backingList);
    }

    // make sure an ObservableList is created when reading the serialized object
    private void readObject(ObjectInputStream inputStream) throws IOException, ClassNotFoundException {
        inputStream.defaultReadObject();
        medienliste = FXCollections.observableArrayList(backingList);
    }  

    //[...]

    public ObservableList<Medium> getMedienliste(){
        return this.medienliste;
    }

    //[...]

}
@Override
public void start(Stage stage) throws Exception{
    medienverwaltung = new Medienverwaltung();

    VBox root = new VBox();
    ListView<Medium> showliste = new ListView<>(medienverwaltung.getMedienliste());

    MenuBar menuBar = createMenuBar(stage);
    root.getChildren().add(menuBar);
    root.getChildren().add(showliste);

    // this adds a Medium object to the Arraylist in Medienverwaltung
    medienverwaltung.aufnehmen(new Bild("Foto12", 2017, "Zuhause"));

    stage.setTitle("Medien Verwaltung");
    stage.setScene(new Scene(root, 800, 400) );
    stage.show();   
}

Обратите внимание, что метод Medienverwaltung.aufnehmen не должен работать напрямую со списком резервных копий — вместо этого следует использовать ObservableList, чтобы убедиться, что изменения можно наблюдать...


ИЗМЕНИТЬ

Глядя на интерфейс IDAO, вероятно, это должен быть объект, отличный от Medienverwaltung, поскольку в противном случае вы нарушили бы принцип проектирования разделения задач; также не имеет смысла передавать в качестве параметра значение, которое уже содержится в качестве свойства самого объекта.

Кажется, что объект IDAO должен отвечать только за чтение/запись данных списка, что сделало бы ненужной реализацию Serializable с Medienverwaltung. Вероятно, что-то вроде этого ожидается от вашего упражнения:

IDAO idao = new IDAOImplementation();
Medienverwaltung medienverwaltung = new Medienverwaltung(idao.laden());
public void handle(ActionEvent e){
    try{
        idao.speichern(medienverwaltung.getMedienliste());
    }catch(PersistenzException pe){
        pe.printStackTrace();
    }
}
public Medienverwaltung(List<Medium> medien) {
    this.medienliste = FXCollections.observableArrayList(medien);
}

Реализация IDAO, скорее всего, не должна зависеть от реализации List и, следовательно, не должна ожидать сериализуемости List. Вы можете просто обойти несериализованные списки, а) не используя ObjectOutputStream для сохранения данных, а каким-то другим способом, не полагаясь на сериализуемые объекты, или б) просто скопировав содержимое списка в сериализуемый список:

@Override
public void speichern(List<Medium> medienliste) throws PersistenzException{
    File sfile = new File("medienliste.dat");

    try(FileOutputStream fos = new FileOutputStream(sfile); ObjectOutputStream oos = new ObjectOutputStream(fos)){
        oos.writeObject(new ArrayList(medienliste));
        System.out.println("Serialisierung erfolgreich!");
    } catch(IOException e){
        throw new PersistenzException(e);
    }
}
person fabian    schedule 05.08.2017
comment
О, большое спасибо, это работает, насколько это возможно. Осталась только одна проблема: мне не разрешено изменять интерфейс DAO (да, я должен был упомянуть об этом раньше -.-', извините), и поэтому у меня все еще есть проблемы при сериализации. Из DAO я получаю следующие подписи: См. Обновление № 1.1. Поэтому я изменил вызов своего метода сохранения в своем графическом интерфейсе на следующее: См. Обновление № 1.2. не приятная практика. Есть ли лучший способ, которого я еще не видел? - person Che of Doom; 05.08.2017
comment
Теперь все правильно и чисто, я думаю. [1.] написал конкретный объектный класс DAO [2.] перенес методы сохранения/загрузки в конкретный объектный класс DAO [3.] передал конкретный DAO в Medienverwaltung над конструктором, который находится в неконкретной ссылке на DAO (чтобы сделать его взаимозаменяемым) [4.] Хотя я все еще использую список поддержки, но только для того, чтобы передать его DAO. Большое спасибо за помощь в улучшении моего кода, Фабиан =D - person Che of Doom; 05.08.2017