JavaFx 属性绑定

发布于 2024-12-18 00:43:07 字数 9934 浏览 28 评论 0

javafx.beans.property 包提供了丰富的 Property 类用于对 java 对象中的属性和 javaFX UI 控件进行绑定,可达到:

  • 在任意位置修改 java 对象的值时可实时更新绑定的 UI 控件的显示,无需手动编写更新方法
  • javaFX UI 控件进行变化时(如用户输入内容),实时写入对应的 java 对象中,无需手动设置
java 属性javaFx 对应 Property绑定构建类
intIntegerPropertyJavaBeanIntegerPropertyBuilder
IntegerObjectProperty<Integer>JavaBeanObjectPropertyBuilder
longLongPropertyJavaBeanLongPropertyBuilder
LongObjectProperty<Long>JavaBeanObjectPropertyBuilder
StringStringPropertyJavaBeanStringPropertyBuilder

需要注意的是,如果 java 对象中的属性为基本数据类型的包装类,则不能直接使用对应的 Property,需使用 ObjectProperty, 否则在属性值为 null 时会报空指针异常(原因是其内部都是使用基本数据类型转换的,感兴趣可以看看源码中的实现)

实现

下面我们就来看看如何实现的

新建简单 javaBean

public class MyJavaBean implements Serializable {

private int myInt;

private Integer myInteger;

private String myString;

public int getMyInt() {
return myInt;
}

public void setMyInt(int myInt) {
this.myInt = myInt;
}

public Integer getMyInteger() {
return myInteger;
}

public void setMyInteger(Integer myInteger) {
this.myInteger = myInteger;
}


public String getMyString() {
return myString;
}

public void setMyString(String myString) {
this.myString = myString;
}

@Override
public String toString() {
return "MyJavaBean{" +
"myInt=" + myInt +
", myInteger=" + myInteger +
", myString='" + myString + '\'' +
'}';
}
}

新建 javaFxWrapper 类,用于实现 javaBean 与 javaFx UI 控件的属性绑定

重点在于 JavaBeanObjectPropertyBuilder 等的使用

public class MyJavaBeanFxWrapper {

private MyJavaBean myJavaBean;

private IntegerProperty myInt;

private ObjectProperty<Integer> myInteger;

private StringProperty myString;

public MyJavaBeanFxWrapper(MyJavaBean myJavaBean) throws NoSuchMethodException {
this.myJavaBean = myJavaBean;
intProperty();
}

private MyJavaBeanFxWrapper() {
}

@SuppressWarnings({"unchecked"})
private void intProperty() throws NoSuchMethodException {

myInt = JavaBeanIntegerPropertyBuilder.create().bean(this.myJavaBean).name("myInt").build();

//IntegerProperty 如果遇到 bean 中属性值为 null 时,初始化会报错(内部使用 int,转换出错),故需使用 ObjectProperty<Integer>
myInteger = JavaBeanObjectPropertyBuilder.create().bean(this.myJavaBean).name("myInteger").build();

myString = JavaBeanStringPropertyBuilder.create().bean(this.myJavaBean).name("myString").build();
}

public MyJavaBean getMyJavaBean() {
return myJavaBean;
}

public void setMyJavaBean(MyJavaBean myJavaBean) throws NoSuchMethodException {
this.myJavaBean = myJavaBean;
intProperty();
}

public int getMyInt() {
return myInt.get();
}

public IntegerProperty myIntProperty() {
return myInt;
}

public Integer getMyInteger() {
return myInteger.get();
}

public ObjectProperty<Integer> myIntegerProperty() {
return myInteger;
}


public String getMyString() {
return myString.get();
}

public StringProperty myStringProperty() {
return myString;
}

}

一个比较好的习惯就是两个对象中的属性命名尽量保持一致,可以减少因属性名不一致造成的绑定失败的概率

Controller 中进行绑定

public class Controller implements Initializable {

public Label intLabel, integerLabel, stringLabel;

public TextField intTextField, integerTextField, stringTextField;

private MyJavaBeanFxWrapper myJavaBeanFxWrapper;
private MyJavaBean myJavaBean;

@Override
public void initialize(URL location, ResourceBundle resources) {
try {
//定义 java 对象
myJavaBean = new MyJavaBean();

//将 java 对象设置到 javafx 辅助类中,以支持属性绑定
myJavaBeanFxWrapper = new MyJavaBeanFxWrapper(myJavaBean);

//控件只做显示, 单向绑定即可
intLabel.textProperty().bind(myJavaBeanFxWrapper.myIntProperty().asString());
integerLabel.textProperty().bind(myJavaBeanFxWrapper.myIntegerProperty().asString());
stringLabel.textProperty().bind(myJavaBeanFxWrapper.myStringProperty());

//控件可编辑,双向绑定
intTextField.textProperty().bindBidirectional(myJavaBeanFxWrapper.myIntProperty(), new StringConverter<>() {
@Override
public String toString(Number object) {
return String.valueOf(object);
}

@Override
public Number fromString(String string) {
return Integer.valueOf(string);
}
});

integerTextField.textProperty().bindBidirectional(myJavaBeanFxWrapper.myIntegerProperty(), new StringConverter<>() {
@Override
public String toString(Integer object) {
return String.valueOf(object);
}

@Override
public Integer fromString(String string) {
if("".equals(string)) {
return null;
}
return Integer.valueOf(string);
}
});

stringTextField.textProperty().bindBidirectional(myJavaBeanFxWrapper.myStringProperty());
} catch (Exception e) {
e.printStackTrace();
}
}

...
}

