Я хотел бы отобразить список лиц (закодированных в POJOS и содержащий имя и свойство фамилии) с помощью элемента управления ListView JavaFX. Я создал ListView и добавил список людей как ObservableList. Все работает нормально, если я удалю или добавлю нового человека в ObservableList, но изменения в POJO не инициируют обновление ListView. Я должен удалить и добавить измененный POJO из ObservableList, чтобы вызвать обновление ListView. Есть ли возможность отображать изменения в POJOS без обходного пути, описанного выше?
JavaFX: обновление ListView при изменении элемента ObservableList
Ответ 1
Есть несколько аспектов вашего вопроса (и я не совсем в том, что является проблемой:-) Я буду считать, что ваш POJO каким-то образом уведомляет слушателей об изменениях, может быть, будучи полноценным JavaBean, который соответствует к его уведомляющему контракту через свойство fring. Измените события по мере необходимости или какие-либо другие средства - в противном случае вам понадобится ручной нажим изменения в любом случае.
Основной подход, чтобы заставить FX-ObservableList уведомлять своих собственных слушателей о мутациях содержащихся элементов, состоит в том, чтобы настроить его с помощью специального обратного вызова, который предоставляет массив Observables. Если у элементов есть fx-properties, вы бы сделали что-то вроде:
Callback<Person, Observable[]> extractor = new Callback<Person, Observable[]>() {
@Override
public Observable[] call(Person p) {
return new Observable[] {p.lastNameProperty(), p.firstNameProperty()};
}
};
ObservableList<Person> teamMembers = FXCollections.observableArrayList(extractor);
// fill list
Если pojo - полноценный ядро javaBean, его свойства должны быть адаптированы к fx-properties, f.i. с помощью JavaBeanProperty:
Callback<PersonBean, Observable[]> extractor = new Callback<PersonBean, Observable[]>() {
List<Property> properties = new ArrayList<Property>();
@Override
public Observable[] call(PersonBean arg0) {
JavaBeanObjectProperty lastName = null;
JavaBeanObjectProperty age = null;
try {
lastName = JavaBeanObjectPropertyBuilder.create()
.bean(arg0).name("lastName").build();
age = JavaBeanObjectPropertyBuilder.create()
.bean(arg0).name("age").build();
// hack around loosing weak references ...
properties.add(age);
properties.add(lastName);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
return new Observable[] {lastName, age};
}
};
ObservableList<Person> teamMembers = FXCollections.observableArrayList(extractor);
// fill list
Обратите внимание на предостережение: не сохраняя ссылки на адаптированные свойства где-нибудь, они будут быстро собраны в мусор, а затем, похоже, не будут иметь никакого эффекта (попадают в ловушку снова и снова, не уверены, как, если есть хорошая стратегия, чтобы избежать этого).
Для любых других средств (возможно, грубозернистого) уведомления вы можете реализовать пользовательский адаптер: нижеприведенный адаптер прослушивает все propertyChanges из bean, слушая другие типы событий, будет совершенно аналогичным.
/**
* Adapt a Pojo to an Observable.
* Note: extending ObservableValue is too much, but there is no ObservableBase ...
*
* @author Jeanette Winzenburg, Berlin
*/
public class PojoAdapter<T> extends ObservableValueBase<T> {
private T bean;
private PropertyChangeListener pojoListener;
public PojoAdapter(T pojo) {
this.bean = pojo;
installPojoListener(pojo);
}
/**
* Reflectively install a propertyChangeListener for the pojo, if available.
* Silently does nothing if it cant.
* @param item
*/
private void installPojoListener(T item) {
try {
Method method = item.getClass().getMethod("addPropertyChangeListener",
PropertyChangeListener.class);
method.invoke(item, getPojoListener());
} catch (NoSuchMethodException | SecurityException | IllegalAccessException |
IllegalArgumentException | InvocationTargetException e) {
e.printStackTrace();
}
}
/**
* Returns the propertyChangeListener to install on each item.
* Implemented to call notifyList.
*
* @return
*/
private PropertyChangeListener getPojoListener() {
if (pojoListener == null) {
pojoListener = new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
fireValueChangedEvent();
}
};
}
return pojoListener;
}
@Override
public T getValue() {
return bean;
}
}
Это использование так же, как и выше (становится скучным, не так ли: -)
Callback<PersonBean, Observable[]> extractor = new Callback<PersonBean, Observable[]>() {
@Override
public Observable[] call(PersonBean arg0) {
return new Observable[] {new PojoAdapter<PersonBean>(arg0)};
}
};
ObservableList<Person> teamMembers = FXCollections.observableArrayList(extractor);
// fill list
К сожалению, автоматические обновления ListView с таким классным списком не будут надежно работать из-за ошибки которая исправлена только в jdk8. В более ранних версиях вы вернулись на квадрат 1 - как-то прослушивая это изменение, а затем вручную обновляете список:
protected void notifyList(Object changedItem) {
int index = list.indexOf(changedItem);
if (index >= 0) {
// hack around RT-28397
//https://javafx-jira.kenai.com/browse/RT-28397
list.set(index, null);
// good enough since jdk7u40 and jdk8
list.set(index, changedItem);
}
}
Ответ 2
Вы можете вручную запустить ListView.EditEvent
&mdash, что вызовет обновление ListView
— вызвав метод ListView::fireEvent
, унаследованный от javafx.scene.Node
. Например,
/**
* Informs the ListView that one of its items has been modified.
*
* @param listView The ListView to trigger.
* @param newValue The new value of the list item that changed.
* @param i The index of the list item that changed.
*/
public static <T> void triggerUpdate(ListView<T> listView, T newValue, int i) {
EventType<? extends ListView.EditEvent<T>> type = ListView.editCommitEvent();
Event event = new ListView.EditEvent<>(listView, type, newValue, i);
listView.fireEvent(event);
}
Или как один вкладыш,
listView.fireEvent(new ListView.EditEvent<>(listView, ListView.editCommitEvent(), newValue, i));
Вот пример приложения, демонстрирующего его использование.
/**
* An example of triggering a JavaFX ListView when an item is modified.
*
* Displays a list of strings. It iterates through the strings adding
* exclamation marks with 2 second pauses in between. Each modification is
* accompanied by firing an event to indicate to the ListView that the value
* has been modified.
*
* @author Mark Fashing
*/
public class ListViewTest extends Application {
/**
* Informs the ListView that one of its items has been modified.
*
* @param listView The ListView to trigger.
* @param newValue The new value of the list item that changed.
* @param i The index of the list item that changed.
*/
public static <T> void triggerUpdate(ListView<T> listView, T newValue, int i) {
EventType<? extends ListView.EditEvent<T>> type = ListView.editCommitEvent();
Event event = new ListView.EditEvent<>(listView, type, newValue, i);
listView.fireEvent(event);
}
@Override
public void start(Stage primaryStage) {
// Create a list of mutable data. StringBuffer works nicely.
final List<StringBuffer> listData = Stream.of("Fee", "Fi", "Fo", "Fum")
.map(StringBuffer::new)
.collect(Collectors.toList());
final ListView<StringBuffer> listView = new ListView<>();
listView.getItems().addAll(listData);
final StackPane root = new StackPane();
root.getChildren().add(listView);
primaryStage.setScene(new Scene(root));
primaryStage.show();
// Modify an item in the list every 2 seconds.
new Thread(() -> {
IntStream.range(0, listData.size()).forEach(i -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(listData.get(i));
Platform.runLater(() -> {
// Where the magic happens.
listData.get(i).append("!");
triggerUpdate(listView, listData.get(i), i);
});
});
}).start();
}
public static void main(String[] args) {
launch(args);
}
}
Ответ 3
Используя идею Фрэнсиса, я сделал:
list.set(list.indexOf(POJO), POJO);
Не может быть лучшим решением, но сработало.
Ответ 4
Так как Java 8u60 ListView официально поддерживает метод refresh()
, чтобы обновить представление вручную. JavaDoc:
Это полезно в случаях, когда базовый источник данных изменился таким образом, что сам ListView не наблюдается.
Я успешно использовал этот метод для этой проблемы здесь, чтобы обновить содержимое элементов в ListView.
Ответ 5
Вы должны взять наблюдаемый список и обновить объект, используя list.set(selectedIndex, object); Мой пример показывает кнопку с помощью метода handle. В этом я редактировал список пользователей в fx viewtable
Button commit = new Button("Commit");
commit.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent evt) {
int selectedIndex = tableView.getSelectionModel().getSelectedIndex();
User user = tableView.getSelectionModel().getSelectedItem();
user.setId(Integer.parseInt(idTF.getText()));
user.setName(nameCB.getValue());
user.setSurname(srnameTF.getText());
user.setAddress(addressTF.getText());
service.getUsers().set(selectedIndex, user);
tableView.toFront();
}
});
Ответ 6
ObservableList<String> items = FXCollections.observableArrayList();
ListView lv;
lv.setItems(items);
items.add();
items.remove;
Ответ 7
попробуйте это
list.remove(POJO);
list.add(index,POJO);