Как обновить 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
хотя, вы можете ожидать, что это вызовет много предупреждений...