【问题标题】:JavaFX: Update of ListView if an element of ObservableList changesJavaFX:如果 ObservableList 的元素发生更改,则更新 ListView
【发布时间】:2012-12-04 01:48:37
【问题描述】:

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

【问题讨论】:

  • 你能发布你的 POJO 课程吗?
  • Oracle 论坛here 中概述了一种方法,该方法概述了此问题的解决方案。
  • kleopatra 给出的方法是最新最简单的解决方案。我只想补充一点,extractor 可以返回它想要的任何 observables。并且它可以用 lambdas 非常简洁地编写。
  • @HRJ 原始问题的主旨是当支持ObservableList 中的元素内容发生变化时如何更新ListView。扩展 kleopatra 的答案,还需要将 ListChangeListener 添加到将捕获和处理 wasUpdated() 更改的列表中。还是我错过了什么?

标签: java listview javafx-2


【解决方案1】:
ObservableList<String> items = FXCollections.observableArrayList();
ListView lv;
lv.setItems(items);
items.add();
items.remove;

【讨论】:

    【解决方案2】:

    试试这个

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

    【讨论】:

      【解决方案3】:

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

      使 FX-ObservableList 通知其自己的侦听器有关包含元素的突变的基本方法是使用自定义 Callback 对其进行配置,该回调提供一组 Observables。如果元素具有 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 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
      

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

      【讨论】:

      • 非常感谢您的警告,您从哪里读到的?你能在这里分享吗:)
      【解决方案4】:

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

      【讨论】:

        【解决方案5】:

        您应该获取可观察列表并使用 list.set(selectedIndex, object); 更新对象我的示例显示带有句柄方法的按钮。在此我在 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();
                }
            });
        

        【讨论】:

          【解决方案6】:

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

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

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

          【讨论】:

            【解决方案7】:

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

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

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

            【讨论】:

              猜你喜欢
              • 2016-03-02
              • 1970-01-01
              • 2020-11-03
              • 2016-01-02
              • 2017-03-06
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多