在 JavaFX ObservableArrayList 中查找更改的对象

Find changed object in JavaFX ObservableArrayList

我花了很长时间才在 Observablelist 中的一个 属性 对象上设置监听器并向其添加监听器。

 ObservableList<Track> observableResult = FXCollections.observableArrayList((Track tr)-> new Observable[]{tr.selectedProperty()});
                    observableResult.addListener(new ListChangeListener<Track>() {
                        @Override
                        public void onChanged(Change<? extends Track> c) {
                            c.next();
                            for(Track k : c.getAddedSubList()){
                                System.out.println(k.getTrackName());
                            }
                        }
                    });

但我似乎无法找到已进行更改的实际对象。 Change class 似乎只支持添加和删除的成员,这些成员不会被其中的实际更改触发。

我有一个解决方法,只需调用另一种方法,该方法将遍历整个 ObservableArrayList 并获取例如仅选定的项目,但在我有几千个对象后,这会变得非常昂贵。找到已更改的源成员将使我能够将它们推送到另一个数组并节省大量开销。

您可以在更改上调用 getFrom() 以获取更改项的索引。我认为没有办法真正弄清楚哪个 属性 发生了变化(如果提取器中列出了多个 属性)或获取旧值,但也许这就足够了。

如果您需要更多,您可以考虑在要跟踪的列表中注册您自己的侦听器,这会很棘手,但并非不可能。

这是演示 getFrom() 调用的 SSCCE:

import java.util.Random;
import java.util.stream.IntStream;

import javafx.beans.Observable;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;

public class ListChangeListenerTest {

    public static void main(String[] args) {
        ObservableList<Item> itemList = FXCollections.observableArrayList(item -> new Observable[]{item.valueProperty()});
        itemList.addListener((Change<? extends Item> c) -> {
            while (c.next()) {
                if (c.wasUpdated()) {
                    int index = c.getFrom();
                    System.out.println("Updated item at "+index+" new value is "+itemList.get(index).getValue());
                }
            }
        });
        IntStream.rangeClosed(1, 1000).mapToObj(Item::new).forEach(itemList::add);

        Random rng = new Random();
        itemList.get(rng.nextInt(itemList.size())).setValue(rng.nextInt(10000));

    }

    public static class Item {
        private final IntegerProperty value = new SimpleIntegerProperty();
        public Item(int value) {
            setValue(value);
        }
        public final IntegerProperty valueProperty() {
            return this.value;
        }

        public final int getValue() {
            return this.valueProperty().get();
        }

        public final void setValue(final int value) {
            this.valueProperty().set(value);
        }



    }

}

这是一个手动管理 属性 上的侦听器的版本。注意

  1. 这没有使用列表中的提取器
  2. Item bean 中的 属性 被构造为传递对拥有 属性 的 bean 的引用。这允许 属性 上的侦听器获得对 Item 的引用(通过一些丑陋的向下转换)

这提供了更多的灵活性;例如如果您想检查多个属性的修改并执行不同的操作,这将允许这样做。如您所见,侦听器也可以访问旧值。

import java.util.Random;
import java.util.stream.IntStream;

import javafx.beans.property.IntegerProperty;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.value.ChangeListener;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;

public class ListChangeListenerTest {

    public static void main(String[] args) {    

        ChangeListener<Number> valueListener = (obs, oldValue, newValue) -> {
            Item item = (Item) ((Property<?>) obs).getBean();
            System.out.println("Value for "+item+" changed from " + oldValue + " to "+newValue);
        };

        ObservableList<Item> itemList = FXCollections.observableArrayList();
        itemList.addListener((Change<? extends Item> change) -> {
            while (change.next()) {
                if (change.wasAdded()) {
                    for (Item item : change.getAddedSubList()) {
                        item.valueProperty().addListener(valueListener);
                    }
                }
                if (change.wasRemoved()) {
                    for (Item item : change.getRemoved()) {
                        item.valueProperty().removeListener(valueListener);
                    }
                }
            }
        });

        IntStream.rangeClosed(1, 1000).mapToObj(Item::new).forEach(itemList::add);

        Random rng = new Random();
        itemList.get(rng.nextInt(itemList.size())).setValue(rng.nextInt(10000));

    }

    public static class Item {
        private final IntegerProperty value = new SimpleIntegerProperty(this, "value");
        private final String id ;
        public Item(int value) {
            id = "Item "+value ;
            setValue(value);
        }
        public final IntegerProperty valueProperty() {
            return this.value;
        }

        public final int getValue() {
            return this.valueProperty().get();
        }

        public final void setValue(final int value) {
            this.valueProperty().set(value);
        }

