JavaFX-可观察集合(Observable Collections)


概述

  • Observable Collections(可观察集合)是JavaFX对Java集合的扩展
  • JavaFX提供了三种类型的可观察集合,(可以观察到集合变化),主要通过以下三个接口进行支持
    • ObservableList : 继承ListObservable接口
    • ObservableSet: 继承SetObservable接口
    • ObservableMap: 继承MapObservable接口
  • Observable Collections有两个额外特性
    • 支持失效通知,继承Observable接口的特性
    • 支持属性变化通知,可以注册监听器,当内容发生改变时会通知
  • 工具类: javafx.collections.FXCollections
  • 创建: 没有公开的构造函数,只能通过工具类FXCollections创建

ObservableList

ObservableList是一个支持属性变化通知的List集合,其实就是对List集合进行封装,底层还是基于List集合,可以通过以下四个方法注册/移除监听器

  • addListener(ListChangeListener<? super E>): 注册属性变化监听器
  • removeListener(ListChangeListener<? super E>): 移除属性变化监听器
  • addListener(InvalidationListener listener): 注册属性失效监听器
  • removeListener(InvalidationListener listener): 移除属性失效监听器
创建

必须通过FXCollections类的工厂方法进行创建

  • ObservableList emptyObservableList() : 创建空的不可修改的ObservableList,常作为空参数传递
  • ObservableList observableArrayList() : 创建底层由ArrayList构成的集合
  • ObservableList observableArrayList(Collection col): 创建并初始化集合内容
  • ObservableList observableArrayList(E... items): 创建并初始化集合内容
  • ObservableList observableList(List list): 创建底层基于传递的List构成的集合
  • ObservableList observableArrayList(Callback extractor): 传入抽取器,用于实现监听集合内元素属性的变化,如不通过集合方法add,remove等直接修改集合内元素属性的更新变化,详见下文
  • ObservableList observableList(List list, Callback extractor): 底层基于传递的List集合,其他同上
监听ObservableList失效
  • Invalidation : 失效监听

        @Test
        public void invalidationTest(){
            ObservableList<Integer> observableList = FXCollections.observableArrayList();
            observableList.addListener((Observable obs) -> {
                System.out.println("invalid");
            });
            observableList.add(10); //集合改变触发失效事件
        }
