概述
Java中的基本数据类型和对象不是可观察的(
observable
),即不支持监听属性的失效和变化,同时也支持数据绑定JavaFX中对Java的数据类型和对象进行了封装,使用JavaFX提供的属性,则支持监听属性的失效和变化,同时还支持数据绑定
1. java bean简介
一个java类包含:
fields
(字段)和methods
(方法),字段代表对象的状态,通常为private
(私有的),然后通过public
(公共的)方法,称为accessors
(访问器),或getters
和setters
方法,用于读取或者修改对象的属性,对于使用公共的访问器去访问类中全部或者部分私有字段的类称为
java bean
java bean
是一个可观察对象,它支持属性变化通知(变化时发送通知给监听器)本质上通过
java bean
定义了可重用组件,可以实现属性的read
丶read/write
丶write
,通过此给第三方使用
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
接口: 对应int
和Integer
,常用SimpleIntegerProperty
创建对象DoubleProperty
接口: 对应double
和Double
,常用SimpleDoubleProperty
创建对象StringProperty
接口: 对应String
,常用SimpleStringProperty
创建对象BooleanProperty
接口: 对应boolean
和Boolean
,常用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/set
丶getValue/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 API
和Bindings
类,可以单独使用也可以混合使用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
:不为nullBooleanBinding isNull
:为nullNumberBinding 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 API
和Bindings
类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()); }