将ObservableList绑定到另外两个ObservableList的内容?

7
如果我有两个独立的ObservableLists,并且两者都放置在一个单独的ObservableList中用于TableView...是否有一种方法可以在这两个ObservableLists和聚合列表之间创建绑定?我尝试过玩弄ObjectBinding的calculate()方法,但我认为这不是我想要的。有什么想法如何解决这个问题吗?
更新:沿着相关的思路展开讨论下面的含义。这是我的ObservableImmutableList实现,正在努力与检查的解决方案一起工作。
   package com.nield.utilities.fx;

import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.concurrent.CopyOnWriteArrayList;

import javafx.beans.InvalidationListener;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.ListChangeListener;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;

import com.google.common.collect.ImmutableList;


public final class ObservableImmutableList<T> implements ObservableList<T> {
    private volatile ImmutableList<T> backingList;
    private final CopyOnWriteArrayList<ListChangeListener<? super T>> listeners = new CopyOnWriteArrayList<>();
    private final CopyOnWriteArrayList<InvalidationListener> invalidationListeners = new CopyOnWriteArrayList<>();

    private final ObjectProperty<ObservableList<T>> property;

    private ObservableImmutableList(ImmutableList<T> immutableList) {
        this.backingList = immutableList;
        this.property = new SimpleObjectProperty<ObservableList<T>>(this);
    }

    public static <T> ObservableImmutableList<T> of(ImmutableList<T> immutableList) {
        return new ObservableImmutableList<T>(immutableList);
    }

    public void set(ImmutableList<T> immutableList) { 

        this.property.setValue(this);

        final ImmutableList<T> oldList = this.backingList;
        final ImmutableList<T> newList = immutableList;
        listeners.forEach(l -> l.onChanged(new Change<T>(this) {
            private int changeNum = 0;
            @Override
            public boolean next() {
                changeNum++;
                return changeNum <= 2 ? true : false;
            }
            @Override
            public boolean wasUpdated() {
                return true;
            }
            @Override
            public void reset() {
                // TODO Auto-generated method stub

            }
            @Override
            public int getFrom() {
                return 0;
            }
            @Override
            public int getTo() {
                return changeNum == 1 ? oldList.size() - 1 : newList.size() - 1;
            }
            @Override
            public List<T> getRemoved() {
                return changeNum == 1 ? oldList : ImmutableList.of();
            }
            @Override
            public List<T> getAddedSubList() { 
                return changeNum == 1 ? ImmutableList.of() : newList;
            }
            @Override
            protected int[] getPermutation() {
                int[] permutations = new int[changeNum == 1 ? oldList.size() : newList.size()];
                for (int i = 0; i < permutations.length; i++) { 
                    permutations[i] = i;
                }
                return permutations;
            }

        }));
        this.backingList = immutableList;

        invalidationListeners.forEach(l -> l.invalidated(this));

    }

    public ImmutableList<T> get() { 
        return backingList;
    }
    public ObjectProperty<ObservableList<T>> asProperty() { 
        return property;
    }

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

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

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

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

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

    @Override
    public <B> B[] toArray(B[] a) {
        return backingList.toArray(a);
    }

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

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

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

    @Override @Deprecated
    public boolean addAll(Collection<? extends T> c) {
        return backingList.addAll(c);
    }

    @Override @Deprecated
    public boolean addAll(int index, Collection<? extends T> c) {
        return backingList.addAll(index, c);
    }

    @Override @Deprecated
    public boolean removeAll(Collection<?> c) {
        return backingList.removeAll(c);
    }

    @Override @Deprecated
    public boolean retainAll(Collection<?> c) {
        return backingList.retainAll(c);
    }

    @Override @Deprecated
    public void clear() {
        backingList.clear();
    }

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

    @Override @Deprecated
    public T set(int index, T element) {
        return backingList.set(index, element);
    }

    @Override @Deprecated
    public void add(int index, T element) {
        backingList.add(index, element);
    }

    @Override @Deprecated
    public T remove(int index) {
        return backingList.remove(index);
    }

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

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

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

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

    @Override
    public ImmutableList<T> subList(int fromIndex, int toIndex) {
        return backingList.subList(fromIndex, toIndex);
    }

    @Override
    public void addListener(InvalidationListener listener) {
        invalidationListeners.add(listener);
    }

    @Override
    public void removeListener(InvalidationListener listener) {
        invalidationListeners.remove(listener);
    }

