JavaFX:如果ObservableList的元素发生更改,则更新ListView

时间:2022-08-18 17:04:39

I would like to display a list of persons (coded in POJOS, and containing a name and surname property) using a JavaFX ListView control. I created the ListView and added the list of persons as an ObservableList. Everything works fine if I delete or add a new person to the ObservableList, but changes in the POJO do not trigger an update of the ListView. I have to remove and add the modified POJO from the ObservableList to trigger the update of the ListView. Is there any possibility to display changes in POJOS without the workaround described above?

我想使用JavaFX ListView控件显示一个人员列表(用POJOS编码,并包含姓名和姓氏属性)。我创建了ListView并添加了人员列表作为ObservableList。如果我删除或向ObservableList添加新人,一切正常,但POJO中的更改不会触发ListView的更新。我必须从ObservableList中删除并添加修改后的POJO以触发ListView的更新。如果没有上述解决方法,是否有可能在POJOS中显示更改?

7 个解决方案

#1


16  

There are several aspects to your question (and I'm not entirely which is the problem :-) I'll assume that your POJO somehow notifying listeners about changes, could be by being a full-fledged JavaBean, that is complies to its notification contract via firing propertyChange events as needed or some other means - otherwise, you would need some manual push of the change anyway.

你的问题有几个方面(我不完全是问题:-)我会假设你的POJO以某种方式通知听众有关变化,可能是一个成熟的JavaBean,这符合它的通知通过触发属性合同根据需要更改事件或其他一些方法 - 否则,您无论如何都需要手动推送更改。

The basic approach to make an FX-ObservableList notify its own listeners on mutations of contained elements is to configure it with a custom Callback that provides an array of Observables. If the elements have fx-properties you would do something like:

使FX-ObservableList通知其自己的侦听器有关包含元素的突变的基本方法是使用提供Observable数组的自定义Callback来配置它。如果元素具有fx属性,您可以执行以下操作:

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

If the pojo is-a full-fledged core javaBean, its properties have to be adapted to fx-properties, f.i. by using JavaBeanProperty:

如果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

Note a caveat: without keeping a strong reference to the adapted properties somewhere, they will be quickly garbage-collected - and then appear to have no effect at all (falling into the trap again and again, not sure how if there's a good strategy to avoid it).

请注意一个警告:如果没有对某个地方适应的属性进行强有力的引用,它们将很快被垃圾收集 - 然后看起来根本没有效果(一次又一次陷入陷阱,不知道如果有一个好的策略如何躲开它)。

For any other means of (possibly coarse-grained) notification, you can implement a custom adapter: the adapter below listens to all propertyChanges of a bean, listening to other types of events would be quite analogous.

对于任何其他(可能是粗粒度)通知的方法,您可以实现自定义适配器:下面的适配器侦听bean的所有propertyChanges,监听其他类型的事件将是非常类似的。

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

}

It's usage just the same as above (getting boring, isn't it :-)

它的用法与上面相同(变得无聊,不是吗:-)

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

Unfortunately, automatic updates of a ListView with such cool list won't work reliably due to a bug that's fixed only in jdk8. In earlier versions, you are back at square 1 - somehow listening to the change and then manually update the list:

不幸的是,由于仅在jdk8中修复的错误,具有如此酷列表的ListView的自动更新将无法可靠地工作。在早期版本中,您回到方块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


5  

You can manually trigger a ListView.EditEvent—which will cause the ListView to update—by calling the ListView::fireEvent method inherited from javafx.scene.Node. For example,

您可以通过调用从javafx.scene.Node继承的ListView :: fireEvent方法手动触发ListView.EditEvent,这将导致ListView更新。例如,

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

Or as a one liner,

或者作为一个班轮,

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

Here is a sample application to demonstrate its use.

这是一个演示其用途的示例应用程序。

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


3  

Using Francis idea I did:

使用弗朗西斯的想法我做了:

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

May not be the best solution but worked.

可能不是最好的解决方案,但有效。

#4


2  

Since Java 8u60 ListView officially supports a method refresh() to update the view manually. JavaDoc:

由于Java 8u60 ListView正式支持方法refresh()来手动更新视图。 JavaDoc的:

This is useful in cases where the underlying data source has changed in a way that is not observed by the ListView itself.

这在底层数据源以ListView本身未观察到的方式发生更改的情况下非常有用。