以上就达到了在不破坏原有 javabean 的基础上通过新建一个 javaFx 的辅助类,即可实现 UI 与数据对象的绑定,大大方便了 javaFx 的的日常开发

日常开发时建议将 java 对象设置到 javaFx 辅助类中后,就直接使用 javaFx 的辅助类进行操作,只有最后需要保存或者传递时才获取 java 原始对象

list 的绑定

javaFx 中承载 list 数据的控件一般为 listView,下面我们就演示下如何进行 list 的绑定

javabean 中新增 list 属性

public class MyJavaBean implements Serializable { 

...

private List<String> myStringList;

//省略 getter/setter

...
}

javaFxWrapper 类中新增对应绑定

javaFx 提供了 ObservableList 以实现 list 数据的动态监听,而 UI 控件也直接与这个 ObservableList 进行交互

public class MyJavaBeanFxWrapper { 
...
private ListProperty<String> myStringList;

@SuppressWarnings({"unchecked"})
private void intProperty() throws NoSuchMethodException {
...
myStringList = new SimpleListProperty<>(FXCollections.observableArrayList());
if(myJavaBean.getMyStringList() != null) {
myStringList.addAll(myJavaBean.getMyStringList());
}

//这里需手动设置到原始对象中(暂时没找到自动绑定的方法)
//除非原始 javabean 中的 list 属性为 ObservableList 类型,才可以直接使用
//myStringList = new SimpleListProperty<>(this.myJavaBean, "myStringList", myJavaBean.getMyStringList());
//但如此 javabean 中就会引入 javafx 相关包,当然不介意这个的就可以改写使用
myStringList.addListener((observable, oldValue, newValue) -> myJavaBean.setMyStringList(newValue));
}

public ObservableList<String> getMyStringList() {
return myStringList.get();
}

public ListProperty<String> myStringListProperty() {
return myStringList;
}

...
}

Controller 中进行绑定

public class Controller implements Initializable { 

...
public ListView<String> listView;

@Override
public void initialize(URL location, ResourceBundle resources) {
try {
//有关 UI 上的操作尽量都使用 myJavaBeanFxWrapper 来修改数据
myJavaBeanFxWrapper.myStringListProperty().addAll("123", "456", "789");
myJavaBeanFxWrapper.getMyStringList().addAll("www", "eee", "rrr");
listView.itemsProperty().bind(myJavaBeanFxWrapper.myStringListProperty());
} catch (Exception e) {
e.printStackTrace();
}
}

}

当然以上做法有一个注意要点,即 javabean 在初始化后就不能自己通过 setMyStringList 来同步 UI 了(除非直接使用的 ObservableList),尽量都使用 myJavaBeanFxWrapper 或 listView 的 getItems() 进行操作,当 javabean 因为外在因素,如从网络上重新加载了,需重新设置到 myJavaBeanFxWrapper

自定义对象绑定

javabean 中除了基本数据类型及其包装类、String、list(集合) 外,还可能会有自定义的对象属性,使用 ObjectProperty 即可, 但这种做法的颗粒度不太细,推荐还是新增对应自定义子对象的 Fxwrapper,并在 javabean 变化时进行绑定初始化

示例代码使用的是 java14+javafx14, github-javafx-property-test , 如果还是使用的 javafx8 则不需要 pom.xml 中对于 openjfx 的引用

属性绑定常用方法

  • bind(): 单向绑定
  • bindBidirectional(): 双向绑定
  • addListener():添加监听

The ObservableValue stores a strong reference to the listener which will prevent the listener from being garbage collected and may result in a memory leak. It is recommended to either unregister a listener by calling removeListener after use or to use an instance of WeakChangeListener avoid this situation.它是一个强引用的 Listener,有可能会引起内存泄露,推荐的解决办法是使用完之后调用 removeListener,或者使用 WeakChangeListener 这个弱引用的监听。

styleTypeToggleGroup.selectedToggleProperty().addListener(new WeakChangeListener<>((observable, oldValue, newValue) -> {
updateView();
}));

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

卷耳

暂无简介

文章
评论
26 人气
更多

推荐作者

櫻之舞

文章 0 评论 0

弥枳

文章 0 评论 0

m2429

文章 0 评论 0

野却迷人

文章 0 评论 0

我怀念的。

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文