处理JavaFX中的派生属性

时间:2016-12-07 10:59:12

标签: java javafx data-binding javafx-8

我正在玩JavaFX,我正在跟随tutorial学习Java的这个部分。我对性能有疑问:

如何在JavaFX中正确处理派生属性?

让我用一个例子来澄清。假设您有一个具有简单属性的模型:

public class User {

    private final StringProperty name;
    private final ObjectProperty<LocalDate> birthday;
}

(我忽略了构造函数和属性getter:假设它们在那里)并且假设你有一个带有2列TableView的视图,其中列出了用户:

  • 在第一列中有用户名;
  • 在第二列中有用户的年龄;

像这样的东西(也展示了一些绘画技巧!):

View example

现在,年龄当然是生日字段的派生属性,可以通过以下方法轻松实现:

public int getAge() {
    return Period.between(this.getBirthday().get(), LocalDate.now()).getYears();
}

但是表视图不接受整数,而只接受可观察的整数。如果有人更改了用户的生日,我希望表格自动更改。

我可以在SimpleIntegerProperty内创建一个setCellValueFactory,但我认为这不是一个解决方案。我还可以在名为IntegerProperty的{​​{1}}课程中创建一个User,但这对我来说听起来并不正确,因为年龄是来自生日的派生属性。 / p>

这就出现了我的问题:如何在JavaFX中处理派生属性?

PS:我看过这个SO answer但是,可能因为我在这个领域的不熟练,我觉得不能令人满意。

感谢您的回复。

2 个答案:

答案 0 :(得分:1)

您可以使用上述setCellValueFactorySimpleIntegerProperty

来完成此操作
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而不是该年龄的属性。 DoubleBindingObservableValue<Number>的实现(因此可以通过cellValueFactory中的回调返回),但不像DoublePropertyReadOnlyDoubleProperty它不会&# 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);

虽然原始版本可能足以满足大多数实际用例。