在进行JavaFX绑定之前,要明白,JavaFX建立在普通的Java SE运行上。为了在本文中展示这个示例,你需要为你的操作系统(在此只支持官方的Windows 和Mac OS X)下载合适的JavaFX SDK。具体请参考Resources。安装installation过程只需要几个步骤。在Windows的机器上,默认安装地址是C:\Program Files\JavaFX\javafx-sdk1.1. Mac OS X users should look at /Library/Frameworks/JavaFX.framework/Versions/1.1。
图1. JavaFX SDK基础目录 |
图1 显示了JavaFX SDK的基础目录。这个bin目录包括可执行编译并运行JavaFX Script程序。我们不使用它们。JavaFX(Script)documentation在docs中。文件src.zip包括部分JavaFX运行的来源。如果你打开它,你会注意到文件是以stg 和 .st结尾的。***lib和它的子目录包含库.jars。本文的示例取决于它们中的一些。
Locations
lib/shared/javafxrt.jar包含com.sun.javafx.runtime.location包。它的类和接口来自基本的JavaFX Binding的构建块。例如,Location接口代表一个值,它可能是可变的或是不能改变的,有效或无效的,空的或是非空。这样的状态可以通过相应的getters来查询,例如,isMutable()。如果Location的值是无效的,当update()方法被调用时,或该值被检索时,它会被更新。
一个Location的类型是通过子接口来决定的;例如,IntLocation.。如果你要在src.zip中查询IntLocation.java,你不会看到它。这是因为它的来源是来自两个文件XxxLocation.st 和XxxTemplate.stg。每个子接口为类型XYZ添加getAsXYZ和and setAsXYZ()。还有DoubleLocation, FloatLocation, ShortLocation, CharLocation, LongLocation, BooleanLocation, ByteLocation和 ObjectLocation。
其他的对象可能会附属于一个Location。当值与地址变化联系在一起的时候,change listeners可以接收到通知。***,Locations是很懒惰的:虽然当值无效的时候change listeners会得到通知,但是新的值不会被重新计算直到需要它的时候。到目前为止,我指谈论了接口的问题。当然,可以为我以上所提到的类型而随时执行Locations。
public static void main(String[] args) {
final IntVariable i1 = IntVariable.make(42); i1.addChangeListener(new ChangeListener() { @Override public boolean onChange() { System.out.println("onChange(): " + i1.get()); return false; } }); System.out.println(i1.get() + ", isMutable(): " + i1.isMutable()); IntLocation i2 = IntConstant.make(24); System.out.println(i2.get() + ", isMutable(): " + i2.isMutable()); i1.set(i2.get()); } |
为了编译并运行LocationDemo1,请附加lib/shared/javafxrt.jar到你的类路径上。这个演示示例采用了IntVariable 和IntConstant类。两个都执行了IntLocation接口,因此是Locations。使用静态make()方法创建Instances。使用get()查询当前值。正如你在图2中所看到的,在初始化引发一个通知之后,设置一个值。它通过子抽象类ChangeListener来进行处理。
图2. LocationDemo1输出 |
当地址内容已经改变的时候,它的onChange()方法被调用。该方法返回一个Boolean值,指示监听者是否仍然有效。返回false将导致监听者从监听者名单上删除。Javadoc建议,当相关的弱引用被报告清除的时候,那些做它们自己弱引用管理的监听者应该返回false。
就像JGoodies Binding的ValueModel,还有Beans Binding的Property一样,Locations 读取和编写类型值提供了一个方法。它们也可以通知注册的监听者关于值的变化。***,你将会在以下的小节中看到它们用于建立绑定。
建立Java绑定
像Beans Binding 和JGoodies Binding一样,JavaFX运行包含一个辅助类来建立JavaFX绑定:com.sun.javafx.runtime.location.Bindings。它是用于在两个Locations之间建立bijective关系。这个意思是说如果一个值被更新,它所对应的也会被更新。在Locations被实例之后,它们被传递到bijectiveBind()。
public class BindingDemo1 {
private static IntLocation i1, i2; public static void main(String[] args) { i1 = IntVariable.make(); i2 = IntVariable.make(); Bindings.bijectiveBind(i2, i1); showValues(); i1.setAsInt(10); showValues(); i2.setAsInt(100); showValues(); } private static void showValues() { System.out.println("i1: " + i1.get()); System.out.println("i2: " + i2.get()); } } |
bijectiveBind(i2, i1)在i1 和 i2之间建立两种依赖关系。如果其中一个被更新,例如,调用setAsInt(),其他的值也会变化。为了到达此目的,执行附加了两个监听者在Locations中分享状态。方便的方法makeBijectiveBind()创建一个新的Location并它绑定到现存的绑定上面。如下所示:
i1 = IntVariable.make();
i2 = Bindings.makeBijectiveBind(i1);
BindingDemo2展示了如何使用它。它包含在/today/2009/06/02/sources.zip中。具体细节请参考Resources。图3显示了它的输出。
图3. BindingDemo2输出 |
请注意只有可编译的类型才能使用bijectiveBind()来进行绑定。以下的代码行取自BindingDemo3.java(包含在sources.zip中的)。***眼看上去代码没什么问题。但是,它们会抛出ClassCastException异常。这里发生什么事情了呢?
ObjectLocation loc1 = IntVariable.make();
ObjectLocation loc2 = BooleanVariable.make();
Bindings.bijectiveBind(loc1, loc2);
在创建绑定的过程中,loc2.get()结果被传递到loc1的set()方法中。这个不会为Boolean 和 Integer工作的。为了避免这种问题,只要适当的确定参数泛型类型ObjectLocation。目前为止,我们已经看到两个变量是如何被同步的。以下的小节中将看一看Swing组件是如何被绑定的。
绑定Swing组件
几乎每个JavaFX Script教程都是由显示一个窗口,按钮或是标签的小程序开始的。JavaFX 使用Swing来构建并显示这些组件。因此,我们可以假设Swing融入了JavaFX运行。不久你将会看到,这个也会应用于绑定。
lib/desktop/javafx-swing.jar文件包含javafx.ext.swing包。它的类包含了大多数常见的Swing组件。如果你检查它们,你将会注意到它们用$开始显示各区域。它们的类型是ObjectVariable,它可以执行ObjectLocation。
图4. 在Eclipse的Members视图中SwingLabel |
考虑到这个接口属于com.sun.javafx.runtime.location包,它是安全的假设这样的Locations可以绑定到其他的变量上。以下的程序展示你是如何做到这个的。为了编译并运行这个示例,请添加lib/shared/javafxrt.jar, lib/desktop/javafx-swing.jar, lib/desktop/Scenario.jar, 以及 lib/desktop/javafxgui.jar到你的类路径上。
public class SwingDemo1 {
public static void main(String[] args) { JFrame f = new JFrame(); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JPanel p = new JPanel(new BorderLayout()); f.setContentPane(p); SwingLabel label = new SwingLabel(); ObjectLocation text = Bindings.makeBijectiveBind(label.$text); p.add(label.getJComponent(), BorderLayout.CENTER); f.pack(); f.setVisible(true); text.set("Hello, JavaFX!"); } } |
SwingLabel被实例化并分配到label。
Location被分配到test并绑定到label.的$text上。
标签被有层次的添加到组件上。
请注意你不能直接添加SwingLabel到容器中。相反。它的getJComponent()方法用来获取JComponent实例。
虽然这个简单的例子展示了一个Swing组件如何被绑定到一个变量上的,但是却没有说明使用JavaFX 绑定的好处。在我以前的文章"Binding Beans,"中,我演示了如何使用JGoodies Binding 和 Beans Binding来执行一个简单的音量控制。
VolumeControl示例
音量控制是基于一个简单的特定应用的POJO叫做Volume。它有两个区域:volume 和mute。如图5所示,它通过一个复选框和一个滑块进行操作。标签显示现在的volume值。除此之外,mute控制音量是否调整。
图5. 音量控制示例 |
涉及Swing组件和POJO区域之间的关系如下:
复选框设置mute。
滑块设置volume。
Mute选择或不选择复选框。
Volume设置成滑块的位置。
Mute启用或禁用滑块。
Volume设置标签文本。
完整的来源包含在sources.zip中。细节请参考Resources部分。它的结构很像我以前的文章中的版本,所以很容易比较不同的版本。为了编译并运行VolumeControl,请添加lib/shared/javafxrt.jar, lib/desktop/javafx-swing.jar, lib/desktop/Scenario.jar, 和lib/desktop/javafxgui.jar到你的类路径。
首先,所有相关的组件都要初始化。这个发生在initComponents()中。例如,垂直坏块被创建并有如下设置:
sliderVolume = new SwingSlider();
sliderVolume.$vertical.set(true);
在initEventHandling()中建立绑定。例如,复选框与mute链接,用以下命令Bindings.bijectiveBind(checkboxMute.$selected, volume.mute); 当复选框被选择的时候禁用滑块是通过添加一个监听者到mute上实现的。
volume.mute.addChangeListener(new ChangeListener() {
@Override public boolean onChange() { sliderVolume.$enabled.set(! volume.mute.get()); return true; } }); |
public boolean onChange() {
labelInfo.$text.set(volume.volume.get().toString()); return true; } |
总结
很惊讶的看到JavaFX绑定在Swing应用程序中的使用是如此简单。虽然绑定构架还没有为这个所设计,但是它是一个相当体面的工作。尽管如此,本文还是故意忽视了一些问题:
Sun会允许使用并可能重新分配部分JavaFX运行给非JavaFX应用程序吗?
如何安全使用内部类?这里所描述的包还没有被设计成公共的APIS。
为什么没有成熟的绑定构架给Swing开发者们使用?
即使JavaFX绑定可以或是不能在产品环境中使用,但是对于它的内部研究还是充满乐趣和鼓舞的。
【编辑推荐】