    @Override
    public void addListener(ListChangeListener<? super T> listener) {
        listeners.add(listener);
    }

    @Override
    public void removeListener(ListChangeListener<? super T> listener) {
        listeners.remove(listener);
    }

    @Override @Deprecated
    public boolean addAll(T... elements) {
        return backingList.addAll(ImmutableList.copyOf(elements));
    }

    @Override @Deprecated
    public boolean setAll(T... elements) {
        return false;
    }

    @Override @Deprecated
    public boolean setAll(Collection<? extends T> col) {
        return false;
    }

    @Override @Deprecated
    public boolean removeAll(T... elements) {
        return backingList.removeAll(ImmutableList.copyOf(elements));
    }

    @Override @Deprecated
    public boolean retainAll(T... elements) {
        return false;
    }

    @Override @Deprecated
    public void remove(int from, int to) {
    }

    @Override
    public String toString() { 
        return backingList.toString();
    }

}

这里是合并两个ObservableList的静态工厂方法。它适用于标准实现的ObservableList,但不适用于我的ObservableImmutableList实现。

package com.nield.finance;

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;

import com.google.common.collect.ImmutableList;
import com.nield.utilities.fx.ObservableImmutableList;

public class ObservableMerge {
    static ObservableImmutableList<String> a = ObservableImmutableList.of(ImmutableList.of("ABQ","DAL"));
    static ObservableImmutableList<String> b = ObservableImmutableList.of(ImmutableList.of("HOU","PHX"));

    static ObservableList<String> aandb = FXCollections.observableArrayList();


    static ObservableList<String> merge(ObservableList<String> into, ObservableList<String>... lists) {
        final ObservableList<String> list = into;
        for (ObservableList<String> l : lists) {
            list.addAll(l);
            l.addListener((javafx.collections.ListChangeListener.Change<? extends String> c) -> {
                while (c.next()) {
                    if (c.wasAdded()) {
                        list.addAll(c.getAddedSubList());
                    }
                    if (c.wasRemoved()) {
                        list.removeAll(c.getRemoved());
                    }
                    if (c.wasUpdated()) {
                        list.removeAll(c.getRemoved());
                        list.addAll(c.getAddedSubList());
                    }
                }
            });
        }

        return list;
    }
    public static void main(String...args) {
        merge(aandb, a, b);

        System.out.println(""+aandb);
        a.set(ImmutableList.of("LAX", "BUR"));
        System.out.println(""+aandb);

    }
}

