【问题标题】:Java 8: Observable List - Invalidation Listener nor Change Listener is called in case of property changeJava 8:可观察列表 - 在属性更改的情况下调用无效侦听器或更改侦听器
【发布时间】:2014-12-31 00:59:41
【问题描述】:

我构建了一个自定义属性并将其添加到可观察列表中。但如果属性内容发生更改,则不会调用任何侦听器。以下代码 sn-ps 向您展示了“建筑”:

public static final class TestObject {
    private final ReadOnlyStringWrapper title = new ReadOnlyStringWrapper();
    private final BooleanProperty selected = new SimpleBooleanProperty(false);

    public TestObject(String title) {
        this.title.set(title);
    }

    public String getTitle() {
        return title.get();
    }

    public ReadOnlyStringProperty titleProperty() {
        return title.getReadOnlyProperty();
    }

    public boolean getSelected() {
        return selected.get();
    }

    public BooleanProperty selectedProperty() {
        return selected;
    }

    public void setSelected(boolean selected) {
        this.selected.set(selected);
    }

    @Override
    public int hashCode() {
        return Objects.hash(title.get());
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null || getClass() != obj.getClass()) {
            return false;
        }
        final TestObject other = (TestObject) obj;
        return Objects.equals(this.title.get(), other.title.get());
    }

    @Override
    public String toString() {
        return "TestObject{" +
                "title=" + title.get() +
                ", selected=" + selected.get() +
                '}';
    }
}

这是我的 POJO 类,其中包含我的内部属性值,例如 name 和 selected。

public static final class TestProperty extends SimpleObjectProperty<TestObject> {
    public TestProperty(String name) {
        super(new TestObject(name));
        init();
    }

    public TestProperty(TestObject testObject) {
        super(testObject);
        init();
    }

    public String getTitle() {
        return getValue().getTitle();
    }

    public void setSelected(boolean selected) {
        getValue().setSelected(selected);
    }

    public boolean getSelected() {
        return getValue().getSelected();
    }

    public BooleanProperty selectedProperty() {
        return getValue().selectedProperty();
    }

    public ReadOnlyStringProperty titleProperty() {
        return getValue().titleProperty();
    }

    @Override
    public void set(TestObject testObject) {
        super.set(testObject);
        init();
    }

    @Override
    public void setValue(TestObject testObject) {
        super.setValue(testObject);
        init();
    }

    private void init() {
        if (get() == null)
            return;

        get().titleProperty().addListener((v, o, n) -> fireValueChangedEvent());
        get().selectedProperty().addListener((v, o, n) -> {
            fireValueChangedEvent();
        });
    }
}

这是我基于 POJO 的自定义属性。所有属性更改都会触发我的自定义属性的更改事件。

@Test
public void testSimple() {
    final AtomicInteger counter = new AtomicInteger(0);
    final TestProperty testProperty = new TestProperty("Test");
    testProperty.addListener(observable -> {
        System.out.println("New state: " + testProperty.get().toString());
        counter.incrementAndGet();
    });

    testProperty.setSelected(true);
    testProperty.setSelected(false);

    Assert.assertEquals(2, counter.intValue());
}

在这个测试中,您可以看到属性更改事件工作正常。

@Test
public void testList() {
    final AtomicInteger counter = new AtomicInteger(0);
    final ObservableList<TestProperty> observableList = new ObservableListWrapper<>(new ArrayList<>());
    observableList.add(new TestProperty("Test 1"));
    observableList.add(new TestProperty("Test 2"));
    observableList.add(new TestProperty("Test 3"));

    observableList.addListener(new ListChangeListener<TestProperty>() {
        @Override
        public void onChanged(Change<? extends TestProperty> change) {
            System.out.println("**************");
        }
    });
    observableList.addListener((Observable observable) -> {
        System.out.println("New state: " + ((TestProperty) observable).get().toString());
        counter.incrementAndGet();
    });

    observableList.get(1).setSelected(true);
    observableList.get(2).setSelected(true);
    observableList.get(1).setSelected(false);
    observableList.get(2).setSelected(false);

    Assert.assertEquals(4, counter.intValue());
}

但在这段代码中,您会看到如果列表中的属性值发生更改,可观察列表不会调用失效侦听器或更改侦听器。

怎么了?

谢谢。

【问题讨论】:

  • 顺便说一句(尽管它与您的问题密切相关),ObservableListWrapper 不是公共 API 的一部分,因此不应真正使用。不能保证它会在 JavaFX 的未来版本中存在。要创建ObservableLists(和其他可观察的集合),请使用FXCollections 中的工厂方法。

标签: java properties javafx observable changelistener


【解决方案1】:

要创建一个可观察列表,如果列表元素的属性发生更改,将发送“列表更新”通知,您需要使用extractor 创建列表。 extractor 是一个Callback,它将列表的每个元素映射到Observables 的数组。如果Observables 中的任何一个发生变化,将通知注册到列表中的InvalidationListeners 和ListChangeListeners。

所以在你的testList() 方法中,你可以这样做

final ObservableList<TestProperty> observableList = FXCollections.observableList(
    new ArrayList<>(),
    (TestProperty tp) -> new Observable[]{tp.selectedProperty()});

如果标题能够更改,并且您还希望列表在发生这种情况时收到通知,您也可以这样做:

final ObservableList<TestProperty> observableList = FXCollections.observableList(
    new ArrayList<>(),
    (TestProperty tp) -> new Observable[]{tp.selectedProperty(), tp.titleProperty()});

请注意,由于提取器是Callback(本质上是一个函数),因此实现可以任意复杂(根据另一个属性的值有条件地观察一个属性,等等)。

【讨论】:

  • 你能举一个例子,让你在 ObservableList 中监听 Person 类的变化(监听姓名、姓氏等吗?)这个例子没有为 90% 的情况提供一个简单的例子: )
  • 看起来很有希望。如果可以,请提供一些说明:stackoverflow.com/q/43745418/546476
  • 是否有原因这不适用于链表而不是数组列表?
  • @Ruben9922 用链表试过这个,效果很好。
  • @James_D 实际上问题似乎出在我使用它的ChoiceBox 上,而不是ObservableList 本身;列表本身更新正常。
【解决方案2】:

以下代码显示了一个带有可观察值的可观察列表的简单实现:

public class ObservableValueListWrapper<E extends ObservableValue<E>> extends ObservableListWrapper<E> {
 public ObservableValueListWrapper(List<E> list) {
  super(list, o -> new Observable[] {o});}}

或者您必须使用 POJO 创建您的列表:

final ObservableList<MyPOJO> list = new ObservableListWrapper<>(new ArrayList(), o -> new Observable[] { new MyPOJOProperty(o) });

或者你这样使用它:

final ObservableList<MyPOJO> list = new ObservableListWrapper<>(new ArrayList(), o -> { return new Observable[] {
o.value1Property(),
o.value2Property(),
...};});

就是这样!谢谢。

【讨论】:

  • 您正在复制 API 中的现有功能,并且还使用了不属于公共 API 的类。
【解决方案3】:

每当列表中包含的属性被修改时,ObservableList 不会通知侦听器,它会在通知列表时通知。

这可以在您修改测试时看到:

@Test
public void testList() {
    final AtomicInteger counter = new AtomicInteger(0);
    final ObservableList<TestProperty> observableList = new ObservableListWrapper<>(new ArrayList<>());

    observableList.addListener(new ListChangeListener<TestProperty>() {
        @Override
        public void onChanged(Change<? extends TestProperty> change) {
            System.out.println("**************");
            counter.incrementAndGet();
        }
    });

    observableList.add(new TestProperty("Test 1"));
    observableList.add(new TestProperty("Test 2"));
    observableList.add(new TestProperty("Test 3"));

    observableList.get(1).setSelected(true);
    observableList.get(2).setSelected(true);
    observableList.get(1).setSelected(false);
    observableList.get(2).setSelected(false);

    Assert.assertEquals(3, counter.intValue());
}

编辑:添加了一个示例 ObserverListener 装饰器,它根据 OP 的需要提供 ObservableValue 更改侦听器的自动注册/注销。

/**
 * Decorates an {@link ObservableList} and auto-registers the provided
 * listener to all new observers, and auto-unregisters listeners when the
 * item is removed from the list.
 *
 * @param <T>
 */
public class ObservableValueList<T extends ObservableValue> implements ObservableList<T> {

    private final ObservableList<T> list;
    private final ChangeListener<T> valueListener;

    public ObservableValueList(ObservableList<T> list, ChangeListener<T> valueListener) {
        this.list = list;
        //list to existing contents of list
        this.list.stream().forEach((item) -> item.addListener(valueListener));

        //register listener which will add/remove listner on change to list
        this.list.addListener((Change<? extends T> change) -> {
            change.getAddedSubList().stream().forEach(
                    (item) -> item.addListener(valueListener));

            change.getRemoved().stream().forEach(
                    (item) -> item.removeListener(valueListener));
        });
        this.valueListener = valueListener;
    }

    /*  What follows is all the required delegate methods */

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

    @Override
    public boolean isEmpty() {
        return list.isEmpty();
    }

    @Override
    public boolean contains(Object o) {
        return list.contains(o);
    }

    @Override
    public Iterator<T> iterator() {
        return list.iterator();
    }

