JavaFX: Actualización de ListView si un elemento de ObservableList cambia

Me gustaría mostrar una lista de personas (codificadas en POJOS y que contienen un nombre y una propiedad de apellido) utilizando un control ListView de JavaFX. Creé ListView y agregué la lista de personas como ObservableList. Todo funciona bien si elimino o agrego una nueva persona a ObservableList, pero los cambios en el POJO no activan una actualización de ListView. Debo eliminar y agregar el POJO modificado de la lista Observable para activar la actualización de ListView. ¿Hay alguna posibilidad de mostrar cambios en POJOS sin la solución descrita anteriormente?

Hay varios aspectos en su pregunta (y no estoy del todo cual es el problema 🙂 Asumiré que su POJO de alguna manera notificará a los oyentes sobre los cambios, podría ser al ser un JavaBean completo, que cumpla con su notificación contrato mediante activación de propiedad Cambie los eventos según sea necesario o de alguna otra forma; de lo contrario, necesitaría algún impulso manual del cambio de todos modos.

El enfoque básico para hacer que una FX-ObservableList notifique a sus propios oyentes sobre mutaciones de elementos contenidos es configurarlo con una callback personalizada que proporcione una matriz de Observables. Si los elementos tienen propiedades fx, harías algo como:

 Callback extractor = new Callback() { @Override public Observable[] call(Person p) { return new Observable[] {p.lastNameProperty(), p.firstNameProperty()}; } }; ObservableList teamMembers = FXCollections.observableArrayList(extractor); // fill list 

Si el pojo es un javaBean de núcleo completo, sus propiedades deben adaptarse a las propiedades fx, fi usando JavaBeanProperty:

 Callback extractor = new Callback() { List properties = new ArrayList(); @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 teamMembers = FXCollections.observableArrayList(extractor); // fill list 

Tenga en cuenta una advertencia: sin guardar una referencia fuerte en alguna parte de las propiedades adaptadas, se recogerán rápidamente y luego parecerán no tener ningún efecto (cayendo en la trampa una y otra vez, sin saber cómo si hay una buena estrategia para evitarlo).

Para cualquier otro medio de notificación (posiblemente de grano grueso), puede implementar un adaptador personalizado: el adaptador siguiente escucha todos los cambios de propiedad de un bean, escuchar otros tipos de eventos sería bastante análogo.

 /** * Adapt a Pojo to an Observable. * Note: extending ObservableValue is too much, but there is no ObservableBase ... * * @author Jeanette Winzenburg, Berlin */ public class PojoAdapter extends ObservableValueBase { 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; } } 

Su uso es el mismo que el anterior (se vuelve aburrido, ¿no? 🙂

 Callback extractor = new Callback() { @Override public Observable[] call(PersonBean arg0) { return new Observable[] {new PojoAdapter(arg0)}; } }; ObservableList teamMembers = FXCollections.observableArrayList(extractor); // fill list 

Desafortunadamente, las actualizaciones automáticas de un ListView con una lista tan interesante no funcionarán de manera confiable debido a un error que solo se solucionó en jdk8 . En versiones anteriores, está de vuelta en el cuadro 1, escucha de alguna manera el cambio y luego actualiza manualmente la lista:

 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); } } 

Puede desencadenar manualmente un ListView.EditEvent que hará que ListView actualice, llamando al método ListView::fireEvent heredado de javafx.scene.Node . Por ejemplo,

 /** * 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  void triggerUpdate(ListView listView, T newValue, int i) { EventType> type = ListView.editCommitEvent(); Event event = new ListView.EditEvent<>(listView, type, newValue, i); listView.fireEvent(event); } 

O como un trazador de líneas,

 listView.fireEvent(new ListView.EditEvent<>(listView, ListView.editCommitEvent(), newValue, i)); 

Aquí hay una aplicación de muestra para demostrar su uso.

 /** * 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  void triggerUpdate(ListView listView, T newValue, int i) { EventType> 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 listData = Stream.of("Fee", "Fi", "Fo", "Fum") .map(StringBuffer::new) .collect(Collectors.toList()); final ListView 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); } } 

Utilizando la idea de Francis lo hice:

  list.set(list.indexOf(POJO), POJO); 

Puede que no sea la mejor solución pero funcionó.

Dado que Java 8u60 ListView admite oficialmente un método refresh() para actualizar la vista manualmente. JavaDoc:

Esto es útil en casos donde la fuente de datos subyacente ha cambiado de una manera que no es observada por el ListView.

Utilicé con éxito este método para este problema aquí para actualizar el contenido de los elementos en ListView.

Debe tomar la lista observable y actualizar el objeto usando list.set (selectedIndex, object); Mi ejemplo que muestra el botón con el método de manejo. En este edité los usuarios de la lista en fx viewtable

 Button commit = new Button("Commit"); commit.setOnAction(new EventHandler() { 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(); } }); 
 ObservableList items = FXCollections.observableArrayList(); ListView lv; lv.setItems(items); items.add(); items.remove; 

prueba esto

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