我正在玩JavaFX,我正在跟随tutorial学习Java的这个部分。我对性能有疑问:
如何在JavaFX中正确处理派生属性?
让我用一个例子来澄清。假设您有一个具有简单属性的模型:
public class User {
private final StringProperty name;
private final ObjectProperty<LocalDate> birthday;
}
(我忽略了构造函数和属性getter:假设它们在那里)并且假设你有一个带有2列TableView的视图,其中列出了用户:
像这样的东西(也展示了一些绘画技巧!):
现在,年龄当然是生日字段的派生属性,可以通过以下方法轻松实现:
public int getAge() {
return Period.between(this.getBirthday().get(), LocalDate.now()).getYears();
}
但是表视图不接受整数,而只接受可观察的整数。如果有人更改了用户的生日,我希望表格自动更改。
我可以在SimpleIntegerProperty
内创建一个setCellValueFactory
,但我认为这不是一个解决方案。我还可以在名为IntegerProperty
的{{1}}课程中创建一个User
,但这对我来说听起来并不正确,因为年龄是来自生日的派生属性。 / p>
这就出现了我的问题:如何在JavaFX中处理派生属性?
PS:我看过这个SO answer但是,可能因为我在这个领域的不熟练,我觉得不能令人满意。感谢您的回复。
答案 0 :(得分:1)
您可以使用上述setCellValueFactory
和SimpleIntegerProperty
:
TableColumn<User, Integer> ageCol = new TableColumn<User, Integer>("Age");
ageCol.setCellValueFactory(param -> {
SimpleIntegerProperty prop = new SimpleIntegerProperty(param.getValue().getAge());
prop.bind(Bindings.createIntegerBinding(() -> {
return param.getValue().getAge();
}, param.getValue().birthday));
return prop.asObject();
});
但是,当您创建绑定时,使用以下依赖项param.getValue().birthday
,这是您知道getAge()
方法的内部计算的标记(好吧,对于这种情况,它是相当的很明显,计算是基于生日,但有些情况下,它不是。)
因此,我会选择您提到的第二个选项将计算封装到它所属的位置:User
类中的附加(只读)属性,其中年龄绑定到生日。
public static class User {
private final StringProperty name;
private final ObjectProperty<LocalDate> birthday;
private final ReadOnlyIntegerWrapper age;
public StringProperty nameProperty() { return name; }
public ObjectProperty<LocalDate> birthdayProperty() { return birthday; }
public ReadOnlyIntegerProperty ageProperty() { return age.getReadOnlyProperty(); }
private User(String name, LocalDate bDay) {
this.name = new SimpleStringProperty(name);
birthday = new SimpleObjectProperty<LocalDate>(bDay);
this.age = new ReadOnlyIntegerWrapper();
age.bind(Bindings.createIntegerBinding(() -> Period.between(this.getBirthday(), LocalDate.now()).getYears(), birthday));
}
public String getName() { return name.get(); }
public LocalDate getBirthday() { return birthday.get(); }
public int getAge() { return ageProperty().get(); }
}
然后使用它:
TableColumn<User, Integer> ageCol = new TableColumn<User, Integer>("Age");
ageCol.setCellValueFactory(new PropertyValueFactory<User, Integer>("age"));
该示例使用ReadOnlyIntegerWrapper
(来自您已链接的答案)并通过age
公开内部属性ReadOnlyIntegerProperty
:
public ReadOnlyIntegerProperty ageProperty() { return age.getReadOnlyProperty(); }
更新:使用User
属性上的延迟初始化的同一age
类。
public static class User {
private final StringProperty name;
private final ObjectProperty<LocalDate> birthday;
private ReadOnlyIntegerWrapper age = null;
public StringProperty nameProperty() { return name; }
public ObjectProperty<LocalDate> birthdayProperty() { return birthday; }
public ReadOnlyIntegerProperty ageProperty() {
if (age == null) {
age = new ReadOnlyIntegerWrapper();
age.bind(Bindings.createIntegerBinding(() -> calculateAge(), birthday));
}
return age.getReadOnlyProperty();
}
private User(String name, LocalDate bDay) {
this.name = new SimpleStringProperty(name);
birthday = new SimpleObjectProperty<LocalDate>(bDay);
}
public String getName() { return name.get(); }
public LocalDate getBirthday() {return birthday.get(); }
public int getAge() {
return (age != null) ? age.get() : calculateAge();
}
private final int calculateAge() {
return Period.between(this.getBirthday(), LocalDate.now()).getYears();
}
}
答案 1 :(得分:0)
您可以展示DoubleBinding
而不是该年龄的属性。 DoubleBinding
是ObservableValue<Number>
的实现(因此可以通过cellValueFactory
中的回调返回),但不像DoubleProperty
或ReadOnlyDoubleProperty
它不会&# 39; t在内部存储它的值:
public class User {
private final ObjectProperty<LocalDate> birthday ;
public ObjectProperty<LocalDate> birthdayProperty() {
return birthday ;
}
public final LocalDate getBirthday() {
return birthdayProperty().get();
}
public final void setBirthday(LocalDate birthday) {
birthdayProperty().set(birthday);
}
private final DoubleBinding age = Bindings.createDoubleBinding(
() -> Period.between(this.getBirthday(), LocalDate.now()).getYears(),
birthday);
public DoubleBinding age() {
return age ;
}
}
然后您可以执行
之类的操作TableColumn<User, Number> ageColumn = new TableColumn<>(age);
ageColumn.setCellValueFactory(cellData -> cellData.getValue().age());
请注意,此示例并非真正理想,因为age
可以在不birthday
更改值的情况下更改值(由于时间推移的无法抑制的性质......)。因此,为了使其真正准确,您可能希望在某处创建ObjectProperty<LocalDate> today
,每天更新。然后在更改时进行age
绑定更新
private final DoubleBinding age = Bindings.createDoubleBinding(
() -> Period.between(this.getBirthday(), LocalDate.now()).getYears(),
birthday, today);
虽然原始版本可能足以满足大多数实际用例。