监听ObservableList变化
  • Change: 变化监听

        @Test
        public void changeTest(){
            ObservableList<Integer> observableList = FXCollections.observableArrayList();
            observableList.addListener((ListChangeListener.Change<? extends Integer> c) -> {
                System.out.println("change");
                while (c.next()){
                    if(c.wasAdded()) System.out.println(" -add");
                }
            });
            observableList.add(10);//集合改变触发变化事件
        }
  • ListChangeListener.Change类: 在变化监听器中通过此对象获取变化详细信息

    方法 描述
    ObservableList getList() 获取源可观察集合
    boolean next() 是否有下一个变化,通过此遍历所有变化(初始change实例是无效的,在调用其他方法之前需要通过此方法使change指向下次变化)
    void reset() 重置到初始阶段,可以重新通过next方法遍历所有变化
    boolean wasAdded() 变化是否为添加元素
    boolean wasRemoved() 变化是否为移除元素
    boolean wasReplaced() 变化是否为替换元素
    boolean wasPermutated() 变化是否为重排序
    boolean wasUpdated() 是否除了getFromgetTo之间元素以外的元素发生改变,可选事件,可能不会被所有ObservableList触发
    int getFrom() 集合中变化的开始位置
    int getTo() 集合中变化的结束位置,getFrom~getTo表示变化的区间,如果为添加则为所有添加元素的区间,如果为重排序则返回变动元素的区间,如果为删除,则开始位置和结束位置同为删除元素的起始索引
    int getAddedSize() 返回添加元素的数量
    List getAddedSubList() 返回添加元素的集合
    List getRemoved() 获取删除元素的集合
    int getRemovedSize() 获取删除元素的数量
    int getPermutation(int oldIndex) 获取指定索引位置元素重排序后的新索引
  • 监听ObservableList更新(内部元素属性更新)

    • 如果还想监听集合内元素属性的更新变化(Observable对象才行),则需要通过一下两个方法传入extractor提取器,当集合发生变化时,会将变化的元素作为参数传入到extractor中,同时返回一个Observable[]即可观察数组,这样数组中的Observable对象发生变化时也会触发集合注册的监听器,触发后change.wasUpdated() = true
    • <E> ObservableList<E> observableArrayList(Callback<E,Observable[]> extractor)
    • <E> ObservableList<E> observableList(List<E> list,Callback<E,Observable[]> extractor)
        @Test
        public void extractorTest(){
            /**
             *  extractor提取器: 集合中元素发生变化时执行
             *  参数: 变化的元素会作为参数传入
             *  返回值: 返回Observable[]数组对象,
             *         这些返回的对象(Observable对象)也会被集合注册的监听器监听,
             *         当这些Observable对象发生变化,change.wasUpdated() = true
             */
            Callback<IntegerProperty,Observable[]> extractor = p -> {
                System.out.println("p: " + p.get());
                return new Observable[]{p};
            };
            //监听器: 注册到集合
            ListChangeListener<IntegerProperty> listener = change -> {
                while(change.next()){
                    if(change.wasUpdated()) {
                        System.out.println("update");
                        int begin = change.getFrom();
                        int end  = change.getTo();
                        System.out.printf("begin~end: [%d,%d]%n",begin,end);
                        List<? extends  IntegerProperty> subList = change.getList().subList(begin,end);
                        subList.stream()
                                .mapToInt(IntegerExpression::intValue)
                                .forEach(System.out::println);
                    }
                }
            };
    
            //不触发change.wasUpdated(): 不传入extractor提取器
            ObservableList<IntegerProperty> listA = FXCollections.observableArrayList();
            listA.addListener(listener);
            IntegerProperty a = new SimpleIntegerProperty(1);
            listA.add(a);
            a.set(10);//listener监听器不触发
    
            //触发change.wasUpdated(): 传入extractor提取器
            ObservableList<IntegerProperty> listB = FXCollections.observableArrayList(extractor);
            listB.addListener(listener);
            IntegerProperty b = new SimpleIntegerProperty(2);
            listB.add(b);
            b.set(20);//listener监听器触发,change.wasUpdated() = true
    
            /** 执行结果
             * p: 2
             * update
             * begin~end: [0,1]
             * 20
             */
        }

ObservableSet

ObservableSet是一个支持属性变化通知的Set集合,其实就是对Set集合进行封装,底层还是基于Set集合,可以通过以下四个方法注册/移除监听器

  • addListener(SetChangeListener<? super E>): 注册属性变化监听器
  • removeListener(SetChangeListener<? super E>): 移除属性变化监听器
  • addListener(InvalidationListener listener): 注册属性失效监听器
  • removeListener(InvalidationListener listener): 移除属性失效监听器
创建

必须通过FXCollections类的工厂方法进行创建

  • <E> ObservableSet<E> observableSet(E... elements)
  • <E> ObservableSet<E> observableSet(Set<E> set)
  • <E> ObservableSet<E> emptyObservableSet()

ObservableMap

是底层基于Map的可观察集合,支持属性变化通知,可以通过一下四个方法注册/移除监听器

  • void addListener(MapChangeListener<? super K, ? super V> listener): 注册属性变化监听器
  • void removeListener(MapChangeListener<? super K, ? super V> listener): 移除属性变化监听器
  • addListener(InvalidationListener listener): 注册属性失效监听器
  • removeListener(InvalidationListener listener): 移除属性失效监听器
创建

必须通过FXCollections类的工厂方法进行创建

  • <K,V> ObservableMap<K, V> observableHashMap(): 底层基于HashMap创建可观察集合
  • <K,V> ObservableMap<K, V> observableMap(Map<K, V> map): 基于传入的Map创建
  • <K,V> ObservableMap<K,V> emptyObservableMap(): 创建空的不可更改的可观察集合

