簡體   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