将标签 textProperty 绑定到另一个最终 ObjectProperty 持有的对象 属性
Binding labels textProperty to object's property held by another final ObjectProperty
在我正在构建的应用程序中,我在这里使用了 James_D 提供的数据模型:
我可以找到一种方法将标签文本绑定到 DataModel
中保存的对象的 属性
数据结构如下:
模特class学生
//partial class
public class Student {
private final StringProperty displayName = new SimpleStringProperty();
public final StringProperty displayNameProperty(){
return this.displayName;
}
public Student(){
}
public final String getDisplayName() {
return this.displayNameProperty().get();
}
public final void setDisplayName(String displayName) {
this.displayNameProperty().set(displayName);
}
}
学生实例由 StudentDataModel 持有class
public class StudentDataModel {
// complete student list
private final ObservableList<Student> studentList = FXCollections.observableArrayList();
private final ObjectProperty<Student> selectedStudent = new SimpleObjectProperty<>(new Student());
public final Student getSelectedStudent() {
return selectedStudent.get();
}
public final ObjectProperty<Student> selectedStudentProperty() {
return selectedStudent;
}
public final void setSelectedStudent(Student student) {
selectedStudent.set(student);
}
}
StudentList 由 Table 视图显示,有设置 selectedStudent 的更改监听器,如下所示:
public class TableViewController {
public void initModel(StudentDataModel studentDM) {
// ensure model is set once
if (this.studentDM != null) {
throw new IllegalStateException("StudentDataModel can only be initialized once");
}
this.studentDM = studentDM;
tableView.getSelectionModel().selectedItemProperty().addListener((obs, oldSelection, newSelection) -> {
if (newSelection != null) {
studentDM.setSelectedStudent(newSelection);
}
});
}}
还有另一个控制器 ActionsBarController 有标签来显示选定的学生(这似乎是多余的,但有选择多个对象来执行批量操作的选项)。
StudentDataModel 已正确初始化(我可以在调试器中看到它)但下面没有执行任何操作:
chosenStudentLabel.textProperty().bind(studentDM.getSelectedStudent().displayNameProperty());
//this shows class name with instance number changing correctly
chosenStudentLabel.textProperty().bind(studentDM.selectedStudentProperty().asString());
我可以将 ActionsBarController 注入 TableViewController 并从那里的更改监听器更改标签文本,但这似乎与数据模型适得其反。
我做错了什么?
您的代码不起作用,因为您在创建绑定时(即初始化模型时)调用(并计算)getSelectedStudent()
。因此,您只绑定到当时选择的学生的 displayName
属性。 (如果未选择任何内容,您将获得 NullPointerException
。)绑定只会在最初选择的学生的显示名称发生变化时发生变化;如果选择更改,它不会更改。
您需要一个绑定,当所选学生更改时,该绑定可与旧所选学生的显示名称解除绑定,并绑定到新所选学生的显示名称。一种方法是:
chosenStudentLabel.textProperty().bind(new StringBinding() {
{
studentDM.selectedStudentProperty().addListener((obs, oldStudent, newStudent) -> {
if (oldStudent != null) {
unbind(oldStudent.displayNameProperty());
}
if (newStudent != null) {
bind(newStudent.displayNameProperty());
}
invalidate();
});
}
@Override
protected String computeValue() {
if (studentDM.getSelectedStudent() == null) {
return "" ;
}
return studentDM.getSelectedStudent().getDisplayName();
}
});
请注意,还有一种“内置”方法可以执行此操作,但由于几个原因,它有点不能令人满意(在我看来)。首先,它依赖于将“嵌套 属性”的名称指定为 String
,使用反射来访问它。这是不可取的,因为它无法在编译时检查 属性 是否存在,它需要打开模块进行访问,而且在性能方面不太好。其次,如果“链”中的一个属性为空(例如,在这种情况下,如果所选学生为空,这将是最初的情况),它会发出虚假警告,即使根据文档这是受支持的情况。但是,代码明显少了:
chosenStudentLabel.textProperty().bind(
Bindings.selectString(studentDM.selectedStudentProperty(), "displayName")
);
在我正在构建的应用程序中,我在这里使用了 James_D 提供的数据模型:
我可以找到一种方法将标签文本绑定到 DataModel
中保存的对象的 属性数据结构如下: 模特class学生
//partial class
public class Student {
private final StringProperty displayName = new SimpleStringProperty();
public final StringProperty displayNameProperty(){
return this.displayName;
}
public Student(){
}
public final String getDisplayName() {
return this.displayNameProperty().get();
}
public final void setDisplayName(String displayName) {
this.displayNameProperty().set(displayName);
}
}
学生实例由 StudentDataModel 持有class
public class StudentDataModel {
// complete student list
private final ObservableList<Student> studentList = FXCollections.observableArrayList();
private final ObjectProperty<Student> selectedStudent = new SimpleObjectProperty<>(new Student());
public final Student getSelectedStudent() {
return selectedStudent.get();
}
public final ObjectProperty<Student> selectedStudentProperty() {
return selectedStudent;
}
public final void setSelectedStudent(Student student) {
selectedStudent.set(student);
}
}
StudentList 由 Table 视图显示,有设置 selectedStudent 的更改监听器,如下所示:
public class TableViewController {
public void initModel(StudentDataModel studentDM) {
// ensure model is set once
if (this.studentDM != null) {
throw new IllegalStateException("StudentDataModel can only be initialized once");
}
this.studentDM = studentDM;
tableView.getSelectionModel().selectedItemProperty().addListener((obs, oldSelection, newSelection) -> {
if (newSelection != null) {
studentDM.setSelectedStudent(newSelection);
}
});
}}
还有另一个控制器 ActionsBarController 有标签来显示选定的学生(这似乎是多余的,但有选择多个对象来执行批量操作的选项)。 StudentDataModel 已正确初始化(我可以在调试器中看到它)但下面没有执行任何操作:
chosenStudentLabel.textProperty().bind(studentDM.getSelectedStudent().displayNameProperty());
//this shows class name with instance number changing correctly
chosenStudentLabel.textProperty().bind(studentDM.selectedStudentProperty().asString());
我可以将 ActionsBarController 注入 TableViewController 并从那里的更改监听器更改标签文本,但这似乎与数据模型适得其反。 我做错了什么?
您的代码不起作用,因为您在创建绑定时(即初始化模型时)调用(并计算)getSelectedStudent()
。因此,您只绑定到当时选择的学生的 displayName
属性。 (如果未选择任何内容,您将获得 NullPointerException
。)绑定只会在最初选择的学生的显示名称发生变化时发生变化;如果选择更改,它不会更改。
您需要一个绑定,当所选学生更改时,该绑定可与旧所选学生的显示名称解除绑定,并绑定到新所选学生的显示名称。一种方法是:
chosenStudentLabel.textProperty().bind(new StringBinding() {
{
studentDM.selectedStudentProperty().addListener((obs, oldStudent, newStudent) -> {
if (oldStudent != null) {
unbind(oldStudent.displayNameProperty());
}
if (newStudent != null) {
bind(newStudent.displayNameProperty());
}
invalidate();
});
}
@Override
protected String computeValue() {
if (studentDM.getSelectedStudent() == null) {
return "" ;
}
return studentDM.getSelectedStudent().getDisplayName();
}
});
请注意,还有一种“内置”方法可以执行此操作,但由于几个原因,它有点不能令人满意(在我看来)。首先,它依赖于将“嵌套 属性”的名称指定为 String
,使用反射来访问它。这是不可取的,因为它无法在编译时检查 属性 是否存在,它需要打开模块进行访问,而且在性能方面不太好。其次,如果“链”中的一个属性为空(例如,在这种情况下,如果所选学生为空,这将是最初的情况),它会发出虚假警告,即使根据文档这是受支持的情况。但是,代码明显少了:
chosenStudentLabel.textProperty().bind(
Bindings.selectString(studentDM.selectedStudentProperty(), "displayName")
);