JavaFX-属性和绑定


概述

  • Java中的基本数据类型和对象不是可观察的(observable),即不支持监听属性的失效和变化,同时也支持数据绑定

  • JavaFX中对Java的数据类型和对象进行了封装,使用JavaFX提供的属性,则支持监听属性的失效和变化,同时还支持数据绑定

1. java bean简介
  • 一个java类包含: fields(字段)和methods(方法),字段代表对象的状态,通常为private(私有的),然后通过public(公共的)方法,称为accessors(访问器),或getterssetters方法,用于读取或者修改对象的属性,

    对于使用公共的访问器去访问类中全部或者部分私有字段的类称为java bean

  • java bean是一个可观察对象,它支持属性变化通知(变化时发送通知给监听器)

  • 本质上通过java bean定义了可重用组件,可以实现属性的readread/writewrite,通过此给第三方使用

2. 属性(property)
  • 即类的可公开访问的属性,它影响类的状态和行为,可以通过公开的方法去访问或者修改, 同时是可以观察的,当属性发生改变时,相关方会受到通知
3. 绑定(binding)
  • unidirectional(单向绑定): 如 x = y + z 关系, 当y和z发生变化时x跟着变化
  • bidirectional(双向绑定): 如 x = y 关系,当y变化是x跟着变化,同时x变化是y跟着变化
  • eager binding (实时绑定): 绑定对象在其依赖项改变时,立刻重新计算
  • lazy binding(懒加载形式绑定): 绑定对象在其依赖项改变时,不会立刻重新计算,而是绑定对象被使用时才会重新计算,对于非必须实时绑定的情况,JavaFx中采用了此种方式进行性能优化
4. JavaBeans对绑定的支持