JavaFX集合的属性和绑定

ObservableListObservableSetObservableMap可以作为对象的公开属性,因此也支持使用high-levellow-level绑定API

ObservableList的属性和绑定
  • 通过ListProperty(对Observable进行封装)实现了属性绑定特性
  • ListProperty实现了ObservableValueObservableList接口,实际上就是对ObservableList的封装,通过ListProperty可以使用ObservableList的所有方法,同时也支持属性和绑定
创建ListProperty
  • *创建: * 通过SimpleListProperty类的构造函数创建ListProperty对象

    • SimpleListProperty()

    • SimpleListProperty(ObservableList initialValue)

    • SimpleListProperty(Object bean, String name)

    • SimpleListProperty(Object bean, String name, ObservableList initialValue)

    • 注意: 必须传入ObservableList对象,不通过构造函数传入,则通过set方法设置

          @Test
          public void listPropertyTest(){
              //错误: 不传入ObservableList则使用时报错
              SimpleListProperty<Integer> listPropertyA = new SimpleListProperty<>();
              //listPropertyA.add(1); //java.lang.UnsupportedOperationException
      
              //正确: 1.通过构造函数传入ObservableList
              SimpleListProperty<Integer> listPropertyB = new SimpleListProperty<>(FXCollections.observableArrayList());
              listPropertyB.add(2);
      
              //正确: 2.通过set方法设置ObservableList
              SimpleListProperty<Integer> listPropertyC = new SimpleListProperty<>();
              listPropertyC.set(FXCollections.observableArrayList());
              listPropertyC.add(3);
          }
监听ListProperty属性变化
  • 可以向ListProperty添加如下监听器,当ListProperty内部引用的ObservableList发生变化或者其内容发生变化是,将会被监听器监听到
    • InvalidationListener: 失效监听器
    • ChangeListener: 属性变化监听器
    • ListChangeListener: 集合属性变化监听器
