Как обновить JavaFX TableView, если привязка вложенного объекта изменяется?

У меня есть ObservableList<Member> членов, которые я хочу отобразить в TableView<Member>,

Member класс состоит из Person (и другие значения, которые не имеют значения, так как они не появятся в табличном представлении). каждый Person имеет StringProperty "имя" и ObjectProperty<Dog> "собака". Также каждая собака имеет StringProperty "название".

В табличном представлении должно быть указано имя человека в первом столбце и имя собаки во втором столбце. Как это: Пример табличного представления

Мне удалось достичь этого, связав оба TableColumn<Member, Person> к member.personProperty() и использование пользовательских клеточных фабрик для отображения имени человека или имени собаки.

Теперь я также должен ComboBoxс помощью которого пользователь может обновить имена в выбранной строке TableView, Поэтому я создал привязки между выбранным элементом и valueProperty() комбо-боксов.

В то время как табличное представление обновляет ячейку для имени человека, если оно изменяется через поле со списком, ячейка для имени собаки автоматически не показывает новое значение. Я знаю, что это потому, что Member не получает уведомления об изменениях в свойстве собаки. Но я не нашел решения, как сделать объект осведомленным об изменениях в его подчиненных объектах.

Таким образом, я предполагаю, что в итоге мои вопросы: (1) как отображать вложенные объекты в виде плоской таблицы и (2) как автоматически обновлять представление таблицы, если изменяется какое-либо из (потенциально) вложенных свойств.

Пример кода:

package sample;

import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.util.StringConverter;

public class ControllerString {
    private class Dog
    {
        private final StringProperty name;

        public Dog(String name) {
            this.name = new SimpleStringProperty(name);
        }

        public String getName() {
            return name.get();
        }

        public StringProperty nameProperty() {
            return name;
        }

        public void setName(String name) {
            this.name.set(name);
        }
    }

    private class Person
    {
        private final StringProperty name;
        private final ObjectProperty<Dog> dog;

        public Person(String name, Dog dog) {
            this.name = new SimpleStringProperty(name);
            this.dog = new SimpleObjectProperty<>(dog);
        }

        public String getName() {
            return name.get();
        }

        public StringProperty nameProperty() {
            return name;
        }

        public void setName(String name) {
            this.name.set(name);
        }

        public Dog getDog() {
            return dog.get();
        }

        public ObjectProperty<Dog> dogProperty() {
            return dog;
        }

        public void setDog(Dog dog) {
            this.dog.set(dog);
        }
    }

    private class Member
    {
        private final ObjectProperty<Person> person;

        public Member(Person person) {
            this.person = new SimpleObjectProperty<>(person);
        }

        public Person getPerson() {
            return person.get();
        }

        public ObjectProperty<Person> personProperty() {
            return person;
        }

        public void setPerson(Person person) {
            this.person.set(person);
        }
    }

    @FXML
    private TableView<Member> membersTableView;
    @FXML
    private TableColumn<Member, Person> nameTableColumn;
    @FXML
    private TableColumn<Member, Person> dogTableColumn;
    @FXML
    private ComboBox<Person> nameComboBox;
    @FXML
    private ComboBox<Dog> dogComboBox;

    private final ObservableList<Member> members = FXCollections.observableArrayList();

    @FXML
    private void initialize()
    {
        nameTableColumn.setCellValueFactory(cellData -> cellData.getValue().personProperty());
        nameTableColumn.setCellFactory(column -> new TableCell<Member, Person>() {
            @Override
            protected void updateItem(Person person, boolean empty) {
                super.updateItem(person, empty);

                setContentDisplay(ContentDisplay.TEXT_ONLY);

                if(person == null || empty)
                {
                    setText(null);
                }
                else
                {
                    setText(person.getName());
                }
            }
        });

        dogTableColumn.setCellValueFactory(cellData -> cellData.getValue().personProperty());
        dogTableColumn.setCellFactory(column -> new TableCell<Member, Person>()
        {
            @Override
            protected void updateItem(Person person, boolean empty) {
                super.updateItem(person, empty);

                setContentDisplay(ContentDisplay.TEXT_ONLY);

                if(person == null || empty)
                {
                    setText(null);
                }
                else
                {
                    setText(person.getDog().getName());
                }
            }
        });

        nameComboBox.setConverter(new StringConverter<Person>() {
            @Override
            public String toString(Person person) {
                if(person == null)
                {
                    return null;
                }

                return person.getName();
            }

            @Override
            public Person fromString(String name) {
                return new Person(name, new Dog("Puppy"));
            }
        });

        dogComboBox.setConverter(new StringConverter<Dog>() {
            @Override
            public String toString(Dog dog) {
                if(dog == null)
                {
                    return null;
                }

                return dog.getName();
            }

            @Override
            public Dog fromString(String name) {
                return new Dog(name);
            }
        });

        membersTableView.getSelectionModel().getSelectedItems().addListener((ListChangeListener<Member>) change -> {
            while(change.next()) {
                if(change.wasRemoved()) {
                    Member oldValue = change.getRemoved().get(0);
                    nameComboBox.valueProperty().unbindBidirectional(oldValue.personProperty());
                }

                if(change.wasAdded()) {
                    Member newValue = change.getAddedSubList().get(0);
                    nameComboBox.valueProperty().bindBidirectional(newValue.personProperty());
                }
            }
        });

        nameComboBox.valueProperty().addListener(((observable, oldValue, newValue) -> {
            if(oldValue != null) {
                dogComboBox.valueProperty().unbindBidirectional(oldValue.dogProperty());
            }

            if(newValue != null)
            {
                dogComboBox.valueProperty().bindBidirectional(newValue.dogProperty());
            }
        }));

        membersTableView.setItems(members);

        members.add(new Member(new Person("Bob", new Dog("Caesar"))));
    }
}

1 ответ

Решение

Если вы измените видимость Person а также Dog в public, Bindings.select может быть использован с cellValueFactory s, который позволяет избавиться от обычая TableCell реализации:

public class Dog {
    ...
}

public class Person {
    ...
}

...

@FXML
private TableColumn<Member, String> nameTableColumn;
@FXML
private TableColumn<Member, String> dogTableColumn;

...

@FXML
private void initialize() {
    nameTableColumn.setCellValueFactory(cellData -> Bindings.select(cellData.getValue().personProperty(), "name"));
    dogTableColumn.setCellValueFactory(cellData -> Bindings.select(cellData.getValue().personProperty(), "dog", "name"));
    ...

Если одно из промежуточных свойств может содержать null хотя, вы можете ожидать, что это вызовет много предупреждений...

Другие вопросы по тегам