Статьи

Свойства Extractor: Лучший способ получить ListView мгновенно обновляя свои элементы

Этот пост посвящен тому, как работать с JavaFX ListViews и TableViews и как эти элементы управления получают информацию об измененном содержимом содержащихся элементов. Интересно, почему я не нашел ничего о следующем паттерне в соответствующих книгах, так как это действительно важный механизм. Во многих публикациях предлагается принудительно вызвать ChangeEvent, чтобы получить ListView для обновления путем вызова:

list.remove(POJO);
list.add(index,POJO);

после каждого внесенного изменения! Бррр!

Но есть гораздо лучший способ:

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

Демо-приложение

Я создал небольшое демонстрационное приложение, чтобы попробовать его. В основном два TableViews и один ListView все совместно используют одни и те же данные. Чтобы изменить свойства элементов, один TableView доступен для редактирования:

PropertiesExtractor-Demo1

Модель данных

Компульсивный PersonBean, свертывающий JavaFX Bean Pattern / Convention

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
public class PersonBean {
 
    private StringProperty firstName;
    private StringProperty lastName;
    private ObjectProperty<LocalDate> birthday;
    private ObjectBinding<Long> age;
 
    public PersonBean() {
    }
 
    public PersonBean(String firstName, String lastName, LocalDate birthday) {
        setFirstName(firstName);
        setLastName(lastName);
        setBirthday(birthday);
    }
 
    public final StringProperty firstNameProperty() {
        if (firstName == null) {
            firstName = new SimpleStringProperty();
        }
        return firstName;
    }
 
    public final String getFirstName() {
        return firstNameProperty().get();
    }
 
    public final void setFirstName(final java.lang.String firstName) {
        firstNameProperty().set(firstName);
    }
 
    public final StringProperty lastNameProperty() {
        if (lastName == null) {
            lastName = new SimpleStringProperty();
        }
        return lastName;
    }
 
    public final java.lang.String getLastName() {
        return lastNameProperty().get();
    }
 
    public final void setLastName(final java.lang.String lastName) {
        lastNameProperty().set(lastName);
    }
 
    public final ObjectProperty<LocalDate> birthdayProperty() {
        if (birthday == null) {
            birthday = new SimpleObjectProperty<>();
        }
        return birthday;
    }
 
    public final LocalDate getBirthday() {
        return birthdayProperty().get();
    }
 
    public final void setBirthday(final java.time.LocalDate birthday) {
        birthdayProperty().set(birthday);
 
    }
 
    public String stringValue() {
        return String.format("%s %s %s", getFirstName(), getLastName(), getBirthday().format(DateTimeFormatter.ISO_LOCAL_DATE));
    }
 
    public final ObjectBinding<Long> ageBinding() {
        if (age == null) {
            age = new ObjectBinding<Long>() {
                {
                    bind(birthdayProperty());
                }
 
                @Override
                protected Long computeValue() {
                    if (getBirthday() == null) {
                        return null;
                    }
                    return getBirthday().until(LocalDate.now(), ChronoUnit.YEARS);
                }
            };
        }
        return age;
    }
 
    public static Callback<PersonBean, Observable[]> extractor() {
        return (PersonBean p) -> new Observable[]{p.lastNameProperty(), p.firstNameProperty(), p.birthdayProperty(), p.ageBinding()};
    }
}

DataModel, содержащий список случайно созданных PersonBeans:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
public class DataModel {
 
    private ObservableList<PersonBean> personFXBeans;
 
    public DataModel() {
        init();
    }
 
    private void init() {
        personFXBeans = DataSource.getRandomPersonBeansList(100);
    }
 
    public ObservableList<PersonBean> getPersonFXBeans() {
        return personFXBeans;
    }
}

Как вы, возможно, знаете, чтобы назначить DataModel, например, для TableView или ListView в JavaFX, вам просто нужно использовать метод setItems (ObvervableList).

1
2
3
4
5
6
@FXML
public void onFillWithDemoDataFXBeans() {
  readOnlyListView.setItems(model.getPersonFXBeans());
  readOnlyTableView.setItems(model.getPersonFXBeans());
  editableTableView.setItems(model.getPersonFXBeans());
}

Теперь получение TableView для информирования об изменениях свойств содержащихся элементов уже выполняется с помощью привязки двумя способами: с помощью PropertyValueFactory и с помощью более или менее прямой привязки свойства:

1
2
3
4
5
6
7
8
9
readOnlyFirstNameColumn.setCellValueFactory(new PropertyValueFactory<>("firstName"));
readOnlyLastNameColumn.setCellValueFactory(new PropertyValueFactory<>("lastName"));
readOnlyBirthdayColumn.setCellValueFactory(new PropertyValueFactory<>("birthday"));
readOnlyAgeColumn.setCellValueFactory(i -> i.getValue().ageBinding());
 
editableFirstNameColumn.setCellValueFactory(i -> i.getValue().firstNameProperty());
editableLastNameColumn.setCellValueFactory(i -> i.getValue().lastNameProperty());
editableBirthdayColumn.setCellValueFactory(i -> i.getValue().birthdayProperty());
ageColumn.setCellValueFactory(i -> i.getValue().ageBinding());

Но ListView в основном наблюдает только за списком, а не за свойствами каждого элемента этого списка.

При использовании ObservableList, созданного с помощью FXCollections.observableArrayList (), ListView будет обновляться только для событий ListChange, таких как remove () и add () элементов. Следовательно:

list.remove(POJO);
list.add(index,POJO);

после каждого внесенного изменения.

Но есть гораздо лучший способ:

Включите ваш список, чтобы сообщить об изменениях элемента, предоставив экстрактор свойств. Вам не нужно заботиться об обновлении!

ObservableList people = FXCollections.observableArrayList ( PersonBean.extractor () );

Смотрите DataSource.getRandomPersonBeansList(int length) :

1
2
3
4
5
6
7
public static ObservableList<PersonBean> getRandomPersonBeansList(int length) {
        ObservableList<PersonBean> persons = FXCollections.observableArrayList(PersonBean.extractor());
        for (int i = 0; i < length; i++) {
            persons.add(new PersonBean(getRandomName(), getRandomLastname(), getRandomLocalDate()));
        }
        return persons;
    }

Этот экстрактор в основном является обратным вызовом, содержащим массив Obvervables, которые затем наблюдаются Obervablelist (более точно: ObservableListWrapper):

Мой PersonBean уже обеспечивает обратный вызов извлечения:

1
2
3
public static Callback<PersonBean, Observable[]> extractor() {
   return (PersonBean p) -> new Observable[]{p.lastNameProperty(), p.firstNameProperty(), p.birthdayProperty(), p.ageBinding()};
}

В соответствии с этим шаблоном все элементы управления обновляются сразу после применения изменения.

Редактировать данные …

PropertiesExtractor-Demo3

и совершить:

PropertiesExtractor-Demo4

КОД, ПОЖАЛУЙСТА!

Вы можете найти полный код в моем BitBucket Repo .