ListProperty绑定
  • ListProperty对外暴露两个属性: sizeempty

    • size属性: 通过sizeProperty()获取,返回ReadOnlyIntegerProperty,表示集合大小属性
    • empty属性: 通过emptyProperty()获取,返回ReadOnlyBooleanProperty,表示集合是否为空属性
        @Test
        public void sizeAndEmptyTest(){
            ListProperty<Integer> listProperty = new SimpleListProperty<>(FXCollections.observableArrayList());
            ReadOnlyIntegerProperty sizeProperty = listProperty.sizeProperty();
            sizeProperty.addListener((observable, oldValue, newValue) -> {
                System.out.println(oldValue + " > " + newValue);
            });
            ReadOnlyBooleanProperty emptyProperty = listProperty.emptyProperty();
            emptyProperty.addListener((observable, oldValue, newValue) -> {
                System.out.println(oldValue + " >> " + newValue);
            });
    
            listProperty.add(1);//0 > 1   //true >> false
            listProperty.addAll(2,3);//1 > 3
            listProperty.addAll(4);//3 > 4
        }
  • 绑定列表属性和内容

    • Low-level: 自定义绑定,可以通过自定义类继承ListBinding类实现

    • high-level: 系统提供的绑定由ListExpressionBindings类提供

    • 绑定其内部ObservableList的引用: 通过bind()bindBidirectional()方法即可

      注意: 不论使用单向还是双向绑定,最终两个ListProperty内部使用同一个ObservableList的引用

          @Test
          public void bindListReferenceTest(){
              ListProperty<String> lp1 = new SimpleListProperty<>(FXCollections.observableArrayList());
              ListProperty<String> lp2 = new SimpleListProperty<>(FXCollections.observableArrayList());
              /** 单向绑定:
               * 绑定之后lp1和lp2内部引用的ObservableList为同一个
               */
              lp1.bind(lp2);
              lp1.addAll("a","b");
              //lp1.get()==lp2.get(): true , lp1: [a, b] ,lp2: [a, b]
              System.out.printf("lp1.get()==lp2.get(): %s , lp1: %s ,lp2: %s%n",lp1.get()==lp2.get(),lp1.get(),lp2.get());
              lp2.set(FXCollections.observableArrayList("1","2"));
              //lp1.get()==lp2.get(): true , lp1: [1, 2] ,lp2: [1, 2]
              System.out.printf("lp1.get()==lp2.get(): %s , lp1: %s ,lp2: %s%n",lp1.get()==lp2.get(),lp1.get(),lp2.get());
      
              //解绑
              lp1.unbind();
              lp2.set(FXCollections.observableArrayList("c"));
              //lp1.get()==lp2.get(): false , lp1: [1, 2] ,lp2: [c]
              System.out.printf("lp1.get()==lp2.get(): %s , lp1: %s ,lp2: %s%n",lp1.get()==lp2.get(),lp1.get(),lp2.get());
      
              /** 双向绑定: 和单向绑定一样
               * 绑定之后lp1和lp2内部引用的ObservableList为同一个
               */
              lp1.bindBidirectional(lp2);
              //lp1.get()==lp2.get(): true , lp1: [c] ,lp2: [c]
              System.out.printf("lp1.get()==lp2.get(): %s , lp1: %s ,lp2: %s%n",lp1.get()==lp2.get(),lp1.get(),lp2.get());
              lp1.set(FXCollections.observableArrayList("d","f"));
              //lp1.get()==lp2.get(): true , lp1: [d, f] ,lp2: [d, f]
              System.out.printf("lp1.get()==lp2.get(): %s , lp1: %s ,lp2: %s%n",lp1.get()==lp2.get(),lp1.get(),lp2.get());
          }
    • 绑定其内部ObservableList的内容: 通过bindContent()bindContentBidirectional()方法即可

          @Test
          public void bindListContentTest(){
              ListProperty<String> lp1 = new SimpleListProperty<>(FXCollections.observableArrayList());
              ListProperty<String> lp2 = new SimpleListProperty<>(FXCollections.observableArrayList());
              //内容单向绑定
              lp1.bindContent(lp2);
              lp1.add("z");//不推荐这样操作: 由于单向绑定,依赖与lp2,不符合单向绑定逻辑
              lp2.addAll("a","b"); //lp1也会增加此内容
              //lp1.get()==lp2.get(): false , lp1: [a, b, z] ,lp2: [a, b]
              System.out.printf("lp1.get()==lp2.get(): %s , lp1: %s ,lp2: %s%n",lp1.get()==lp2.get(),lp1.get(),lp2.get());
      
              //解绑
              lp1.unbindContent(lp2);
              //lp1.get()==lp2.get(): false , lp1: [a, b, z] ,lp2: [a, b]
              System.out.printf("lp1.get()==lp2.get(): %s , lp1: %s ,lp2: %s%n",lp1.get()==lp2.get(),lp1.get(),lp2.get());
      
              //内容双向绑定: 对任意一个集合内容做出修改,通用会作用到另一个集合上
              lp1.bindContentBidirectional(lp2); //双向绑定后内容相同
              //lp1.get()==lp2.get(): false , lp1: [a, b] ,lp2: [a, b]
              System.out.printf("lp1.get()==lp2.get(): %s , lp1: %s ,lp2: %s%n",lp1.get()==lp2.get(),lp1.get(),lp2.get());
              lp1.add("1");
              //lp1.get()==lp2.get(): false , lp1: [a, b, 1] ,lp2: [a, b, 1]
              System.out.printf("lp1.get()==lp2.get(): %s , lp1: %s ,lp2: %s%n",lp1.get()==lp2.get(),lp1.get(),lp2.get());
              lp2.add("2");
              //lp1.get()==lp2.get(): false , lp1: [a, b, 1, 2] ,lp2: [a, b, 1, 2]
              System.out.printf("lp1.get()==lp2.get(): %s , lp1: %s ,lp2: %s%n",lp1.get()==lp2.get(),lp1.get(),lp2.get());
          }
  • 对列表中的元素进行绑定: 可以绑定到ListProperty内部ObservableList中的特定元素

    • ObjectBinding<E> valueAt(int index) : 绑定指定索引的元素,如果索引超出范围则绑定null
    • ObjectBinding<E> valueAt(ObservableIntegerValue index)绑定指定索引(索引为可观察类型)的元素,超出范围则绑定null
        @Test
        public void bindListElementsTest(){
            ListProperty<Integer> lp = new SimpleListProperty<>(FXCollections.observableArrayList());
            lp.addAll(100);
            ObjectBinding<Integer> zero = lp.valueAt(0);
            //lp[0]:100,zero:100
            System.out.printf("lp[0]:%s,zero:%s%n",lp.get(0),zero.get());
            lp.set(0,10);
            //lp[0]:10,zero:10
            System.out.printf("lp[0]:%s,zero:%s%n",lp.get(0),zero.get());
    
            //绑定末尾元素,size-1=末尾索引
            ObjectBinding<Integer> last = lp.valueAt(lp.sizeProperty().subtract(1));
            //lp[0]:10,last:10
            System.out.printf("lp[0]:%s,last:%s%n",lp.get(0),last.get());
            lp.add(120);
            //lp[0]:10,last:120
            System.out.printf("lp[0]:%s,last:%s%n",lp.get(0),last.get());
        }