实例: 当员工(Employee)的薪水(salary)发生改变时实现通知

  • Employee.java

    import java.beans.PropertyChangeListener;
    import java.beans.PropertyChangeSupport;
    
    public class Employee {
        private String name;
        private double salary;
        //JavaBeans对绑定的支持,通过此对象实现salary变化时执行监听器
        //requires java.desktop; 需添加此模块
        private PropertyChangeSupport pcs = new PropertyChangeSupport(this);
        public Employee(String name, double salary) {
            this.name = name;
            this.salary = salary;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public double getSalary() {
            return salary;
        }
        public void setSalary(double salary) {
            double oldSalary = this.salary;
            this.salary = salary;
            //将改变通知给注册的监听器
            pcs.firePropertyChange("salary",oldSalary,salary);
        }
        //注册监听器
        public void addPropertyChangeListener(
                PropertyChangeListener listener) {
            pcs.addPropertyChangeListener(listener);
        }
        //移除监听器
        public void removePropertyChangeListener(
                PropertyChangeListener listener) {
            pcs.removePropertyChangeListener(listener);
        }
    }
  • 测试

        @Test
        public void changeSupportTest(){
            Employee employee = new Employee("Tom",10D);
            employee.setSalary(100D);
            //添加监听器
            employee.addPropertyChangeListener((PropertyChangeEvent evt) -> {
                System.out.printf("%s发生变化,oldValue: %s,newValue: %s",evt.getPropertyName(),evt.getOldValue(),evt.getNewValue());
                //输出结果:   salary发生变化,oldValue: 100.0,newValue: 123.0
            });
            //改变salary,则监听器触发
            employee.setSalary(123D);
        }

JavaFX中的属性

  • JavaFX中的所有属性都是可观察的(observable),即可以监听属性失效(invalidation)和变化(change),同时可以是读/写(read/write)或只读(read-only)的
  • JavaFX中属性可以表示为一个值(单值属性),或者包含值的集合(集合属性: 后续介绍)
1.属性

JavaFX提供的属性: 其实就是对Java数据类型和JavaBean的封装,增加了可观察(observable)和绑定特性,即

  • 支持属性读/写只读特性

  • 支持监听属性失效和变化

  • 支持属性绑定

  • 使用JavaFX提供的属性替换Java提供的数据类型: 可以实现对象属性的监听和绑定,以下列举部分,其他类似
  • 单值属性
    • IntegerProperty 接口: 对应intInteger,常用SimpleIntegerProperty创建对象
    • DoubleProperty接口: 对应doubleDouble,常用SimpleDoubleProperty创建对象
    • StringProperty接口: 对应String,常用SimpleStringProperty创建对象
    • BooleanProperty接口: 对应booleanBoolean,常用SimpleBooleanProperty创建对象
    • ObjectProperty接口: 对应对象类型,通过泛型指定具体类型,常用SimpleObjectProperty<T>创建
  • 集合属性
    • ListProperty接口: 对应List,常用SimpleListProperty创建对象
    • MapProperty接口: 对应Map,常用SimpleMapProperty创建对象
    • SetProperty接口: 对应Set,常用SimpleSetProperty创建对象
  • 只读属性: 使用对应的ReadOnlyXXXWrapper创建wrapper对象,该对象是可读/写的,然后使用wrapper对象getReadOnlyProperty()方法获取只读属性对象,如:
    • ReadOnlyIntegerWrapper
    • ReadOnlyStringWrapper
2.方法

JavaFX的属性都有以下方法

  • get/set: 通过基本类型获取或者设置属性的值(无法拆箱为基本类型的还为引用类型,此时与getValue/setValue相同)

  • getValue/setValue: 通过包装类型/引用类型获取或设置属性的值

3.创建

JavaFX的属性包含以下三个部分

  • 一个bean对象的引用,必须在构造函数指定,不指定则为null,可通过getBean方法获取
  • name: 名称,必须在构造函数中指定,不指定则为null,可通过getName方法获取
  • value: 值,可通过get/setgetValue/setValue获取和设置

构造函数

  • SimpleIntegerProperty()
  • SimpleIntegerProperty(int initialValue): 创建时指定值
  • SimpleIntegerProperty(Object bean, String name): 创建时指定bean对象和值
  • SimpleIntegerProperty(Object bean, String name, int initialValue): 创建是指定bean对象丶名称和值

JavaFX属性类的层次结构

1.都实现了Observable接口: 属性失效接口(即支持失效监听)
  • 实现该接口则实现了属性失效监听
  • 接口中抽象方法
    • void addListener(InvalidationListener): 添加失效监听器
    • void removeListener(InvalidationListener): 移除失效监听器
2.都实现了ObservableValue接口: 属性变化接口(即支持变化监听)
  • 实现该接口则实现了属性变化监听
  • 接口中抽象方法
    • void addListener(ChangeListener<? super T>): 添加变化监听器
    • void removeListener(ChangeListener<? super T>): 移除变化监听器
3.都实现了Property<T>接口: 属性绑定接口(即支持单向双向绑定)
  • 该接口定义了单向绑定和双向绑定
  • 接口中的抽象方法
    • void bind(ObservableValue<? extends T> observable): Property类型对象单向绑定到ObservableValue对象
    • void unbind(): 解绑单向绑定
    • void bindBidirectional(Property<T> other): Property类型对象双向绑定到Property类型对象
    • void unbindBidirectional(Property<T> other): 解绑双向绑定
    • boolean isBound(): 是否绑定

JavaFX属性的事件监听

1.属性无效事件
    @Test
    public void invalidationTest(){
        SimpleIntegerProperty sip = new SimpleIntegerProperty(1);
        //添加属性失效监听器
        sip.addListener(observable -> {
            System.out.println("invalidation");
        });
        sip.set(2);//触发失效事件
        sip.set(3);//不触发失效事件: 失效后再次改变值不触发

        //获取值后,变为有效,下次再改变值则触发失效事件
        System.out.println("get: " + sip.get());
        sip.set(4);

        /** 输出
         * invalidation
         * get: 3
         * invalidation
         */
    }
2.属性变化事件
    @Test
    public void changeTest(){
        SimpleIntegerProperty sip = new SimpleIntegerProperty(1);
        //添加属性变化事件
        sip.addListener((observableValue, oldValue, newValue) -> {
            System.out.println("changed: " + oldValue + "->" + newValue);
        });
        //触发
        sip.set(2);
        //设置相同的值不触发
        System.out.println("set 2 begin: ");
        sip.set(2);
        //触发
        System.out.println("set 3 begin: ");
        sip.set(3);
        /** 输出结果
         * changed: 1->2
         * set 2 begin: 
         * set 3 begin: 
         * changed: 2->3
         */
    }
3.无效事件和变化事件选择
  • 对于通过两种事件监听都能实现的功能,优先考虑使用无效事件监听,因为其基于懒加载模式
  • 通常不获取属性的值时只需要使用无效事件监听即可,如果要获取属性的值则需要使用变化事件监听

JavaFX属性的绑定

  • unidirectional binding(单向绑定)
  • bidirectional binding(双向绑定)
1.概述
  • 绑定表达式规则后,一个属性的依赖属性值改变时,其属性也会跟着改变

  • JavaFX的绑定时懒加载形式的,当使用时才会去重新计算值

    @Test
    public void bindTest(){
        IntegerProperty x = new SimpleIntegerProperty(100);
        IntegerProperty y = new SimpleIntegerProperty(200);
        //创建绑定: sum = x + y
        NumberBinding sum = x.add(y);
        //无效: 懒加载: 未使用时,当使用时才会重新计算
        System.out.println("bind begin");
        System.out.println("invalid: " + sum.isValid());
        System.out.println("sum: " + sum.intValue());
        System.out.println("invalid: " + sum.isValid());
        //懒加载: 依赖属性改变时失效,当使用时重新计算变为有效
        System.out.println("x set 101 begin");
        x.set(101);
        System.out.println("invalid: " + sum.isValid());
        System.out.println("sum: " + sum.intValue());
        System.out.println("invalid: " + sum.isValid());
        /** 输出结果
         * bind begin
         * invalid: false
         * sum: 300
         * invalid: true
         * x set 101 begin
         * invalid: false
         * sum: 301
         * invalid: true
         */
    }
2.单向绑定
  • 单向传播: 依赖属性的改变会传播到绑定属性,但是反之则不行
  • 单向绑定之后,只需要改变依赖属性,则绑定属性会跟着改变,通常逻辑上不要直接修改绑定属性的值
  • 单向绑定只能有一个,绑定多次会取消前面的绑定
  • 方法:
    • void bind(ObservableValue<? extends T>): 单向绑定,注意单向绑定的参数ObservableValue类型,和绑定属性类型可以不同: 即此方式调用Property<T>.bind(ObservableValue)
    • unbind(): 解绑单向绑定
    @Test
    public void unidirectionalTest(){
        IntegerProperty x = new SimpleIntegerProperty(1);
        IntegerProperty y = new SimpleIntegerProperty();
        System.out.println("bind before x=" + x.intValue() +  ",y=" + y.intValue());
        //单向绑定: 此时y = 1
        y.bind(x);
        System.out.println("bind after x=" + x.intValue() +  ",y=" + y.intValue());
        //改变x的值,则y跟着改变
        x.set(10);
        System.out.println("x set 10,y=" + y.intValue());
        //解绑
        y.unbind();
        x.set(100);
        System.out.println("unbind after and x set 100: x=" + x.intValue() + ",y=" + y.intValue());
        /**输出结果
         * bind before x=1,y=0
         * bind after x=1,y=1
         * x set 10,y=10
         * unbind after and x set 100: x=100,y=10
         */
    }
3.双向绑定
  • 双向绑定: 依赖属性或者绑定属性的改变均会传播到另一方
  • 双向绑定可以有多个,因此解绑时需要指定解绑的依赖属性对象
  • 方法:
    • bindBidirectional(Property): 双向绑定,注意双向绑定的参数和绑定属性类型相同,即此方式调用Property.bindBidirectional(Property)
    • void unbindBidirectional(Property<Number>): 解绑双向绑定
    @Test
    public void bidirectionalTest(){
        SimpleIntegerProperty x = new SimpleIntegerProperty(1);
        SimpleIntegerProperty y = new SimpleIntegerProperty(2);
        System.out.printf("bind before: x=%d,y=%d%n",x.intValue(),y.intValue());
        //双向绑定
        x.bindBidirectional(y);
        System.out.printf("bind after: x=%d,y=%d%n",x.intValue(),y.intValue());
        //改变x值
        x.set(3);
        System.out.printf("x set 3 after: x=%d,y=%d%n",x.intValue(),y.intValue());
        //改变y值
        y.set(4);
        System.out.printf("y set 4 after: x=%d,y=%d%n",x.intValue(),y.intValue());
        /**输出结果
         * bind before: x=1,y=2
         * bind after: x=2,y=2
         * x set 3 after: x=3,y=3
         * y set 4 after: x=4,y=4
         */
    }

绑定相关的API

1.High-Level绑定: JavaFX类库提供的绑定
  • Ternary: when-then-otherwise三元运算符可在绑定api中使用,直接返回绑定对象,即XXXBinding对象

    • new When(condition).then(value1).otherwise(value2)
       @Test
        public void ternaryTest(){
            IntegerProperty x = new SimpleIntegerProperty(10);
            StringBinding desc = new When(x.divide(2).multiply(2).isEqualTo(x.intValue()))
                    .then("偶数:even")
                    .otherwise("奇数:odd");
            System.out.println("x="+x.intValue() + ": " + desc.get());
            x.set(13);
            System.out.println("x="+x.intValue() + ": " + desc.get());
            /**输出结果
             * x=10: 偶数:even
             * x=13: 奇数:odd
             */
        }
  • 有两种形式: Fluent APIBindings类,可以单独使用也可以混合使用

  • Fluent API: 即使用绑定属性对象的绑定相关方法(实现了绑定表达式接口的方法),如x.add(y)

    • 一般XXXProperty都实现了绑定表达式接口如NumberExpression,StringExpression,ObjectExpression,BooleanExpression
    • NumberExpression的方法
      • NumberBinding add(): 加
      • NumberBinding subtract(): 减
      • NumberBinding multiply(): 乘
      • NumberBinding divide(): 除
      • BooleanBinding greaterThan(): 大于
      • BooleanBinding greaterThanOrEqualTo(): 大于等于
      • BooleanBinding isEqualTo(): 等于
      • BooleanBinding isNotEqualTo(): 不等于
      • BooleanBinding lessThan(): 小于
      • BooleanBinding lessThanOrEqualTo(): 小于等于
      • NumberBinding negate(): 否定
      • StringBinding asString(): 转字符串
    • StringExpression的方法
      • StringExpression concat(): 拼接字符串
    • ObjectExpression的方法
      • BooleanBinding isEqualTo():是否相等
  • Bindings类: 即使用该类的方法绑定两个属性对象

    • NumberBinding add():加

    • NumberBinding subtract(): 减

    • NumberBinding multiply():乘

    • NumberBinding divide():除

    • BooleanBinding and():与

    • void bindBidirectional():双向绑定

    • void unbindBidirectional():解绑双向绑定

    • StringExpression concat():拼接字符串

    • StringExpression convert():字符串反转

    • XXXBinding createXXXBinding(): 创建XXXBinding对象

    • BooleanBinding equal():相等

    • BooleanBinding notEqual():不等

    • BooleanBinding equalIgnoreCase():字符串相等忽略大小写

    • BooleanBinding notEqualIgnoreCase():字符串不等忽略大小写

    • StringExpression format(): 格式化字符串

    • BooleanBinding greaterThan():大于

    • BooleanBinding greaterThanOrEqual():大于等于

    • BooleanBinding lessThan():小于

    • BooleanBinding lessThanOrEqual():小于等于

    • BooleanBinding isNotNull:不为null

    • BooleanBinding isNull:为null

    • NumberBinding max():最大值,其中一个参数必须为ObservableNumberValue类型

    • NumberBinding min():最小值

    • NumberBinding negate():否定

    • NumberBinding not():非

    • NumberBinding or():或

    • XXXBinding selectXXX():用于选择嵌套属性,如Bindings.selectString(school,student,name)表示school对象内部的student对象的name属性

    • when():创建When实例

        @Test
        public void bindingsTest(){
            IntegerProperty x = new SimpleIntegerProperty(2);
            //创建绑定y = x + 1
            NumberBinding y = Bindings.add(1,x);
            System.out.printf("bind after: x=%d,y=%d%n",x.intValue(),y.intValue());
            x.set(10);
            System.out.printf("x set 10: x=%d,y=%d%n",x.intValue(),y.intValue());
            StringExpression desc = Bindings.format(Locale.US, "x=%d,y=%d%n",x,y);
            System.out.println("desc: " + desc.get());
            /**输出结果
             * bind after: x=2,y=3
             * x set 10: x=10,y=11
             * desc: x=10,y=11
             */
        }
  • 混合使用Fluent APIBindings

    DoubleProperty radius = new SimpleDoubleProperty(1.0);
    DoubleProperty area = new SimpleDoubleProperty(0);
    //混合使用
    area.bind(Bindings.multiply(Math.PI, radius.multiply(radius)));
2.Low-Level绑定: 自定义绑定
  • 基本步骤

    • 自定义一个类继承绑定类XXXBinding,如DoubleBinding
    • 调用父类的bind()方法来绑定所有依赖(将依赖项作为参数传递给此方法即可)
    • 重写父类的computeValue()方法,用于实现自定义绑定计算规则
  • 实例:圆面积area(绑定属性)与半径radius(依赖项)绑定

        @Test
        public void lowLevelBindTest(){
            final DoubleProperty radius = new SimpleDoubleProperty(1D);
            DoubleProperty area = new SimpleDoubleProperty(0D);
            //使用匿名类方式
            DoubleBinding areaBinding = new DoubleBinding() {
                //调用父类bind方法,并传入依赖项
                {
                    super.bind(radius);//依赖项为圆的半径
                }
                //重写computeValue方法
                @Override
                protected double computeValue() {
                    double r = radius.get();
                    return Math.PI * r * r; //面积
                }
            };
            area.bind(areaBinding);//绑定
            System.out.printf("radius=%.2f,area=%.2f%n",radius.get(),area.get());
            System.out.println("set radius=3");
            radius.set(3D);
            System.out.printf("radius=%.2f,area=%.2f%n",radius.get(),area.get());
        }

文章作者: Bryson
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Bryson !
评论
 上一篇
下一篇 
JavaFX-初识 JavaFX-初识
概述 JavaFX是一个基于Java的开源框架,用于Java平台的图形用户界面(GUI)开发技术领域,是Java swing的继承者 JavaFX还可以使用任何支持Java虚拟机(JVM)的脚本语言编写,如Kotlin丶Groovy和Sc
2022-05-28
  目录