如何在JavaFX中连接可观察列表?

Dim*_*ims 4 java concat javafx list observablelist

通过连接,我的意思是获取一个新列表,该列表侦听所有连接部分的更改.

方法的目的是FXCollections#concat(ObservableList<E>... lists)什么?如果它只是合并了几个列表,那么我认为没有任何意义可以为此设置单独的方法.

如果考虑做我想做的事情,那么它不起作用:

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

public class ConcatObservabeList {

   public static void main(String[] args) {

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


      ObservableList<Integer> concat = FXCollections.concat(list1, list2);
      concat.addListener(new ListChangeListener<Integer>() {
         public void onChanged(Change<? extends Integer> c) {
            System.out.println("changed");
         }
      });

      list1.add(12);


   }
}
Run Code Online (Sandbox Code Playgroud)

小智 6

我有同样需要将几个ObservableLists连接/聚合到JavaFX LineChart的一个列表中.我在此处或在另一个答案中发布的github上找到的示例始终将每个更改中的子列表中的所有条目复制到聚合列表中.对于包含许多条目的列表,这似乎不是很优雅.

我决定实现我的on版本,它跟踪聚合列表中子列表的位置,当子列表中的元素发生更改时,在聚合列表中应用相同的更改.仍然有改进的余地(不是使用委托列表,而是直接扩展ObservableList,或者将子事件中的事件发送到聚合列表并覆盖getter和迭代器 - 请提供帮助),但我想我发布了版本在这里,也许它可以帮助某人.

码:

/**
 * This class aggregates several other Observed Lists (sublists), observes changes on those sublists and applies those same changes to the
 * aggregated list.
 * Inspired by:
 * - http://stackoverflow.com/questions/25705847/listchangelistener-waspermutated-block
 * - http://stackoverflow.com/questions/37524662/how-to-concatenate-observable-lists-in-javafx
 * - https://github.com/lestard/advanced-bindings/blob/master/src/main/java/eu/lestard/advanced_bindings/api/CollectionBindings.java
 * Posted result on: http://stackoverflow.com/questions/37524662/how-to-concatenate-observable-lists-in-javafx
 */
public class AggregatedObservableArrayList<T> {

    protected final List<ObservableList<T>> lists = new ArrayList<>();
    final private List<Integer> sizes = new ArrayList<>();
    final private List<InternalListModificationListener> listeners = new ArrayList<>();
    final protected ObservableList<T> aggregatedList = FXCollections.observableArrayList();

    public AggregatedObservableArrayList() {

    }

    /**
     * The Aggregated Observable List. This list is unmodifiable, because sorting this list would mess up the entire bookkeeping we do here.
     *
     * @return an unmodifiable view of the aggregatedList
     */
    public ObservableList<T> getAggregatedList() {
        return FXCollections.unmodifiableObservableList(aggregatedList);
    }

    public void appendList(@NotNull ObservableList<T> list) {
        assert !lists.contains(list) : "List is already contained: " + list;
        lists.add(list);
        final InternalListModificationListener listener = new InternalListModificationListener(list);
        list.addListener(listener);
        //System.out.println("list = " + list + " puttingInMap=" + list.hashCode());
        sizes.add(list.size());
        aggregatedList.addAll(list);
        listeners.add(listener);
        assert lists.size() == sizes.size() && lists.size() == listeners.size() :
              "lists.size=" + lists.size() + " not equal to sizes.size=" + sizes.size() + " or not equal to listeners.size=" + listeners.size();
    }

    public void prependList(@NotNull ObservableList<T> list) {
        assert !lists.contains(list) : "List is already contained: " + list;
        lists.add(0, list);
        final InternalListModificationListener listener = new InternalListModificationListener(list);
        list.addListener(listener);
        //System.out.println("list = " + list + " puttingInMap=" + list.hashCode());
        sizes.add(0, list.size());
        aggregatedList.addAll(0, list);
        listeners.add(0, listener);
        assert lists.size() == sizes.size() && lists.size() == listeners.size() :
              "lists.size=" + lists.size() + " not equal to sizes.size=" + sizes.size() + " or not equal to listeners.size=" + listeners.size();
    }

    public void removeList(@NotNull ObservableList<T> list) {
        assert lists.size() == sizes.size() && lists.size() == listeners.size() :
              "lists.size=" + lists.size() + " not equal to sizes.size=" + sizes.size() + " or not equal to listeners.size=" + listeners.size();
        final int index = lists.indexOf(list);
        if (index < 0) {
            throw new IllegalArgumentException("Cannot remove a list that is not contained: " + list + " lists=" + lists);
        }
        final int startIndex = getStartIndex(list);
        final int endIndex = getEndIndex(list, startIndex);
        // we want to find the start index of this list inside the aggregated List. End index will be start + size - 1.
        lists.remove(list);
        sizes.remove(index);
        final InternalListModificationListener listener = listeners.remove(index);
        list.removeListener(listener);
        aggregatedList.remove(startIndex, endIndex + 1); // end + 1 because end is exclusive
        assert lists.size() == sizes.size() && lists.size() == listeners.size() :
              "lists.size=" + lists.size() + " not equal to sizes.size=" + sizes.size() + " or not equal to listeners.size=" + listeners.size();
    }

    /**
     * Get the start index of this list inside the aggregated List.
     * This is a private function. we can safely asume, that the list is in the map.
     *
     * @param list the list in question
     * @return the start index of this list in the aggregated List
     */
    private int getStartIndex(@NotNull ObservableList<T> list) {
        int startIndex = 0;
        //System.out.println("=== searching startIndex of " + list);
        assert lists.size() == sizes.size() : "lists.size=" + lists.size() + " not equal to sizes.size=" + sizes.size();
        final int listIndex = lists.indexOf(list);
        for (int i = 0; i < listIndex; i++) {
            final Integer size = sizes.get(i);
            startIndex += size;
            //System.out.println(" startIndex = " + startIndex + " added=" + size);
        }
        //System.out.println("startIndex = " + startIndex);
        return startIndex;
    }

    /**
     * Get the end index of this list inside the aggregated List.
     * This is a private function. we can safely asume, that the list is in the map.
     *
     * @param list       the list in question
     * @param startIndex the start of the list (retrieve with {@link #getStartIndex(ObservableList)}
     * @return the end index of this list in the aggregated List
     */
    private int getEndIndex(@NotNull ObservableList<T> list, int startIndex) {
        assert lists.size() == sizes.size() : "lists.size=" + lists.size() + " not equal to sizes.size=" + sizes.size();
        final int index = lists.indexOf(list);
        return startIndex + sizes.get(index) - 1;
    }

    private class InternalListModificationListener implements ListChangeListener<T> {

        @NotNull
        private final ObservableList<T> list;

        public InternalListModificationListener(@NotNull ObservableList<T> list) {
            this.list = list;
        }

        /**
         * Called after a change has been made to an ObservableList.
         *
         * @param change an object representing the change that was done
         * @see Change
         */
        @Override
        public void onChanged(Change<? extends T> change) {
            final ObservableList<? extends T> changedList = change.getList();
            final int startIndex = getStartIndex(list);
            final int index = lists.indexOf(list);
            final int newSize = changedList.size();
            //System.out.println("onChanged for list=" + list + " aggregate=" + aggregatedList);
            while (change.next()) {
                final int from = change.getFrom();
                final int to = change.getTo();
                //System.out.println(" startIndex=" + startIndex + " from=" + from + " to=" + to);
                if (change.wasPermutated()) {
                    final ArrayList<T> copy = new ArrayList<>(aggregatedList.subList(startIndex + from, startIndex + to));
                    //System.out.println("  permutating sublist=" + copy);
                    for (int oldIndex = from; oldIndex < to; oldIndex++) {
                        int newIndex = change.getPermutation(oldIndex);
                        copy.set(newIndex - from, aggregatedList.get(startIndex + oldIndex));
                    }
                    //System.out.println("  permutating done sublist=" + copy);
                    aggregatedList.subList(startIndex + from, startIndex + to).clear();
                    aggregatedList.addAll(startIndex + from, copy);
                } else if (change.wasUpdated()) {
                    // do nothing
                } else {
                    if (change.wasRemoved()) {
                        List<? extends T> removed = change.getRemoved();
                        //System.out.println("  removed= " + removed);
                        // IMPORTANT! FROM == TO when removing items.
                        aggregatedList.remove(startIndex + from, startIndex + from + removed.size());
                    }
                    if (change.wasAdded()) {
                        List<? extends T> added = change.getAddedSubList();
                        //System.out.println("  added= " + added);
                        //add those elements to your data
                        aggregatedList.addAll(startIndex + from, added);
                    }
                }
            }
            // update the size of the list in the map
            //System.out.println("list = " + list + " puttingInMap=" + list.hashCode());
            sizes.set(index, newSize);
            //System.out.println("listSizesMap = " + sizes);
        }

    }