I successfully used this method for this issue here to update the content of items in the ListView.

我在这里成功地使用此方法来更新ListView中的项目内容。

#5


1  

You should take the observable list and update the object using list.set(selectedIndex, object); My example showing button with the handle method. In this I edited list users in fx viewtable

您应该使用可观察列表并使用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


-1  

ObservableList<String> items = FXCollections.observableArrayList();
ListView lv;
lv.setItems(items);
items.add();
items.remove;

#7


-4  

try this

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

#1


16  

There are several aspects to your question (and I'm not entirely which is the problem :-) I'll assume that your POJO somehow notifying listeners about changes, could be by being a full-fledged JavaBean, that is complies to its notification contract via firing propertyChange events as needed or some other means - otherwise, you would need some manual push of the change anyway.

你的问题有几个方面(我不完全是问题:-)我会假设你的POJO以某种方式通知听众有关变化,可能是一个成熟的JavaBean,这符合它的通知通过触发属性合同根据需要更改事件或其他一些方法 - 否则,您无论如何都需要手动推送更改。

The basic approach to make an FX-ObservableList notify its own listeners on mutations of contained elements is to configure it with a custom Callback that provides an array of Observables. If the elements have fx-properties you would do something like:

使FX-ObservableList通知其自己的侦听器有关包含元素的突变的基本方法是使用提供Observable数组的自定义Callback来配置它。如果元素具有fx属性,您可以执行以下操作:

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

If the pojo is-a full-fledged core javaBean, its properties have to be adapted to fx-properties, f.i. by using JavaBeanProperty:

如果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

Note a caveat: without keeping a strong reference to the adapted properties somewhere, they will be quickly garbage-collected - and then appear to have no effect at all (falling into the trap again and again, not sure how if there's a good strategy to avoid it).

请注意一个警告:如果没有对某个地方适应的属性进行强有力的引用,它们将很快被垃圾收集 - 然后看起来根本没有效果(一次又一次陷入陷阱,不知道如果有一个好的策略如何躲开它)。

For any other means of (possibly coarse-grained) notification, you can implement a custom adapter: the adapter below listens to all propertyChanges of a bean, listening to other types of events would be quite analogous.

对于任何其他(可能是粗粒度)通知的方法,您可以实现自定义适配器:下面的适配器侦听bean的所有propertyChanges,监听其他类型的事件将是非常类似的。

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

}

It's usage just the same as above (getting boring, isn't it :-)

它的用法与上面相同(变得无聊,不是吗:-)

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

Unfortunately, automatic updates of a ListView with such cool list won't work reliably due to a bug that's fixed only in jdk8. In earlier versions, you are back at square 1 - somehow listening to the change and then manually update the list:

不幸的是,由于仅在jdk8中修复的错误,具有如此酷列表的ListView的自动更新将无法可靠地工作。在早期版本中,您回到方块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


5  

You can manually trigger a ListView.EditEvent—which will cause the ListView to update—by calling the ListView::fireEvent method inherited from javafx.scene.Node. For example,

您可以通过调用从javafx.scene.Node继承的ListView :: fireEvent方法手动触发ListView.EditEvent,这将导致ListView更新。例如,

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

Or as a one liner,

或者作为一个班轮,

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

Here is a sample application to demonstrate its use.

这是一个演示其用途的示例应用程序。

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


3  

Using Francis idea I did:

使用弗朗西斯的想法我做了:

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

May not be the best solution but worked.

可能不是最好的解决方案,但有效。

#4


2  

Since Java 8u60 ListView officially supports a method refresh() to update the view manually. JavaDoc:

由于Java 8u60 ListView正式支持方法refresh()来手动更新视图。 JavaDoc的:

This is useful in cases where the underlying data source has changed in a way that is not observed by the ListView itself.

这在底层数据源以ListView本身未观察到的方式发生更改的情况下非常有用。

I successfully used this method for this issue here to update the content of items in the ListView.

我在这里成功地使用此方法来更新ListView中的项目内容。

#5


1  

You should take the observable list and update the object using list.set(selectedIndex, object); My example showing button with the handle method. In this I edited list users in fx viewtable

您应该使用可观察列表并使用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


-1  

ObservableList<String> items = FXCollections.observableArrayList();
ListView lv;
lv.setItems(items);
items.add();
items.remove;

#7


-4  

try this

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