And here is the static factory to merge two ObservableLists (it should work with the ObservableImmutableList too, but its not.


package com.nield.finance;

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;

import com.google.common.collect.ImmutableList;
import com.nield.utilities.fx.ObservableImmutableList;

public class ObservableMerge {
    static ObservableImmutableList<String> a = ObservableImmutableList.of(ImmutableList.of("ABQ","DAL"));
    static ObservableImmutableList<String> b = ObservableImmutableList.of(ImmutableList.of("HOU","PHX"));

    static ObservableList<String> aandb = FXCollections.observableArrayList();


    static ObservableList<String> merge(ObservableList<String> into, ObservableList<String>... lists) {
        final ObservableList<String> list = into;
        for (ObservableList<String> l : lists) {
            list.addAll(l);
            l.addListener((javafx.collections.ListChangeListener.Change<? extends String> c) -> {
                while (c.next()) {
                    if (c.wasAdded()) {
                        list.addAll(c.getAddedSubList());
                    }
                    if (c.wasRemoved()) {
                        list.removeAll(c.getRemoved());
                    }
                    if (c.wasUpdated()) {
                        list.removeAll(c.getRemoved());
                        list.addAll(c.getAddedSubList());
                    }
                }
            });
        }

        return list;
    }
    public static void main(String...args) {
        merge(aandb, a, b);

        System.out.println(""+aandb);
        a.set(ImmutableList.of("LAX", "BUR"));
        System.out.println(""+aandb);

    }
}

将这两个列表合并成一个的算法是什么?简单的合并/追加吗? - Jens-Peter Haack
1
如果您设置了一个新的ImmutablList,就会调用失效侦听器,这将触发值绑定中的值重新计算,但不会触发任何列表更改...那么如何在删除所有旧列表值并添加所有新列表值时也调用listChangeListener()?...对我来说似乎是连贯的... - Jens-Peter Haack
好的,所以我在尝试以某种方式做到这一点时走上了正确的道路。我会再试一次,并发布可工作的类... - tmn
2个回答

7
这可能是一个起点:
    public class ObservableMerge {
        static ObservableList<String> a = FXCollections.observableArrayList();
        static ObservableList<String> b = FXCollections.observableArrayList();

        static ObservableList<String> aandb = FXCollections.observableArrayList();


        static ObservableList<String> merge(ObservableList<String> into, ObservableList<String>... lists) {
            final ObservableList<String> list = into;
            for (ObservableList<String> l : lists) {
                list.addAll(l);
                l.addListener((javafx.collections.ListChangeListener.Change<? extends String> c) -> {
                    while (c.next()) {
                        if (c.wasAdded()) {
                            list.addAll(c.getAddedSubList());
                        }
                        if (c.wasRemoved()) {
                            list.removeAll(c.getRemoved());
                        }
                    }
                });
            }

            return list;
        }
        public static void main(String...args) {
            merge(aandb, a, b);

            System.out.println(""+aandb);
            a.add("Hello");
            b.add("Peter");
            System.out.println(""+aandb);

        }
    }

太棒了,这个逐字翻译的方法非常有效。我只是将其泛化并封装在一个静态工厂中,现在可以使用了! - tmn
1
一种弱点是:如果对象被多次插入,remove()函数将会删除所有的出现实例,这时程序将无法正常工作... ;-) - Jens-Peter Haack
是的,我有点意识到了它的局限性。例如,我有自己的ObservableList实现,它包装了一个ImmutableList,可以随时在内部与新的ImmutableList交换,这会使所有依赖项无效。不确定如何管理ListChangeListeners,但对于TableView来说,它非常有效。但我认为对于包含ObservableImmutableList的两个合并列表的这种边缘情况,我会使用Guava EventBus。 - tmn
1
也许wasUpdated()函数对于你的封装器内容是否改变会有用。 - Jens-Peter Haack
好的,我正在尝试覆盖wasUpdated()并给抽象的Change类提供它想要的内容。但我仍然没有看到合并列表更新。我已经在上面更新了我的问题,其中包括我的ObserableImmutableList实现的WIP和您提供的静态工厂。 - tmn
好的,我已经纠正了实现并更新了上面的ObservableImmutableList类。感谢您的帮助! - tmn

1

我想为未来的读者记录这个。RxJavaFX 可以使像这样的推送任务变得更加容易。

    ObservableList<String> list1 = FXCollections.observableArrayList();
    ObservableList<String> list2 = FXCollections.observableArrayList();

    ObservableList<String> combinedList = FXCollections.observableArrayList();

    Observable.combineLatest(
                JavaFxObservable.fromObservableList(list1),
                JavaFxObservable.fromObservableList(list2),
            (l1,l2) -> {
                ArrayList<String> combined = new ArrayList<>();
                combined.addAll(l1);
                combined.addAll(l2);
                return combined;
            }).subscribe(combinedList::setAll);

//return unmodifiable version of combinedList

这是一个关于此工作的完整可用示例:

    ObservableList<String> list1 = FXCollections.observableArrayList();
    ObservableList<String> list2 = FXCollections.observableArrayList();

    ObservableList<String> combinedList = FXCollections.observableArrayList();

    Observable.combineLatest(
                JavaFxObservable.fromObservableList(list1),
                JavaFxObservable.fromObservableList(list2),
            (l1,l2) -> {
                ArrayList<String> combined = new ArrayList<>();
                combined.addAll(l1);
                combined.addAll(l2);
                return combined;
            }).subscribe(combinedList::setAll);

    JavaFxObservable.fromObservableList(combinedList).subscribe(System.out::println);

    list1.add("Alpha");
    list2.add("Beta");
    list1.add("Gamma");
    list1.remove("Alpha");
    list2.add("Delta");

    Thread.sleep(10000);

OUTPUT

[]
[Alpha]
[Alpha, Beta]
[Alpha, Gamma, Beta]
[Gamma, Beta]
[Gamma, Beta, Delta]

这个解决方案总是返回(或标记为已更改)整个列表,而上面的另一个解决方案使用JavaFX的“Change”系统仅通知监听器有关结果列表的更改。 - Dominique
我认为JavaFX会进行内部验证,以确定“更改”实际上是否不是更改。如果使用setAll()太过于粗暴,您可以始终使用RxJavaFX中的其他工厂来进行更精细的更改监听:https://github.com/ReactiveX/RxJavaFX#observablelist-observablemap-and-observableset - tmn

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接