JavaBean扩展
为了实现基于JavaBean的属性、绑定以及事件机制,JavaFX对JavaBean进行了扩展,JavaBean不再是POJO,显得更加“重量级”一些。
JavaFX属性
JavaFX为Java通用包装类提供了通用的属性包装类,以实现事件监听、数据绑定等功能。如下表:
所有这些类都是Observable接口的实现类,以上只是示例了部分,详细类见javafx.beans.property包下。
JavaFX中的Property体系没有实现Serializable接口,所以无法跨JVM传递。也就是说,应用服务器无法向客户端返回这类信息;客户端也无法将这类信息传递给应用服务器。这给我们开发B/S应用系统造成了困扰。
由于需要进行值监听等操作,所以JavaFX的Property对空属性比较敏感,我们在写代码时要特别关注。
JavaFX Property对空属性的敏感
如果Property没有初始化,则对其操作时会报出空指针。
package com.lirong.javafx.demo.j1003; import javafx.beans.property.StringProperty; public class DemoFXBean { /** * 编码 */ private StringProperty test_code; /** * 名称 */ private StringProperty test_name; public String getTest_code() { return test_code.get(); } public StringProperty test_codeProperty() { return test_code; } public void setTest_code(String test_code) { this.test_code.set(test_code); } public String getTest_name() { return test_name.get(); } public StringProperty test_nameProperty() { return test_name; } public void setTest_name(String test_name) { this.test_name.set(test_name); } }
我们可以看到,JavaFX Bean中,每个属性都会有三个方法:
1、getter值的方法,如getTest_code();
2、setter值的方法,如setTest_code(String test_code);
3、获取属性的方法,如test_codeProperty();
当我们使用以下代码进行测试时:
package com.lirong.javafx.demo.j1003; public class TestFXBean { public static void main(String[] args) { DemoFXBean demoFXBean = new DemoFXBean(); demoFXBean.setTest_code("TestCode"); } }
控制台打印如下错误信息:
从DemoFXBean的代码可以看出,对属性操作的三个方法,都是基于Property进行的。所以如果不初始化Property,get、set时,就会发生空指针异常。
一个简单的JavaFX Bean
当Bean中某个字段值发生变化时,打印信息并修改其它值。我们把DemoFXBean修改一下:
package com.lirong.javafx.demo.j1003; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; public class DemoFXBean { /** * 编码 */ private StringProperty test_code = new SimpleStringProperty(); /** * 名称 */ private StringProperty test_name = new SimpleStringProperty(); public DemoFXBean() { super(); initListener(); } private void initListener() { // 监听test_code属性的变化,同时修改其它属性值 test_codeProperty().addListener((observable, oldValue, newValue) -> { System.out.println(String.format("test_code changed. oldValue=%s, newValue=%s", oldValue, newValue)); // 设置其它属性值 setTest_name(String.format("%s 的名称", newValue)); }); // 监听test_name属性的变化 test_nameProperty().addListener((observable, oldValue, newValue) -> { System.out.println(String.format("test_name changed. oldValue=%s, newValue=%s", oldValue, newValue)); }); } public String getTest_code() { return test_code.get(); } public StringProperty test_codeProperty() { return test_code; } public void setTest_code(String test_code) { this.test_code.set(test_code); } public String getTest_name() { return test_name.get(); } public StringProperty test_nameProperty() { return test_name; } public void setTest_name(String test_name) { this.test_name.set(test_name); } }
主要修改了以下内容:
1、初始化Property;
2、增加监听器;
再运行TestFXBean测试类时,控制台打印如下信息:
可以看到,两个监听器都产生了预期的动作。
JavaBean的属性监听机制
如前所述,JavaFX的Property由于没有实现序列化,所以无法跨JVM传递。所以我们要有一种JavaFX Bean和JavaBean之间的包装、转换机制,使Bean能够跨JVM。这种机制是我们后续在B/S框架中使用JavaFX的必要准备。
我们的方法是:改造javaBean,通过改造后的JavaBean包装生成JavaFX Bean,并使JavaBean和JavaFX Bean之间的属性实现双向联动。
增加一个DemoJavaBean,为其实现属性监听:
package com.lirong.javafx.demo.j1003; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.io.Serializable; public class DemoJavaBean implements Serializable { private static final long serialVersionUID = -6499105304636177551L; protected final PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this); /** * 编码 */ private String test_code; /** * 名称 */ private String test_name; public String getTest_code() { return test_code; } public void setTest_code(String test_code) { final String oldValue = this.test_code; this.test_code = test_code; propertyChangeSupport.firePropertyChange("test_code", oldValue, this.test_code); } public String getTest_name() { return test_name; } public void setTest_name(String test_name) { final String oldValue = this.test_name; this.test_name = test_name; propertyChangeSupport.firePropertyChange("test_name", oldValue, this.test_name); } public void addPropertyChangeListener(PropertyChangeListener listener) { propertyChangeSupport.addPropertyChangeListener(listener); } public void removePropertyChangeListener(PropertyChangeListener listener) { propertyChangeSupport.removePropertyChangeListener(listener); } }
需要注意的是:
1、为DemoJavaBean增加了PropertyChangeSupport属性,并增加了两个相关方法addPropertyChangeListener和removePropertyChangeListener;
2、在setter中,产生属性变化事件,并传递修改前、修改后的值;
3、该类实现了序列化接口;
增加DemoJavaFXBean类:
package com.lirong.javafx.demo.j1003; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; import javafx.beans.property.adapter.JavaBeanStringPropertyBuilder; public class DemoJavaFXBean { private DemoJavaBean javaBean; /** * 编码 */ private StringProperty test_code = new SimpleStringProperty(); /** * 名称 */ private StringProperty test_name = new SimpleStringProperty(); /** * DemoJavaFXBean的构造器,必须传入一个DemoJavaBean,用于初始化属性 * * @param javaBean */ public DemoJavaFXBean(DemoJavaBean javaBean) { super(); this.javaBean = javaBean; initProperty(); } /** * 通过 DemoJavaBean 初始化 DemoJavaFXBean 的属性 */ private void initProperty() { // 根据JavaBean初始化JavaFX Bean的属性 try { /* 编码 */ test_code = JavaBeanStringPropertyBuilder.create().bean(this.javaBean).name("test_code").build(); /* 名称 */ test_name = JavaBeanStringPropertyBuilder.create().bean(this.javaBean).name("test_name").build(); } catch (Exception ex) { throw new RuntimeException(ex); } } public DemoJavaBean getJavaBean() { return javaBean; } public void setJavaBean(DemoJavaBean javaBean) { this.javaBean = javaBean; // 设置JavaBean后,需要重新初始化JavaFX Bean initProperty(); } public String getTest_code() { return test_code.get(); } public StringProperty test_codeProperty() { return test_code; } public void setTest_code(String test_code) { this.test_code.set(test_code); } public String getTest_name() { return test_name.get(); } public StringProperty test_nameProperty() { return test_name; } public void setTest_name(String test_name) { this.test_name.set(test_name); } }
需要注意的是:
1、DemoJavaFXBean的构造器,授受一个DemoJavaBean,以进行本类的属性初始化;
2、通过initProperty方法进行属性初始化;
3、该类没有实现序列化接口;
测试类TestJavaBeanFX,主要用于测试属性修改是否在JavaFX Bean和JavaBean之间自动联动:
package com.lirong.javafx.demo.j1003; public class TestJavaBeanFX { public static void main(String[] args) { DemoJavaBean demoJavaBean = new DemoJavaBean(); DemoJavaFXBean demoJavaFXBean = new DemoJavaFXBean(demoJavaBean); final String strFormatter = "JavaFX Bean PropertyName=%s, PropertyValue=%s; Java Bean PropertyName=%s, PropertyValue=%s"; // 修改Java Bean的属性,查看JavaFX Bean相应的属性是否同步修改了 demoJavaFXBean.getJavaBean().setTest_code("TestCode"); System.out.println(String.format(strFormatter, "Code", demoJavaFXBean.getTest_code(), "Code", demoJavaFXBean.getJavaBean().getTest_code())); // 修改JavaFX Bean的属性,查看Java Bean相应的属性是否同步修改了 demoJavaFXBean.setTest_name("TestName"); System.out.println(String.format(strFormatter, "Name", demoJavaFXBean.getTest_name(), "Name", demoJavaFXBean.getJavaBean().getTest_name())); } }
控制台输出以下信息:
我们可以看到,修改JavaFX Bean或Java Bean的属性值,将同步两个Bean的属性值。
需要注意的是:
1、必须为JavaBean实现属性变化监听事件机制;
2、通过JavaBean构造JavaFX Bean,并实现属性初始化;
通过本例也可以进一步看出:在JavaFX属性机制中,属性不可为空,属性值可以为空。我们在TestJavaBeanFX类中实例化DemoJavaBean时,没有为它的任何属性赋值。但在初始化DemoJavaFXBean时,通过JavaFX的PropertyBuilder产生属性值为空的非空属性。