JavaFX: mise à jour de ListView si un élément de ObservableList change

j'aimerais afficher une liste de personnes (codée en POJOS, et contenant un nom et une propriété de nom de famille) en utilisant un contrôle ListView JavaFX. J'ai créé la ListView et ajouté la liste des personnes en tant qu'ObservableList. Tout fonctionne très bien si je supprime ou ajoute une nouvelle personne à L'ObservableList, mais les changements dans le POJO ne déclenchent pas une mise à jour de la ListView. Je dois supprimer et ajouter le POJO modifié de L'ObservableList pour déclencher la mise à jour de ListView. Est-il possible pour afficher les changements dans POJOS sans la solution décrite ci-dessus?

27
demandé sur assylias 2012-12-17 02:33:00

7 réponses

il y a plusieurs aspects à votre question (et je ne suis pas tout à fait qui est le problème :-) je vais supposer que votre POJO en quelque sorte notifier les auditeurs sur les changements, pourrait être en étant un JavaBean à part entière, qui est conforme à son contrat de notification via le tir propertyChange événements comme nécessaire ou d'autres moyens-sinon, vous auriez besoin d'une certaine poussée manuelle de la modification de toute façon.

l'approche de base pour faire un Fx-ObservableList notifier ses propres auditeurs sur les mutations de contained elements est de le configurer avec un Callback personnalisé qui fournit un tableau D'Observables. Si les éléments ont des propriétés fx, vous feriez quelque chose comme:

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

si le pojo est un javabéen de base à part entière, ses propriétés doivent être adaptées à FX-properties, F. I. en utilisant 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

notez une mise en garde: sans conserver une référence forte aux propriétés adaptées quelque part, elles seront rapidement ramassées-et semblent ensuite avoir aucun effet (tomber dans le piège encore et encore, Je ne sais pas comment s'il y a une bonne stratégie pour l'éviter).

pour tout autre moyen de notification (éventuellement à grain grossier), vous pouvez implémenter un adaptateur personnalisé: l'adaptateur ci-dessous écoute toutes les modifications de propriété d'un haricot, l'écoute d'autres types d'événements serait tout à fait analogue.

/**
 * 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;
    }

}

C'est l'utilisation de la même chose que ci-dessus (devient ennuyeux, n'est-ce pas :-)

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

malheureusement, mises à jour automatiques D'un ListView avec une liste aussi cool ne fonctionnera pas de manière fiable en raison d'un bug qui n'est corrigé qu'en jdk8. Dans les versions précédentes, vous êtes de retour à la case 1 - d'une manière ou d'une autre, en écoutant le changement puis en mettant à jour manuellement la liste:

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);
    }
}
16
répondu kleopatra 2015-02-25 12:25:00

Vous pouvez déclencher manuellement un ListView.EditEvent - ce qui causera le ListView pour mettre à jour-en appelant le ListView::fireEvent méthode héritée de javafx.scene.Node. Par exemple,

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

Ou comme une seule ligne,

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

voici un exemple d'application pour démontrer son utilisation.

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

}
5
répondu Mark 2014-04-17 18:51:11

à l'Aide de Francis idée que j'ai fait:

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

Peut-être pas la meilleure solution, mais il a travaillé.

3
répondu emorgado 2015-10-27 01:13:00

depuis Java 8u60 ListView soutient officiellement une méthode refresh() mettre à jour la vue manuellement. JavaDoc:

ceci est utile dans les cas où la source de données sous-jacente a changé d'une manière qui n'est pas respectée par la liste elle-même.

j'ai utilisé avec succès cette méthode pour ce numéro ici pour mettre à jour le contenu des éléments dans ListView.

2
répondu pathfinder78 2016-11-10 17:59:21

Vous devriez prendre la liste observable et mettre à jour l'objet en utilisant la liste.set (selectedIndex, object); mon exemple montrant le bouton avec la méthode de la poignée. Dans ce j'ai édité la liste des utilisateurs dans 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();
        }
    });
1
répondu milosz 2015-01-11 20:53:22
ObservableList<String> items = FXCollections.observableArrayList();
ListView lv;
lv.setItems(items);
items.add();
items.remove;
-1
répondu WeiChao 2012-12-23 05:01:51

essayez ceci

  list.remove(POJO);
  list.add(index,POJO);
-4
répondu Francis 2014-01-19 07:03:47