概述
Java中的基本数据类型和对象不是可观察的(
observable),即不支持监听属性的失效和变化,同时也支持数据绑定JavaFX中对Java的数据类型和对象进行了封装,使用JavaFX提供的属性,则支持监听属性的失效和变化,同时还支持数据绑定
1. java bean简介
一个java类包含:
fields(字段)和methods(方法),字段代表对象的状态,通常为private(私有的),然后通过public(公共的)方法,称为accessors(访问器),或getters和setters方法,用于读取或者修改对象的属性,对于使用公共的访问器去访问类中全部或者部分私有字段的类称为
java beanjava 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.javaimport 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()方法获取只读属性对象,如:ReadOnlyIntegerWrapperReadOnlyStringWrapper
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()); }