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

时间:2012-12-16 22:33:01

标签: java listview javafx-2

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

7 个答案:

答案 0 :(得分:17)

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

使FX-ObservableList通知其自己的侦听器有关包含元素的突变的基本方法是使用提供Observable数组的自定义Callback来配置它。如果元素具有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

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

对于任何其他(可能是粗粒度)通知的方法,您可以实现自定义适配器:下面的适配器侦听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;
    }

}

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

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

不幸的是,由于bug that's fixed only in 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);
    }
}

答案 1 :(得分:5)

您可以通过调用从ListView.EditEvent继承的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);
    }

}

答案 2 :(得分:3)

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

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

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

答案 3 :(得分:2)

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

  

这在基础数据源发生变化的情况下非常有用   以ListView本身未观察到的方式。

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

答案 4 :(得分:1)

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

答案 5 :(得分:-1)

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

答案 6 :(得分:-4)

试试这个

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