通过连接,我的意思是获取一个新列表,该列表侦听所有连接部分的更改。
方法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);
}
}
答案 0 :(得分: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();
}
}
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));
}
}
答案 1 :(得分:2)
添加到ListChangeListener
的ObservableList
会将某些特定changes视为整个列表。如API所引用here所示,也可以听取任何祖先列表的开销很大。由于FXCollections.concat()
只是将源列表中的引用复制到目标的后备列表,因此添加到concat
的侦听器将查看对concat
所做的更改;它不会看到对list1
或list2
的更改。
如果您因某些其他原因不需要创建新的ObservableList
,汇总列表的方式允许您为每个列表添加相同的侦听器。< / p>
控制台:
changed { [42] added at 0 }
代码:
import java.util.ArrayList;
import java.util.List;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
/**
* @see https://stackoverflow.com/a/37527245/230513
*/
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);
}
}
}
}
要查看各个列表元素的更改,请使用ObservableList<Observable>
,例如ObservableList<IntegerProperty>
。在下面的示例中,请注意,添加到ip
的{{1}}是list1
中稍后修改的相同 IntegerProperty
。
控制台:
concat
代码:
IntegerProperty [value: 42]
concat changed { [IntegerProperty [value: 2147483647]] added at 1 }
答案 2 :(得分:1)
我偶然发现了相同的问题,因为方法FXCollections.concat(...)
并没有随着源列表的更改而改变,这是我的用例所必需的。
由于这个问题的其他答案对我来说似乎有点过大,所以我将添加自己的解决方案,它有两个主要限制:
ObservableList
将对源列表的内容只读。ObservableList
将是每次更改的新实例,因此,基于它们的可能的UI元素将在每次更改时重新创建。由于任何ListBinding
都会计算ObservableList
作为其值并同时实现(因此有一个)ObservableList
,因此可以轻松地使用它来增强默认值{{1 }}方法来更新源列表的更改:
concat
答案 3 :(得分:0)
我遇到了同样的问题,并在库advanced-bindings(javadoc of the helper method)中为此用例创建了一个帮助器。
答案 4 :(得分:0)
我也有同样的问题。最后我列出了ObservableList:
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.WeakInvalidationListener;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.collections.WeakListChangeListener;
/**
* Read only view on a list of {@link ObservableList}
*
* @author Marcel Heckel
*/
public class CompositeObservableList<T> extends AbstractList<T> implements ObservableList<T>
{
protected List<ObservableList<T>> lists;
protected List<ListChangeListener<? super T>> listChangeListeners = new ArrayList<>();
protected List<InvalidationListener> invalidationListeners = new ArrayList<>();
protected InvalidationListener internalInvalidationListener = this::invalidated;
protected ListChangeListener<T> internalListChangeListener = this::onChanged;
protected WeakInvalidationListener weakInvalidationListener = new WeakInvalidationListener(
internalInvalidationListener);
protected WeakListChangeListener<T> weakListChangeListener = new WeakListChangeListener<>(
internalListChangeListener);
public CompositeObservableList()
{
this.lists = new ArrayList<>();
}
public CompositeObservableList(List<ObservableList<T>> lists)
{
this.lists = lists;
for (ObservableList<T> l : lists)
{
l.addListener(weakInvalidationListener);
l.addListener(weakListChangeListener);
}
}
public void addObservableList(ObservableList<T> l)
{
lists.add(l);
l.addListener(weakInvalidationListener);
l.addListener(weakListChangeListener);
}
/** remove listeners and clears the internal list */
public void clearLists()
{
for (ObservableList<T> l : lists)
{
l.removeListener(weakInvalidationListener);
l.removeListener(weakListChangeListener);
}
lists.clear();
}
///////////////////////////////////////
// listeners
private void invalidated(Observable observable)
{
for (InvalidationListener l : invalidationListeners)
l.invalidated(CompositeObservableList.this);
}
private void onChanged(ListChangeListener.Change<? extends T> c)
{
int idx = getStartIndexOfListReference(c.getList());
assert (idx >= 0);
if (idx < 0)
return;
c = new IndexOffsetChange<>(CompositeObservableList.this, idx, c);
for (ListChangeListener<? super T> l : listChangeListeners)
{
l.onChanged(c);
}
}
private int getStartIndexOfListReference(ObservableList<?> l)
{
int startIndex = 0;
for (int i = 0; i < lists.size(); i++ )
{
if (l == lists.get(i))
return startIndex;
startIndex += l.size();
}
return -1;
}
////////////////////////////////////////
@Override
public int size()
{
int size = 0;
for (Collection<T> c : lists)
size += c.size();
return size;
}
@Override
public boolean isEmpty()
{
for (Collection<T> c : lists)
if ( !c.isEmpty())
return false;
return true;
}
@Override
public boolean contains(Object obj)
{
for (Collection<T> c : lists)
if (c.contains(obj))
return true;
return false;
}
@Override
public boolean containsAll(Collection<?> c)
{
for (Object ele : c)
{
if ( !this.contains(ele))
return false;
}
return true;
}
@Override
public int indexOf(Object o)
{
int index = 0;
for (List<T> l : lists)
{
int i = l.indexOf(o);
if (i >= 0)
return index + i;
index += l.size();
}
return -1;
}
@Override
public T get(int index)
{
if (index < 0)
throw new IndexOutOfBoundsException("index: " + index + " - size: " + size());
for (List<T> l : lists)
{
if (l.size() > index)
return l.get(index);
index -= l.size();
}
throw new IndexOutOfBoundsException("index: " + index + " - size: " + size());
}
@Override
public Iterator<T> iterator()
{
return new Iterator<T>()
{
Iterator<T> currentIterator = null;
Iterator<ObservableList<T>> listsIterator = lists.iterator();
@Override
public boolean hasNext()
{
while (true)
{
if (currentIterator != null && currentIterator.hasNext())
return true;
if ( !listsIterator.hasNext())
return false;
currentIterator = listsIterator.next().iterator();
}
}
@Override
public T next()
{
if ( !hasNext())
throw new NoSuchElementException();
return currentIterator.next();
}
@Override
public void remove()
{
throw new UnsupportedOperationException();
}
};
}
// editing methods
@Override
public boolean add(T obj)
{
throw new UnsupportedOperationException();
}
@Override
public boolean remove(Object obj)
{
throw new UnsupportedOperationException();
}
@Override
public boolean addAll(Collection<? extends T> c)
{
throw new UnsupportedOperationException();
}
@Override
public boolean removeAll(Collection<?> c)
{
throw new UnsupportedOperationException();
}
@Override
public boolean retainAll(final Collection<?> c)
{
throw new UnsupportedOperationException();
}
@Override
public void clear()
{
throw new UnsupportedOperationException();
}
@Override
public boolean addAll(int index, Collection<? extends T> c)
{
throw new UnsupportedOperationException();
}
@Override
public T set(int index, T element)
{
throw new UnsupportedOperationException();
}
@Override
public void add(int index, T element)
{
throw new UnsupportedOperationException();
}
@Override
public T remove(int index)
{
throw new UnsupportedOperationException();
}
// editing methods of ObservableList list
@Override
public boolean addAll(T... elements)
{
throw new UnsupportedOperationException();
}
@Override
public boolean setAll(T... elements)
{
throw new UnsupportedOperationException();
}
@Override
public boolean setAll(Collection<? extends T> c)
{
throw new UnsupportedOperationException();
}
@Override
public boolean removeAll(T... elements)
{
throw new UnsupportedOperationException();
}
@Override
public boolean retainAll(T... elements)
{
throw new UnsupportedOperationException();
}
@Override
public void remove(int from, int to)
{
throw new UnsupportedOperationException();
}
/////////////////////////////
@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)
{
listChangeListeners.add(listener);
}
@Override
public void removeListener(ListChangeListener<? super T> listener)
{
listChangeListeners.remove(listener);
}
////////////////
private static class IndexOffsetChange<T> extends ListChangeListener.Change<T>
{
private final int indexOffset;
private final ListChangeListener.Change<? extends T> delegate;
public IndexOffsetChange(ObservableList<T> list, final int indexOffset,
ListChangeListener.Change<? extends T> c)
{
super(list);
this.indexOffset = indexOffset;
this.delegate = c;
}
@Override
public boolean next()
{
return delegate.next();
}
@Override
public void reset()
{
delegate.reset();
}
@Override
public int getFrom()
{
return delegate.getFrom() + indexOffset;
}
@Override
public int getTo()
{
return delegate.getTo() + indexOffset;
}
@Override
public boolean wasPermutated()
{
return delegate.wasPermutated();
}
@Override
public int getPermutation(int i)
{
return indexOffset + super.getPermutation(i - indexOffset);
}
@Override
protected int[] getPermutation()
{
return null;
}
@SuppressWarnings("unchecked")
@Override
public List<T> getAddedSubList()
{
return (List<T>) delegate.getAddedSubList();
}
@Override
public int getAddedSize()
{
return delegate.getAddedSize();
}
@Override
public boolean wasAdded()
{
return delegate.wasAdded();
}
@SuppressWarnings("unchecked")
@Override
public List<T> getRemoved()
{
return (List<T>) delegate.getRemoved();
}
@Override
public int getRemovedSize()
{
return delegate.getRemovedSize();
}
@Override
public boolean wasRemoved()
{
return delegate.wasRemoved();
}
@Override
public boolean wasReplaced()
{
return delegate.wasReplaced();
}
@Override
public boolean wasUpdated()
{
return delegate.wasUpdated();
}
}
}
答案 5 :(得分:0)
如果您满意使用库,建议使用EasyBind
:
ObservableList<String> listA = ...;
ObservableList<String> listB = ...;
ObservableList<String> combinedList = EasyBind.concat(listA, listB);
如果您不喜欢添加新的依赖项,那么使用ListBinding
的@LukasKörfer的答案将是最好的。