    public String dump(Function<T, Object> function) {
        StringBuilder sb = new StringBuilder();
        sb.append("[");
        aggregatedList.forEach(el -> sb.append(function.apply(el)).append(","));
        final int length = sb.length();
        sb.replace(length - 1, length, "");
        sb.append("]");
        return sb.toString();
    }
}
Run Code Online (Sandbox Code Playgroud)

jUnit测试:

/**
 * Testing the AggregatedObservableArrayList
 */
public class AggregatedObservableArrayListTest {


    @Test
    public void testObservableValue() {
        final AggregatedObservableArrayList<IntegerProperty> aggregatedWrapper = new AggregatedObservableArrayList<>();
        final ObservableList<IntegerProperty> aggregatedList = aggregatedWrapper.getAggregatedList();
        aggregatedList.addListener((Observable observable) -> {
            System.out.println("observable = " + observable);
        });

        final ObservableList<IntegerProperty> list1 = FXCollections.observableArrayList();
        final ObservableList<IntegerProperty> list2 = FXCollections.observableArrayList();
        final ObservableList<IntegerProperty> list3 = FXCollections.observableArrayList();

        list1.addAll(new SimpleIntegerProperty(1), new SimpleIntegerProperty(2), new SimpleIntegerProperty(3), new SimpleIntegerProperty(4),
                     new SimpleIntegerProperty(5));
        list2.addAll(new SimpleIntegerProperty(10), new SimpleIntegerProperty(11), new SimpleIntegerProperty(12), new SimpleIntegerProperty(13),
                     new SimpleIntegerProperty(14), new SimpleIntegerProperty(15));
        list3.addAll(new SimpleIntegerProperty(100), new SimpleIntegerProperty(110), new SimpleIntegerProperty(120), new SimpleIntegerProperty(130),
                     new SimpleIntegerProperty(140), new SimpleIntegerProperty(150));

        // adding list 1 to aggregate
        aggregatedWrapper.appendList(list1);
        assertEquals("wrong content", "[1,2,3,4,5]", aggregatedWrapper.dump(ObservableIntegerValue::get));

        // removing elems from list1
        list1.remove(2, 4);
        assertEquals("wrong content", "[1,2,5]", aggregatedWrapper.dump(ObservableIntegerValue::get));

        // adding second List
        aggregatedWrapper.appendList(list2);
        assertEquals("wrong content", "[1,2,5,10,11,12,13,14,15]", aggregatedWrapper.dump(ObservableIntegerValue::get));

        // removing elems from second List
        list2.remove(1, 3);
        assertEquals("wrong content", "[1,2,5,10,13,14,15]", aggregatedWrapper.dump(ObservableIntegerValue::get));

        // replacing element in first list
        list1.set(1, new SimpleIntegerProperty(3));
        assertEquals("wrong content", "[1,3,5,10,13,14,15]", aggregatedWrapper.dump(ObservableIntegerValue::get));

        // adding third List
        aggregatedWrapper.appendList(list3);
        assertEquals("wrong content", "[1,3,5,10,13,14,15,100,110,120,130,140,150]", aggregatedWrapper.dump(ObservableIntegerValue::get));

        // emptying second list
        list2.clear();
        assertEquals("wrong content", "[1,3,5,100,110,120,130,140,150]", aggregatedWrapper.dump(ObservableIntegerValue::get));

        // adding new elements to second list
        list2.addAll(new SimpleIntegerProperty(203), new SimpleIntegerProperty(202), new SimpleIntegerProperty(201));
        assertEquals("wrong content", "[1,3,5,203,202,201,100,110,120,130,140,150]", aggregatedWrapper.dump(ObservableIntegerValue::get));

        // sorting list2. this results in permutation
        list2.sort((o1, o2) -> o1.getValue().compareTo(o2.getValue()));
        assertEquals("wrong content", "[1,3,5,201,202,203,100,110,120,130,140,150]", aggregatedWrapper.dump(ObservableIntegerValue::get));

        // removing list2 completely
        aggregatedWrapper.removeList(list2);
        assertEquals("wrong content", "[1,3,5,100,110,120,130,140,150]", aggregatedWrapper.dump(ObservableIntegerValue::get));

        // updating one integer value in list 3
        SimpleIntegerProperty integer = (SimpleIntegerProperty) list3.get(0);
        integer.set(1);
        assertEquals("wrong content", "[1,3,5,1,110,120,130,140,150]", aggregatedWrapper.dump(ObservableIntegerValue::get));

        // prepending list 2 again
        aggregatedWrapper.prependList(list2);
        assertEquals("wrong content", "[201,202,203,1,3,5,1,110,120,130,140,150]", aggregatedWrapper.dump(ObservableIntegerValue::get));

    }

}
Run Code Online (Sandbox Code Playgroud)


