(原文:斯科特霍梅尔/甲骨文高级技术专家)
原文地址:http://docs.oracle.com/javafx/2/binding/jfxpub-binding.htm
本教程通过一些可以编译和运行的例子描述了JavaFX的属性和绑定。关于JavaFX的安装,请参阅JavaFX安装指南。
概述
很多年以来,Java语言一直使用JavaBean来表示对象的属性,这种模式既包含API,也包含设计模式,它已经广泛地被Java应用程序开发者所熟知,开发工具也一直使用这种模式。 这个版本将属性支持引入到了JavaFX中,它基于经过验证的JavaBean的模式,但做了扩展和改进。
JavaFX的属性经常通过绑定来综合,这是一种表达变量间关系的强大的机制。当对象被绑定后,一个对象的改变会自动被反射到另一个对象。对应用程序来说,这是非常有用的。例如,绑定可以用在帐单票据跟踪程序中。在这种情况下,当一个独立的帐单改动后,总帐单将会自动改变。另外,绑定还可以应用到图形用户界面(GUI)中,用来将应用程序的基础数据的改变同步显示出来。
绑定集成了一个或多个资源,也称为“依赖关系”,绑定观察者保存依赖关系的列表,当检测到变化时自动更新列表。
绑定的API分为两大类:
1.高级别的API:提供了一种简单的方法来创建最常见的绑定用例。它的语法很容易学习和使用,特别是在支持自动完成功能的环境中。比如NetBeans IDE中。
2.低级别的API:提供额外的灵活性,可供高级开发人员在高级别API不能满足需要时使用。低级别的API是专为快速执行和小内存占用设计的。
本教程的所面部分将介绍这些API,并提供代码示例,您可以编译和运行它们。有关其他信息,请参阅JavaFX API文档。
理解属性
理解JavaFX的属性需要学习一些新的API和命名约定。你完全有可能只对使用包含属性的类感兴趣(反对在你自已的类中实现属性)。但例1将让你熟悉一种新的来自属性模式的命名规则。它定义了一个名为Bill的类,实现了一个名为amoutDue的属性。
例1:定义一个属性
import javafx.beans.property.DoubleProperty;
class Bill {
//定义一个变量来保存属性
private DoubleProperty amountDue = new DoubleProperty();
//定义一个getter方法来获取属性的值
public final double getAmountDue(){return amountDue.get();}
//定义一个setter来设定属性的值
public final void setAmountDue(double value){amountDue.set(value);}
//定义一个getter来访问属性
public DoubleProperty amountDueProperty() {return amountDue;}
}
amountDue对象是一个javafx.beans.property.DoubleProperty的类实例,被标以private来限制外部访问。这在Java或JavaBean程序开发中是一种标准做法。但是请注意,该对象的不是一个标准的Java基本数据类型,而是封装了Java基本数据类型,并增加了一些额外功能的封装类。(javafx.beans.property包中的类都内置了观察和绑定作为设计的一部分)。
属性方法命名规则如下:
l getAmountDue()是一个标准的getter方法,返回当前值amountDue属性的值。按照惯例,它被声明为final。注意,此方法返回类型是双精度数值而不DoubleProperty类型。
l setAmountDue(double)方法(也被声明为final)是一个标准的setter方法,用来设置属性的值。setter方法是可选的,其参数的类型也是双精度数值。
l 最后,amountDueProperty()方法定义了属性的getter。这是一种新的命名约定,方法中包含了属性的名称(在本例中是amountDue),然后加上“Property”。 返回类型是属性本身的类型(本例中是DoubleProperty)。
在用JavaFX构建GUI应用程序时,你会注意到某些API里的类已经实现了属性。例如:javafx.scene.shape.Rectangle类包含的属性arcHeight,arcWidth,height,width,x,y。对于这些属性,都有与前面提到的命名规则相对应的方法。例如,getArcHeight(),setArcHeight(double),arcHeightProperty()。 它们共同说明(对开发人员和工具),给定的属性存在。
例2:使用ChangeListener
import javafx.beans.value.ObservableValue;
import javafx.beans.value.ChangeListener;
public class Main {
public static void main(String[] args) {
Bill electricBill = new Bill();
electricBill.amountDueProperty().addListener(new ChangeListener(){
@Override public void changed(ObservableValue o,Object oldVal,
Object newVal){
System.out.println("Electric bill has changed!");
}
});
electricBill.setAmountDue(100.00);
}
}
运行该示例将在标准输出中打印消息“帐单已经改变”,证明更改侦听的起作用了。
使用高级别绑定API
高级别API是在你的应用程序中使用绑定的最快和最简单的方式。 它包括两个部分:Fluent API和绑定类。Fluent API将方法向各种依赖对象公开,而绑定类提供静态工厂方法。
要开始使用Fluent API,考虑一个简单的用例。其中两个整数是绑定的,以使他们的值观总是加在一起。在例3中,涉及三个变量:num1(依赖者),num2(依赖者)和sum(绑定)。依赖类型都IntegerProperty,绑定类型是NumberBinding。
例3:使用Fluent API
package bindingdemo;
import javafx.beans.property.IntegerProperty;
import javafx.binding.NumberBinding;
public class Main {
public static void main(String[] args) {
IntegerProperty num1 = new IntegerProperty(1);
IntegerProperty num2 = new IntegerProperty(2);
NumberBinding sum = num1.add(num2);
System.out.println(sum.getValue());
num1.set(2);
System.out.println(sum.getValue());
}
}
此代码绑定两个依赖,打印其总和。改变值num1并再次打印总和,结果是“3”和“4”,这证明了绑定起作用了。
也可以使用绑定类来做同样的事,如例4所示。
例4:使用绑定类
import javafx.beans.property.IntegerProperty;
import javafx.binding.NumberBinding;
import javafx.binding.Bindings;
public class Main {
public static void main(String[] args) {
IntegerProperty num1 = new IntegerProperty(1);
IntegerProperty num2 = new IntegerProperty(2);
NumberBinding sum = Bindings.add(num1,num2);
System.out.println(sum.getValue());
num1.setValue(2);
System.err.println(sum.getValue());
}
}
例5使用了两种方法。
例5:使用两种方法
import javafx.beans.property.IntegerProperty;
import javafx.binding.NumberBinding;
import javafx.binding.Bindings;
public class Main {
public static void main(String[] args) {
IntegerProperty num1 = new IntegerProperty(1);
IntegerProperty num2 = new IntegerProperty(2);
IntegerProperty num3 = new IntegerProperty(3);
IntegerProperty num4 = new IntegerProperty(4);
NumberBinding total = Bindings.add(num1.multiply(num2),num3.multiply(num4));
System.out.println(total.getValue());
num1.setValue(2);
System.err.println(total.getValue());
}
}
例5修改代码来调用Fluent API的multiply和绑定类的add方法。你也应该知道,高级别API允许你定义算术运算时混合类型。结果类型的定义为采用与Java编程语言相同的规则:
1. 如果操作数是双精度,则结果也是双精度。
2. 如果不是双精度而是浮点型,其结果是一个浮点型。
3. 如果不是浮点型而是长整型,则结果是长整型。
4. 其他情况,结果是一个整数。
下一节探讨观察者,并演示失效监听与改变监听的不同。
探索ObservableValue,InvalidationListener和ChangeListener
属性和绑定类都实现了ObservableValue<T>接口,ObservableValue包装了值并允许它观察改变。JavaFX绑定和属性实现都支持懒惰计算,这意味着当变化发生时,该值不立即重新计算。重新计算稍后发生,也就时当再次请求时。
在例6中,帐单总数(一个绑定)在它检测到其中的一个依赖的第一时间被标记为无效。无论如何,只有当帐单总数被再次请求时,绑定对象才被重新计算。
例6:使用InvalidationListener
import javafx.beans.property.DoubleProperty;
import javafx.binding.NumberBinding;
import javafx.binding.Bindings;
import javafx.beans.value.InvalidationListener;
import javafx.beans.value.ObservableValue;
class Bill {
// Define the property
private DoubleProperty amountDue = new DoubleProperty();
// Define a getter for the property‘s value
public final double getAmountDue(){return amountDue.get();}
// Define a setter for the property‘s value
public final void setAmountDue(double value){amountDue.set(value);}
// Define a getter for the property itself
public DoubleProperty amountDueProperty() {return amountDue;}
}
public class Main {
public static void main(String[] args) {
Bill bill1 = new Bill();
Bill bill2 = new Bill();
Bill bill3 = new Bill();
NumberBinding total = Bindings.add(bill1.amountDueProperty().add(bill2.amountDueProperty()),
bill3.amountDueProperty());
total.addListener(new InvalidationListener() {
@Override public void invalidated(ObservableValue o) {
System.out.println("The binding is now invalid.");
}
});
//第一次调用使绑定失效
bill1.setAmountDue(200.00);
//绑定现在无效
bill2.setAmountDue(100.00);
bill3.setAmountDue(75.00);
//绑定现在有效
System.out.println(total.getValue());
//再次使用失效
bill3.setAmountDue(150.00);
//使它有效
System.out.println(total.getValue());
}
}
通过改变单一帐单的值,绑定变为无效,无效监听器触发。但是,如果绑定已经无效,无效监听器将会再次被触发,即使其他帐单发生了变化。(在例6中,调用total.getValue()使绑定从无效变为有效)。我们知道这个是因为随后对依赖列表中任一帐单的改变导致了无准备监听器的再次触发。如果绑定仍然无效,则触发不会发生。
ObservableValue,InvalidationListener和ChangeListener的方法签名如下:
ObservableValue
l public void addListener(ChangeListener listener)
l public void addListener(InvalidationListener listener)
l public T getValue()
l public void removeListener(ChangeListener listener)
l public void removeListener(InvalidationListener listener)
InvalidationListener
l public void invalidated(ObservableValue observable)
ChangeListener
l public void changed(ObservableValue observable, T oldValue, T newValue)
请注意,注册一个ChangeListener将强制执行急迫计算,即使ObservableValue的实现支持懒惰计算。对于一个懒惰计算值,在它被重新计算前,是不可能知道一个无效值是否已经被改变的。出于这个原因,产生改变事件需要急迫计算。而失效的事件,可以通过急迫实现产生,也可以通过懒惰实现产生。
使用低级别绑定API
如果高级别API不够满足您的需求,你总是可以使用低级别的API来代替。低级别的API是为那些需要更多灵活性(或更好性能)的开发者准备的,这些特性在高级API中没有提供。
示例7展示了使用低级别的API基本的例子。
例7:使用低级别API
import javafx.beans.property.DoubleProperty;
import javafx.binding.DoubleBinding;
public class Main {
public static void main(String[] args) {
final DoubleProperty a = new DoubleProperty(1);
final DoubleProperty b = new DoubleProperty(2);
final DoubleProperty c = new DoubleProperty(3);
final DoubleProperty d = new DoubleProperty(4);
DoubleBinding db = new DoubleBinding() {
{
super.bind(a, b, c, d);
}
@Override
protected double computeValue() {
return (a.get() * b.get()) + (c.get() * d.get());
}
};
System.out.println(db.get());
b.set(3);
System.out.println(db.get());
}
}
使用低级别的API调用继承自绑定类的一个方法并且覆写了computeValue()方法,返回当前绑定的值。例7通过这一个自定义的DoubleBinding子类完成这一工作。调用super.bind()向上将依赖传递给DoubleBinding,以便默认无效行为被保留。它通常没有必要为你检查绑定是否无效,这些行为通过基类来提供。
现在你已经有足够的信息来使用低级别API了,本文的后续版本将扩展这部份内容来说明如何展现各种不周的优化策略以使你自定义的绑定更有效率。