ObservableList使用元素转换绑定内容

时间:2017-05-10 10:59:20

标签: javafx binding observablelist

是否有一种方法可以绑定两个可观察列表的内容并在它们之间转换元素?例如,像这样:

ObservableList<Model> models = FXCollections.observableArrayList();
ObservableList<TreeItem<Model>> treeItemModels = FXCollections.observableArrayList();
Bindings.bindContent(treeItemModels, models, m -> new TreeItem<Model>(m));

3 个答案:

答案 0 :(得分:1)

标准API中不提供此功能。但是,ReactFX框架提供了执行此操作的机制:

ObservableList<Model> models = FXCollections.observableArrayList();
ObservableList<TreeItem<Model>> treeItemModels 
    = LiveList.map(models, m -> new TreeItem<Model>(m));

答案 1 :(得分:1)

正如James_D所说,标准API中不存在此功能,并且可以使用ReactFX框架来执行此操作。但是如果不能选择多余的家属,那么没有它们就可以轻松实现这些功能。为此,只需经过Bindings#bindContentContentBinding#bindListContentBinding类的JDK源代码,然后通过必要的修改进行复制/粘贴。因此,我们得到一个像标准内容绑定一样的绑定:

ObservableList<Model> models = FXCollections.observableArrayList();
ObservableList<TreeItem<Model>> treeItemModels = FXCollections.observableArrayList();
BindingUtil.mapContent(treeItemModels, models, m -> new TreeItem<Model>(m));

BindingUtil的来源:

public class BindingUtil {

    public static <E, F> void mapContent(ObservableList<F> mapped, ObservableList<? extends E> source,
            Function<? super E, ? extends F> mapper) {
        map(mapped, source, mapper);
    }

    private static <E, F> Object map(ObservableList<F> mapped, ObservableList<? extends E> source,
            Function<? super E, ? extends F> mapper) {
        final ListContentMapping<E, F> contentMapping = new ListContentMapping<E, F>(mapped, mapper);
        mapped.setAll(source.stream().map(o -> mapper.apply(o)).collect(toList()));
        source.removeListener(contentMapping);
        source.addListener(contentMapping);
        return contentMapping;
    }

    private static class ListContentMapping<E, F> implements ListChangeListener<E>, WeakListener {
        private final WeakReference<List<F>> mappedRef;
        private final Function<? super E, ? extends F> mapper;

        public ListContentMapping(List<F> mapped, Function<? super E, ? extends F> mapper) {
            this.mappedRef = new WeakReference<List<F>>(mapped);
            this.mapper = mapper;
        }

        @Override
        public void onChanged(Change<? extends E> change) {
            final List<F> mapped = mappedRef.get();
            if (mapped == null) {
                change.getList().removeListener(this);
            } else {
                while (change.next()) {
                    if (change.wasPermutated()) {
                        mapped.subList(change.getFrom(), change.getTo()).clear();
                        mapped.addAll(change.getFrom(), change.getList().subList(change.getFrom(), change.getTo())
                                .stream().map(o -> mapper.apply(o)).collect(toList()));
                    } else {
                        if (change.wasRemoved()) {
                            mapped.subList(change.getFrom(), change.getFrom() + change.getRemovedSize()).clear();
                        }
                        if (change.wasAdded()) {
                            mapped.addAll(change.getFrom(), change.getAddedSubList()
                                    .stream().map(o -> mapper.apply(o)).collect(toList()));
                        }
                    }
                }
            }
        }

        @Override
        public boolean wasGarbageCollected() {
            return mappedRef.get() == null;
        }

        @Override
        public int hashCode() {
            final List<F> list = mappedRef.get();
            return (list == null) ? 0 : list.hashCode();
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }

            final List<F> mapped1 = mappedRef.get();
            if (mapped1 == null) {
                return false;
            }

            if (obj instanceof ListContentMapping) {
                final ListContentMapping<?, ?> other = (ListContentMapping<?, ?>) obj;
                final List<?> mapped2 = other.mappedRef.get();
                return mapped1 == mapped2;
            }
            return false;
        }
    }
}

答案 2 :(得分:0)

要删除临时缓存元素的另一个提议:在sdorof的代码中,重新排序原始列表时出现问题。提供的更改将包含&#34;删除&#34;所有受影响的映射元素(此处为TreeItems)后跟一个&#34; add&#34;在新的序列中。因此,上面的代码在这种情况下创建了许多新的TreeItem。假设您有其他映射元素包含&#34; important&#34;你不想放松的信息,把新的&#34;空的&#34;放在一起并不是一个好主意。而不是目标列表中的元素。这里有意义的是缓存即将被删除的元素,直到处理完整个onChanged()方法。另见一个类似的线程(Best practice to decorate an ObservableList and retain change events) 更新的代码如下所示:

....
private IdentityHashMap<E, F> cache = null;

@Override
public void onChanged(Change<? extends E> change) {
    final List<F> mapped = mappedRef.get();
    if (mapped == null) {
        change.getList().removeListener(this);
    } else {
        while (change.next()) {
            if (change.wasPermutated()) {
                List<? extends E> orig = change.getList().subList(change.getFrom(), change.getTo());
                List<F> sub = mapped.subList(change.getFrom(), change.getTo());
                cache(orig, sub);
                sub.clear();
                mapped.addAll(change.getFrom(), orig.stream().map(e -> computeIfAbsent(e)).collect(Collectors.toList()));
            } else {
                if (change.wasRemoved()) {
                    List<F> sub = mapped.subList(change.getFrom(), change.getFrom() + change.getRemovedSize());
                    if (change.wasAdded()) {
                        List<? extends E> orig = change.getRemoved();
                        cache(orig, sub);
                    }
                    sub.clear();
                }
                if (change.wasAdded())
                    mapped.addAll(change.getFrom(),change.getAddedSubList().stream().map(e -> computeIfAbsent(e)).collect(Collectors.toList()));
            }
        }
        cache = null;
    }
}

private void cache(List<? extends E> orig, List<F> mapped) {
    if (cache == null)
        cache = new IdentityHashMap<>();
    for (int i = 0; i < orig.size(); i++)
        cache.put(orig.get(i), mapped.get(i));
}

private F computeIfAbsent(E e) {
    F f = null;
    if (cache != null)
        f = cache.get(e);
    if (f == null)
        f = mapper.apply(e);
    return f;
}
....