繁体   English   中英

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

[英]JavaFX: Update of ListView if an element of ObservableList changes

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

您的问题有几个方面(我不完全确定哪个方面是问题:-)我假设您的 POJO 以某种方式通知听众有关更改的信息,可能是作为一个成熟的 JavaBean。 也就是说,它通过根据需要或通过其他方式触发 propertyChange 事件来遵守其通知合同 - 否则,无论如何您都需要手动推送更改。

使 FX-ObservableList 在包含元素的变化时通知它自己的侦听器的基本方法是使用提供 Observable 数组的自定义回调对其进行配置。 如果元素具有 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,它的属性必须通过使用 JavaBeanProperty 适应 fx-properties,fi:

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 losing 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

不幸的是,由于仅在 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);
    }
}

您可以通过调用从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);
}

或者作为单衬,

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

}

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

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

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

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

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

我在此问题上成功使用此方法更新 ListView 中项目的内容。

您应该获取可观察列表并使用 list.set(selectedIndex, object); 更新对象。 我的示例显示带有 handle 方法的按钮。 在此我编辑了 fx 视图中的列表用户

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();
        }
    });
ObservableList<String> items = FXCollections.observableArrayList();
ListView lv;
lv.setItems(items);
items.add();
items.remove;

试试这个

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM