首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >JavaFX: ObsevableMap keySet as a ObservableSet

JavaFX: ObsevableMap keySet as a ObservableSet
EN

Stack Overflow用户
提问于 2014-09-15 07:24:00
回答 1查看 721关注 0票数 3

我想将观察者地图的keySet转换为只读ObservableSet。我不想复制值,对ObservableMap的任何修改都必须影响可观察到的keySet。如果我将另一组绑定到可观察的密钥集内容,则其内容将自动更新。

这就是我想写的。

代码语言:javascript
复制
ObservableMap<String, Object> map = FXCollections.observableHashMap();
ObservableSet<String> keySet = FXCollections.observableKeySet(map);
Set<String> boundSet = new HashSet<String>();
Bindings.bindContent(boundSet, keySet);
map.put("v", new Object());
assert boundSet.contains("v");

JavaFX SDK中有这个功能吗?

EN

回答 1

Stack Overflow用户

发布于 2022-04-19 15:55:39

据我所知,没有内置的方法可以做到这一点。

下面是我为处理这个问题而创建的实用方法:

代码语言:javascript
复制
    /**
     * Builds and returns an observable keyset of the specified map. Note that the resulting
     * keyset is a new set that is guaranteed to have the same items as the map's true
     * keyset, but does not make any guarantees about iteration order or implementation
     * details of the keyset (for example, if the Map is a SortedMap, the keyset 
     * will not necessarily maintain keys in sorted order).
     * @param <K> Map's key type
     * @param <V> Map's value type
     * @param map the ObservableMap to which the set should be bound
     * @return a new observable set reflecting the map's keys 
     */
    public static <K, V> ObservableSet<K> getObservableKeySet(ObservableMap<K, V> map) {
        
        ObservableSet<K> set = FXCollections.observableSet(new HashSet<>());
        
        map.addListener(
                (MapChangeListener<K, V>)(
                        change -> {
                            if(change.wasAdded() && !change.wasRemoved()) {
                                set.add(change.getKey());
                            }
                            if(change.wasRemoved() && !change.wasAdded()) {
                                set.remove(change.getKey());
                            }
                            //Note that if change was added and removed, that means that
                            //the key was unchanged and a value was just replaced. That
                            //shouldn't affect the keyset so we do nothing
                            
                        })
                );
        
        return set;
        
    }

更新:

我决定不喜欢结果集的迭代顺序与原始映射的迭代顺序不匹配,因此我创建了一个新的类,充当映射的键集的可观察包装器。这更像Map的内置keySet()方法,其中它返回实际集合的视图。这只是添加了使其可观察到的侦听器:

代码语言:javascript
复制
/**
 * Observable view of an ObservableMap's keyset
 */
public class ObservableKeySet<K, V> implements ObservableSet<K> {

    /**
     * The map's actual keyset object that gets wrapped
     */
    private Set<K> wrappedSet;
    
    /**
     * Invalidation listeners to be notified when the set changes. Note that we end 
     * up calling these more than we should since invalidation listeners should only 
     * be called if the observable value is observed between changes and we're going 
     * to call them on every change. However, the ObservableSet returned from 
     * FxCollectionUtilities.observableSet actually also does that too, so I feel 
     * like we can get away with it.
     */
    private Collection<InvalidationListener> invalidationListeners = new ArrayList<>();
    
    /**
     * Change listeners to be notified when the set changes
     */
    private Collection<SetChangeListener<? super K>> changeListeners = new ArrayList<>();
    
    /**
     * Creates an Observable Set view of the specified map's keyset
     * @param map ObservableMap
     */
    public ObservableKeySet(ObservableMap<K, V> map) {
        this.wrappedSet = map.keySet();
        
        map.addListener((MapChangeListener<K,V>)this::onMapChange);
        
    }
    
    /**
     * Code to be executed on any match change. It will determine if there is a resulting
     * set change and trigger listeners as appropriate
     * @param change
     */
    private void onMapChange(MapChangeListener.Change<? extends K, ? extends V> change) {
        
        SetChangeListener.Change<K> setChange = null;
        
        //Note that if the map change says that there was an add and removal, then
        //that means a value was getting replaced, which doesn't result in a keySet
        //change
        if(change.wasAdded() && ! change.wasRemoved()) {
            setChange = new BasicSetChange(true, change.getKey());
        }
        else if(change.wasRemoved() && ! change.wasAdded()) {
            setChange = new BasicSetChange(false, change.getKey());
        }
        
        if(setChange != null) {
            
            invalidationListeners.forEach(listener -> listener.invalidated(this));
            
            final SetChangeListener.Change<K> finalChange = setChange;
            changeListeners.forEach(listener -> listener.onChanged(finalChange));
        }
        
    }

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

    @Override
    public void addListener(SetChangeListener<? super K> listener) {
        changeListeners.add(listener);
    }

    @Override
    public void removeListener(SetChangeListener<? super K> listener) {
        changeListeners.remove(listener);
    }
    
    //Simple wrapper method that either just pass through to the wrapped set or 
    //throw an unsupported operation exception
    @Override public int size() {return wrappedSet.size();}
    @Override public boolean isEmpty() {return wrappedSet.isEmpty();}
    @Override public boolean contains(Object o) {return wrappedSet.contains(o);}
    @Override public Iterator<K> iterator() {return wrappedSet.iterator();}
    @Override public Object[] toArray() {return wrappedSet.toArray();}
    @Override public <T> T[] toArray(T[] a) {return wrappedSet.toArray(a);}
    @Override public boolean containsAll(Collection<?> c) {return wrappedSet.containsAll(c);}
    
    @Override public boolean add(K e) {throw new UnsupportedOperationException();}
    @Override public boolean remove(Object o) {throw new UnsupportedOperationException();}
    @Override public boolean addAll(Collection<? extends K> c) {throw new UnsupportedOperationException();}
    @Override public boolean retainAll(Collection<?> c) {throw new UnsupportedOperationException();}
    @Override public boolean removeAll(Collection<?> c) {throw new UnsupportedOperationException();}
    @Override public void clear() {throw new UnsupportedOperationException();}
    
    
    /**
     * Simple implementation of {@link SetChangeListener.Change}
     */
    private class BasicSetChange extends SetChangeListener.Change<K> {
        
        /** If true, it is an add change, otherwise it is a remove change*/
        private final boolean isAdd;
        
        /** Value that was added or removed */
        private final K value;
        
        /**
         * @param isAdd {@link #isAdd}
         * @param value {@link #value}
         */
        public BasicSetChange(boolean isAdd, K value) {
            super(ObservableKeySet.this);
            this.isAdd = isAdd;
            this.value = value;
        }
        

        @Override
        public boolean wasAdded() {
            return isAdd;
        }

        @Override
        public boolean wasRemoved() {
            return !isAdd;
        }

        @Override
        public K getElementAdded() {
            return isAdd ? value : null;
        }

        @Override
        public K getElementRemoved() {
            return isAdd ? null : value;
        }
    }

}
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/25842780

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档