绑定到 TableView 的 ObservableMap 不更新新值

ObservableMap bound to TableView not updating new values

我有一个 ObservableMap,我想将其绑定到 table 视图,以显示地图值具有的一些属性,但地图上的新值未显示。在 SO 上搜索和谷歌搜索我发现大部分相同的代码但没有解决方案。

这是一个解释问题的 SSCCE,我发现的唯一 'solution':使用地图的值创建一个新的 ObservableList 并将其设置为 table 每次地图都会改变,这不是一个很好的解决方案。

public class MapTableBindTest extends Application {

    int personIDCounter;

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

    @Override
    public void start(Stage primaryStage) {
        personIDCounter = 0;
        // Map - TableView binding
        ObservableMap<Integer, Person> map = FXCollections.observableHashMap();

        Person first = new Person("First", "Person");   // this person is mapped before the bind to the table
        map.put(first.getID(), first);                  // and because that, is shown on it

        ObservableList<Map.Entry<Integer, Person>> list = FXCollections.observableArrayList(map.entrySet());
        TableView<Map.Entry<Integer, Person>> table = new TableView<>(list);

        Person second = new Person("Second", "Person"); // once the bind has been setted no new items
        map.put(second.getID(), second);                // are shown on the table. Same case when added in the UI

        // Columns
        TableColumn<Map.Entry<Integer, Person>, String> nameCol = new TableColumn<>("Name");
        nameCol.setCellValueFactory(cellData -> cellData.getValue().getValue().getName());
        TableColumn<Map.Entry<Integer, Person>, String> lastNameCol = new TableColumn<>("Surname");
        lastNameCol.setCellValueFactory(cellData -> cellData.getValue().getValue().getSurname());
        table.getColumns().addAll(nameCol, lastNameCol);

        // View
        BorderPane root = new BorderPane();
        TextField nameField = new TextField();
        TextField lastNameField = new TextField();
        Button addButton = new Button("Add");
        addButton.setOnMouseClicked(event -> {
            Person newP = new Person(nameField.getText(), lastNameField.getText());
            map.put(newP.getID(), newP);    // new items are not populated in the table...
            nameField.clear();
            lastNameField.clear();
            //                      ... unless a new observable list is created and setted to the table
            ObservableList<Map.Entry<Integer, Person>> auxiliarList = FXCollections.observableArrayList(map.entrySet());
            table.setItems(auxiliarList);
            //                      commenting this two lines result with no updates shown in the table
        });
        HBox TextHB = new HBox(nameField, lastNameField, addButton);
        root.setTop(TextHB);
        root.setCenter(table);

        Scene scene = new Scene(root,400,500);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    // Sample class with some properties
    class Person {

        private int id;
        private StringProperty name, surname;

        public Person(String name, String surname) {
            id = personIDCounter++;
            this.name = new SimpleStringProperty(name);
            this.surname = new SimpleStringProperty(surname);
        }

        public int getID() {
            return id;
        }

        public StringProperty getName() {
            return name;
        }

        public StringProperty getSurname() {
            return surname;
        }
    }
}

地图的 entryset()ObservableList 之间的绑定可能有问题。 javadoc 说 entrySet() of an ObservableMap:

Returns a Set view of the mappings contained in this map. The set is backed by the map, so changes to the map are reflected in the set, and vice-versa. (...)

这并不清楚,否则 TableView 将随着更改而更新。

解决方案(感谢James_D): ObservableMap adds/remove 上的此侦听器值在 ObservableList 中更改,因此显示在 table.

map.addListener((MapChangeListener.Change<? extends Integer, ? extends Person> c) -> {
        if (c.wasAdded()) {
            Person added = c.getValueAdded();
            list.add(new AbstractMap.SimpleEntry<Integer, Person>(added.getID(), added));
        } else if (c.wasRemoved()) {
            Person removed = c.getValueRemoved();
            list.remove(new AbstractMap.SimpleEntry<Integer, Person>(removed.getID(), removed));
        }
    });

当您向其中添加项目时,您的地图会发生变化,entrySet() 返回的集合也会发生变化。但是,该集合不是可观察集合,因此 ObservableList 不可能 "see" 发生任何变化。

相反,您需要将列表直接连接到地图。您可以简单地通过使 table 视图成为 TableView<Person> 然后向地图注册一个侦听器来做到这一点:

map.addListener((Change<? extends Integer, ? extends Person> c) -> {
    if (c.wasAdded()) {
        list.add(c.getValueAdded());
    } else if (c.wasRemoved()) {
        list.remove(c.getValueRemoved());
    }
});

完整示例如下:

import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.MapChangeListener.Change;
import javafx.collections.ObservableList;
import javafx.collections.ObservableMap;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

public class MapTableBindTest extends Application {

    int personIDCounter;

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

    @Override
    public void start(Stage primaryStage) {
        personIDCounter = 0;
        // Map - TableView binding
        ObservableMap<Integer, Person> map = FXCollections.observableHashMap();

        Person first = new Person("First", "Person");   // this person is mapped before the bind to the table
        map.put(first.getID(), first);                  // and because that, is shown on it

        ObservableList<Person> list = FXCollections.observableArrayList(map.values());
        TableView<Person> table = new TableView<>(list);

        map.addListener((Change<? extends Integer, ? extends Person> c) -> {
            if (c.wasAdded()) {
                list.add(c.getValueAdded());
            } else if (c.wasRemoved()) {
                list.remove(c.getValueRemoved());
            }
        });

        Person second = new Person("Second", "Person"); // once the bind has been setted no new items
        map.put(second.getID(), second);                // are shown on the table. Same case when added in the UI

        // Columns
        TableColumn<Person, String> nameCol = new TableColumn<>("Name");
        nameCol.setCellValueFactory(cellData -> cellData.getValue().getName());
        TableColumn<Person, String> lastNameCol = new TableColumn<>("Surname");
        lastNameCol.setCellValueFactory(cellData -> cellData.getValue().getSurname());
        table.getColumns().addAll(nameCol, lastNameCol);

        // View
        BorderPane root = new BorderPane();
        TextField nameField = new TextField();
        TextField lastNameField = new TextField();
        Button addButton = new Button("Add");
        addButton.setOnMouseClicked(event -> {
            Person newP = new Person(nameField.getText(), lastNameField.getText());
            map.put(newP.getID(), newP);    // new items are not populated in the table...
            nameField.clear();
            lastNameField.clear();
        });
        HBox TextHB = new HBox(nameField, lastNameField, addButton);
        root.setTop(TextHB);
        root.setCenter(table);

        Scene scene = new Scene(root,400,500);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    // Sample class with some properties
    class Person {

        private int id;
        private StringProperty name, surname;

        public Person(String name, String surname) {
            id = personIDCounter++;
            this.name = new SimpleStringProperty(name);
            this.surname = new SimpleStringProperty(surname);
        }

        public int getID() {
            return id;
        }

        public StringProperty getName() {
            return name;
        }

        public StringProperty getSurname() {
            return surname;
        }
    }
}