        @Override
        public String toString() {
            return id ;
        }

    }

}

最后,如果您想考虑 "bulk" 更新,您需要自己实施 ObservableList。您可以通过 subclassing ModifiableObservableListBase 来做到这一点,基本思想非常简单。由于必须创建表示更新的 Change 对象,因此实现有点乏味,但还算不错。这是一个允许更新连续范围的示例:

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;

import javafx.collections.ListChangeListener.Change;
import javafx.collections.ModifiableObservableListBase;

public class UpdatingObservableList<T> extends ModifiableObservableListBase<T> {

    private final List<T> list ;

    public UpdatingObservableList(List<T> list) {
        this.list = list ;
    }

    public UpdatingObservableList() {
        this(new ArrayList<>());
    }

    public void updateSublist(int start, int end, Consumer<T> updater) {
        if (start < 0) throw new ArrayIndexOutOfBoundsException("Start ("+start+") cannot be < 0");
        if (end < start) throw new IllegalArgumentException("End ("+end+") cannot be less than start ("+start+")");
        if (end > size()) throw new ArrayIndexOutOfBoundsException("End ("+end+") cannot be greater than list size ("+size()+")");

        for (T element : list.subList(start, end)) {
            updater.accept(element);
        }

        fireChange(createUpdate(start, end));
    }

    @Override
    public T get(int index) {
        return list.get(index);
    }

    @Override
    public int size() {
        return list.size();
    }

    @Override
    protected void doAdd(int index, T element) {
        list.add(index, element);
    }

    @Override
    protected T doSet(int index, T element) {
        return list.set(index, element);
    }

    @Override
    protected T doRemove(int index) {
        return list.remove(index);
    }

    private Change<T> createUpdate(int start, int end) {
        return new Change<T>(this) {

            private boolean initialState = true ;

            @Override
            public boolean next() {
                if (initialState) {
                    initialState = false ;
                    return true ;
                }
                return false ;
            }

            @Override
            public void reset() {
                initialState = true ;
            }

            @Override
            public int getFrom() {
                checkState();
                return start ;
            }

            @Override
            public int getTo() {
                checkState();
                return end ;
            }

            @Override
            public List<T> getAddedSubList() {
                checkState();
                return Collections.emptyList();
            }

            @Override
            public List<T> getRemoved() {
                checkState();
                return Collections.emptyList();
            }

            @Override
            protected int[] getPermutation() {
                checkState();
                return new int[0];
            }

            @Override
            public boolean wasAdded() {
                checkState();
                return false ;
            }

            @Override
            public boolean wasRemoved() {
                checkState();
                return false ;
            }

            @Override
            public boolean wasUpdated() {
                return true ;
            }

            @Override
            public boolean wasPermutated() {
                checkState();
                return false ;
            }

            @Override
            public int getRemovedSize() {
                checkState();
                return 0 ;
            }

            @Override
            public int getAddedSize() {
                checkState();
                return 0 ;
            }

            private void checkState() {
                if (initialState) {
                    throw new IllegalStateException("Must call Change.next()");
                }
            }

        };
    }
}

这里是使用它的测试版本class。请注意,更新是通过列表执行的:

import java.util.Random;
import java.util.stream.IntStream;

import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.collections.ListChangeListener.Change;

public class ListChangeListenerTest {

    public static void main(String[] args) {    

        UpdatingObservableList<Item> itemList = new UpdatingObservableList<Item>();
        itemList.addListener((Change<? extends Item> change) -> {
            while (change.next()) {
                if (change.wasUpdated()) {
                    for (int i = change.getFrom() ; i < change.getTo() ; i++) {
                        System.out.println(itemList.get(i) + " updated - new value: "+itemList.get(i).getValue());
                    }
                }
            }
        });

        IntStream.rangeClosed(1, 1000).mapToObj(Item::new).forEach(itemList::add);

        Random rng = new Random();
        int start = rng.nextInt(itemList.size());
        int end = Math.min(itemList.size(), start + 1 + rng.nextInt(15));
        itemList.updateSublist(start, end, item -> item.setValue(rng.nextInt(10000)));
    }

    public static class Item {
        private final IntegerProperty value = new SimpleIntegerProperty(this, "value");
        private final String id ;
        public Item(int value) {
            id = "Item "+value ;
            setValue(value);
        }
        public final IntegerProperty valueProperty() {
            return this.value;
        }

        public final int getValue() {
            return this.valueProperty().get();
        }

        public final void setValue(final int value) {
            this.valueProperty().set(value);
        }

        @Override
        public String toString() {
            return id ;
        }

    }

}