ObservableSet的属性和绑定
  • 通过SetProperty(对ObservableSet进行封装)实现了属性绑定特性

  • SetProperty使用上与ListProperty相似,以下可以直接参考上面Listproperty即可

  • 相同点:

    • *绑定: *
      • high-level: 系统提供的绑定可以直接使用SetExpressionBindings
      • low-level: 自定义绑定通过自定义类继承SetBinding类实现
    • sizeempty属性
    • 绑定到内部ObservableSet的引用,绑定到内部ObservableSet的内容
    • 支持incalidationchangesetChange事件
  • 不同点:

    • SetProperty内部的ObservableSet的元素是无序的,因此不支持绑定到指定元素,即没有valueAt()方法支持
创建SetProperty

通过 SimpleSetProperty类的构造方法进行创建

  • SimpleSetProperty()
  • SimpleSetProperty(ObservableSet<E> initialValue)
  • SimpleSetProperty(Object bean, String name)
  • SimpleSetProperty(Object bean, String name, ObservableSet<E> initialValue)
  • 注意: 必须传入ObservableSet对象,不通过构造函数传入,则通过set方法设置
ObservableMap的属性和绑定
  • 通过MapProperty(对ObservableMap进行封装)实现了属性绑定特性
  • MapProperty使用上与ListProperty相似,以下可以直接参考上面Listproperty即可
  • 相同点:
    • *绑定: *
      • high-level: 系统提供的绑定可以直接使用MapExpressionBindings
      • low-level: 自定义绑定通过自定义类继承MapBinding类实现
    • sizeempty属性
    • 绑定到内部ObservableMap的引用,绑定到内部ObservableMap的内容
    • 支持incalidationchangemapChange事件
  • 不同点:
    • MapProperty支持绑定到其内部ObservableMap的指定key的元素,通过valueAt()方法即可
创建MapProperty

通过SimpleMapProperty类构造方法创建

  • SimpleMapProperty()
  • SimpleMapProperty(Object bean, String name)
  • SimpleMapProperty(Object bean, String name, ObservableMap<K,V> initialValue)
  • SimpleMapProperty(ObservableMap<K,V> initialValue)

文章作者: Bryson
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Bryson !
评论
 本篇
下一篇 
JavaFX-属性和绑定 JavaFX-属性和绑定
概述 Java中的基本数据类型和对象不是可观察的(observable),即不支持监听属性的失效和变化,同时也支持数据绑定 JavaFX中对Java的数据类型和对象进行了封装,使用JavaFX提供的属性,则支持监听属性的失效和变化,同时还
2022-06-10
  目录