    @Override
    public Object[] toArray() {
        return list.toArray();
    }

    @Override
    public <T> T[] toArray(T[] ts) {
        return list.toArray(ts);
    }

    @Override
    public boolean add(T e) {
        return list.add(e);
    }

    @Override
    public boolean remove(Object o) {
        return list.remove(o);
    }

    @Override
    public boolean containsAll(Collection<?> clctn) {
        return list.containsAll(clctn);
    }

    @Override
    public boolean addAll(Collection<? extends T> clctn) {
        return list.addAll(clctn);
    }

    @Override
    public boolean addAll(int i, Collection<? extends T> clctn) {
        return list.addAll(i, clctn);
    }

    @Override
    public boolean removeAll(Collection<?> clctn) {
        return list.removeAll(clctn);
    }

    @Override
    public boolean retainAll(Collection<?> clctn) {
        return list.retainAll(clctn);
    }

    @Override
    public void replaceAll(UnaryOperator<T> uo) {
        list.replaceAll(uo);
    }

    @Override
    public void sort(Comparator<? super T> cmprtr) {
        list.sort(cmprtr);
    }

    @Override
    public void clear() {
        list.clear();
    }

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

    @Override
    public T set(int i, T e) {
        return list.set(i, e);
    }

    @Override
    public void add(int i, T e) {
        list.add(i, e);
    }

    @Override
    public T remove(int i) {
        return list.remove(i);
    }

    @Override
    public int indexOf(Object o) {
        return list.indexOf(o);
    }

    @Override
    public int lastIndexOf(Object o) {
        return list.lastIndexOf(o);
    }

    @Override
    public ListIterator<T> listIterator() {
        return list.listIterator();
    }

    @Override
    public ListIterator<T> listIterator(int i) {
        return list.listIterator(i);
    }

    @Override
    public List<T> subList(int i, int i1) {
        return list.subList(i, i1);
    }

    @Override
    public Spliterator<T> spliterator() {
        return list.spliterator();
    }

    @Override
    public void addListener(ListChangeListener<? super T> ll) {
        list.addListener(ll);
    }

    @Override
    public void removeListener(ListChangeListener<? super T> ll) {
        list.removeListener(ll);
    }

    @Override
    public boolean addAll(T... es) {
        return list.addAll(es);
    }

    @Override
    public boolean setAll(T... es) {
        return list.setAll(es);
    }

    @Override
    public boolean setAll(Collection<? extends T> clctn) {
        return list.setAll(clctn);
    }

    @Override
    public boolean removeAll(T... es) {
        return list.removeAll(es);
    }

    @Override
    public boolean retainAll(T... es) {
        return list.retainAll(es);
    }

    @Override
    public void remove(int i, int i1) {
        list.remove(i, i1);
    }

    @Override
    public FilteredList<T> filtered(Predicate<T> prdct) {
        return list.filtered(prdct);
    }

    @Override
    public SortedList<T> sorted(Comparator<T> cmprtr) {
        return list.sorted(cmprtr);
    }

    @Override
    public SortedList<T> sorted() {
        return list.sorted();
    }

    @Override
    public void addListener(InvalidationListener il) {
        list.addListener(il);
    }

    @Override
    public void removeListener(InvalidationListener il) {
        list.removeListener(il);
    }

}

【讨论】:

  • 但是监听方法参数类中的'change.wasUpdated'是什么意思?
  • 是的,这里的 javadoc 有点混乱!这表明在 observablelist 中链接的项目(即索引值为 0、1、2 等)是否已更新(即新对象已放置在该索引处),而不是对象本身已更改。这样做的目的是让您在 ListChangeListener 中决定是否要在列表中的(可能)ObservableProperty 上注册 ChangeListener。需要注意的是,ObservableList需要 ObservableProperty 作为成员,它可以保存 Object 类型。
  • 但是有一些实现可以做到我想做的事情:如果列表中的属性值发生变化,则获取回调?
  • 嗯...我应该预料到这些问题 =) 我会说不。我没有遇到它,我只是浏览了 javadoc - 那里没有什么值得注意的。但是,我认为装饰一个 ObservableList 以提供该功能是相当微不足道的。我将在上面添加一个简单的示例。
  • 太疯狂了 :) 该列表有一个失效监听器,就像一个可观察值的失效监听器一样。如果您有一个属性,则在该属性更改其值之一时调用失效侦听器。我认为可观察列表必须具有与属性相同的行为:如果列表中的属性已更改,则必须调用失效侦听器。我认为该列表必须检查添加的对象是否为可观察值。
猜你喜欢
  • 2011-12-23
  • 2014-11-10
  • 2013-12-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-05-23
相关资源
最近更新 更多