tra*_*god 3

ListChangeListener添加到 an 中的A可以看到对整个列表所做的ObservableList某些特定更改。监听任何祖先列表的开销是相当大的,如此处引用的 API所示。因为只是将引用从源列表复制到目标的后备列表,所以添加到 的侦听器将看到对;所做的更改。它不会看到或的更改。FXCollections.concat()concat concatlist1list2

如果您不需要ObservableList因其他原因创建新的侦听器,请以允许您向每个列表添加相同侦听器的方式聚合列表。

安慰:

changed { [42] added at 0 }
Run Code Online (Sandbox Code Playgroud)

代码:

import java.util.ArrayList;
import java.util.List;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;

/**
 * @see /sf/answers/2626907181/
 */
public class ObservableListAggregate {

    public static void main(String[] args) {
        ObservableList<Integer> list1 = FXCollections.observableArrayList();
        ObservableList<Integer> list2 = FXCollections.observableArrayList();
        Aggregate<ObservableList<Integer>> aggregate = new Aggregate(list1, list2);
        aggregate.addListener(new ListChangeListener<ObservableList<Integer>>() {
            @Override
            public void onChanged(ListChangeListener.Change<? extends ObservableList<Integer>> c) {
                System.out.println("changed " + c);
            }
        });
        list1.add(42);
    }

    private static class Aggregate<T> {

        List<ObservableList<T>> lists = new ArrayList<>();

        public Aggregate(ObservableList<T>... lists) {
            for (ObservableList<T> list : lists) {
                this.lists.add(list);
            }
        }

        public final void addListener(ListChangeListener<? super T> listener) {
            for (ObservableList<T> list : lists) {
                list.addListener(listener);
            }
        }

        public final void removeListener(ListChangeListener<? super T> listener) {
            for (ObservableList<T> list : lists) {
                list.removeListener(listener);
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

要查看各个列表元素的更改,请使用ObservableList<Observable>,例如ObservableList<IntegerProperty>。在下面的示例中,请注意ip,添加到 中的与后来在 中修改的list1内容相同。 IntegerPropertyconcat

安慰:

IntegerProperty [value: 42]
concat changed { [IntegerProperty [value: 2147483647]] added at 1 }
Run Code Online (Sandbox Code Playgroud)

代码:

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

public class ConcatObservabeList {

    public static void main(String[] args) {

        ObservableList<IntegerProperty> list1 = FXCollections.observableArrayList();
        IntegerProperty ip = new SimpleIntegerProperty(0);
        ip.addListener(System.out::println);
        list1.add(ip);
        ObservableList<IntegerProperty> list2 = FXCollections.observableArrayList();
        ObservableList<IntegerProperty> concat = FXCollections.concat(list1, list2);
        concat.get(0).setValue(42);
        concat.addListener(new ListChangeListener<IntegerProperty>() {
            @Override
            public void onChanged(ListChangeListener.Change<? extends IntegerProperty> c) {
                System.out.println("concat changed " + c);
            }
        });
        concat.add(new SimpleIntegerProperty(Integer.MAX_VALUE));
    }
}
Run Code Online (Sandbox Code Playgroud)