简体   繁体   English

处理JavaFX中的派生属性

[英]handle derived property in JavaFX

I'm playing with JavaFX right now and I'm following a tutorial to learn this sector of Java. 我现在正在使用JavaFX,并且正在关注一个教程来学习Java的这一领域。 I have a question about properties though: 我对属性有疑问:

How to properly deal with derived property in JavaFX? 如何正确处理JavaFX中的派生属性?

Let me clarify with an example. 让我用一个例子来阐明。 Suppose you have a model with a simple property: 假设您有一个具有简单属性的模型:

public class User {

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

(I ignored the constructor and the property getters: suppose they are there) and suppose you have a view with a TableView with 2 columns listing users where: (我忽略了构造函数和属性getter:假设它们在那里),并假设您有一个带有TableView的视图,其中有2列列出了用户,其中:

  • in the first column there is the name of the user; 第一栏中有用户名;
  • in the second column there is the age of the user; 在第二栏中是用户的年龄

Something like this (also displaying some paint skills!): 这样的事情(还显示一些绘画技巧!):

查看范例

Now, the age is of course a derived property of the birthday field and it can be easily implemented via the method: 现在,年龄当然是生日字段的派生属性,可以通过以下方法轻松实现:

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

However the table view won't accept an integer, but only an observable integer. 但是,表视图将不接受整数,而仅接受可观察的整数。 I want the table to change automatically if someone changes the birthday of a user. 如果有人更改用户的生日,我希望表格自动更改。

I could create a SimpleIntegerProperty inside the setCellValueFactory but I don't think that could be a solution. 我可以创建一个SimpleIntegerProperty里面setCellValueFactory ,但我不认为这可能是一个解决方案。 I could also create a IntegerProperty inside User class called age but it doesn't sound right to me because the age is a derived property from birthday. 我还可以在User类中创建一个名为ageIntegerProperty但听起来不对,因为age是生日的派生属性

So that arise my question: how to deal with derived property in JavaFX? 所以这引起了我的问题:如何处理JavaFX中的派生属性?

PS: I looked at this SO answer but, probably for my inexpertise in this field, I couldn't find it satisfactory. PS:我看了这个SO答案,但是,可能由于我在该领域的专业知识,我无法令人满意。

Thanks for any kind reply. 感谢您的任何答复。

You can do it with the mentioned setCellValueFactory and SimpleIntegerProperty : 您可以使用提到的setCellValueFactorySimpleIntegerProperty来做到这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();
});

However, when you create the binding you use the following dependency param.getValue().birthday which is the mark that you know the inner calculation of the getAge() method (okay, for this case it's quite obvious that the calculation is based on the birthday, but there are some cases where it is not). 但是,在创建绑定时,请使用以下依赖项param.getValue().birthday ,这是您知道getAge()方法的内部计算的标记(好的,对于这种情况,很明显,计算基于生日,但在某些情况下并非如此)。

Therefore I would choose the second option that you mentioned to encapsulate the calculation to the place where it belongs: an additional (readonly) property in the User class, where the age is bound to the birthday. 因此,我将选择您提到的第二个选项,将计算封装到它所属的位置: 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(); }
}

and then to use it: 然后使用它:

TableColumn<User, Integer> ageCol = new TableColumn<User, Integer>("Age");
ageCol.setCellValueFactory(new PropertyValueFactory<User, Integer>("age"));

The example uses the ReadOnlyIntegerWrapper (from the answer you have linked) and exposes the inner property age via a ReadOnlyIntegerProperty : 该示例使用ReadOnlyIntegerWrapper (根据您链接的答案),并通过ReadOnlyIntegerProperty公开内部属性的age

public ReadOnlyIntegerProperty ageProperty() { return age.getReadOnlyProperty(); }

Update: The same User class using lazy-initialization on the age property. 更新:age属性上使用延迟初始化的同一User类。

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();
    }
}

You could expose a DoubleBinding instead of a property for the age. 您可以公开DoubleBinding而不是年龄的属性。 A DoubleBinding is an implementation of ObservableValue<Number> (so it can be returned by the callback in a cellValueFactory ), but unlike a DoubleProperty or ReadOnlyDoubleProperty it doesn't store its value internally: DoubleBindingObservableValue<Number>的实现(因此可以由cellValueFactory的回调返回),但是与DoublePropertyReadOnlyDoubleProperty它不内部存储其值:

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 ;
    }
}

You can then do things like 然后您可以做类似的事情

TableColumn<User, Number> ageColumn = new TableColumn<>(age);
ageColumn.setCellValueFactory(cellData -> cellData.getValue().age());

Note this example isn't really ideal, because age can change value without birthday changing value (due to the irrepressible nature of the advance of time...). 请注意,此示例并非真正理想,因为age可以更改值,而birthday不会更改值(由于时间的推移具有不可抑制的性质...)。 So to make this really accurate you might want to create an ObjectProperty<LocalDate> today somewhere, which updated on a daily basis. 因此,要使其真正准确,您可能需要ObjectProperty<LocalDate> today某个地方创建一个ObjectProperty<LocalDate> today ,并每天进行更新。 Then make your age binding update when it changed too 然后,在更改age限制时也进行更新

private final DoubleBinding age = Bindings.createDoubleBinding(
    () -> Period.between(this.getBirthday(), LocalDate.now()).getYears(),
    birthday, today);

Though the original version is likely to be sufficient for most realistic use cases. 尽管原始版本可能足以满足大多数实际用例。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM