JavaFX绑定制定者大会

时间:2014-10-21 21:51:49

标签: binding javafx javabeans

我知道Java和C#,但属性绑定我只知道来自C#MVVM。 我试图理解JavaFX中的属性绑定与自定义getter和属性值的setter(如在C#中)。

我创建了以下类:

public class ViewModel {

    private StringProperty name;

    public ViewModel() {
        name = new SimpleStringProperty();
    }

    public final String getName() {
        return name.get();
    }

    public final void setName(String name) {
        this.name.set(name);
    }

    public StringProperty getNameProperty() {
        return name;
    }
}

public class Controller implements Initializable {

    @FXML
    private TextField nameField;

    private final ViewModel viewModel;

    public Controller() {
        viewModel = new ViewModel();
    }

    @Override
    public void initialize(URL url, ResourceBundle resourceBundle) {
        Bindings.bindBidirectional(nameField.textProperty(),
                viewModel.getNameProperty());
    }
}

我的印象是,如果我遵循推荐的JavaBean / JavaFX命名约定,那么绑定系统将足够聪明以使用反射(?)并使用我的自定义getter / setter作为属性。但我的视图模型getter / setter从未使用过。

相反,绑定使用属性'直接获取/设置方法而无需我的交互。 我读到我可以使用以下代码,但必须有一个比这更好的方法:

name = new SimpleStringProperty() {
    @Override public void set(String value) {
        // do something...
        super.set(value);
    }
};

我可以指定绑定应该使用哪些方法来获取/设置我的属性吗? 另外,如何通知C属性已更改(C#中的NotifyOfPropertyChange())而不必更改它?

修改

我想要做的是对于最终设置到我的属性的内容有一点选择性,因为我想稍后使用它的值来填充我的数据模型(这里省略) )。

在C#中,这是微不足道的,我只是在setter中设置了一个谓词。而且我可以通过设置其他属性来推动表单/向导的进度。

public String Property {
    get { return _property; }
    set { 
        if(SomePredicate(value)) {
            _property = value;
            _nextButtonCommand.canExecute() = true;
            // notify...
        }
    }
}

1 个答案:

答案 0 :(得分:3)

JavaFX属性

name类中定义ViewModel属性的约定是:

public class ViewModel {

    private StringProperty name;

    public ViewModel() {
        // parameters are owning bean, property name, and initial value, 
        // and are optional for the property convention
        name = new SimpleStringProperty(this, "name", "");
    }

    public final String getName() {
        return name.get();
    }

    public final void setName(String name) {
        this.name.set(name);
    }

    // Note this method name:
    public final StringProperty nameProperty() {
        return name;
    }
}

有一些变体:特别是如果你想让事情变得可以覆盖,你可以从final移除nameProperty()修饰符,并在获取中用this.name替换this.nameProperty()并设置方法。关键是要确保调用setName(...)始终提供与nameProperty().set(...)相同的结果(对于get方法也是如此)。

我认为你的一般解释在某种程度上是一个等级(如果这是有道理的)。 StringProperty类定义了getset方法,这些方法将因为绑定而“自动”为您调用。所以(即使你有一个稍微非标准的命名约定),在文本字段中输入仍然会更新属性值。据我所知,绑定API不使用(多?)反射 - 它只是向侦听器注册属性,并在另一个更改时更新。

对于自定义getter和setter,这些实际上仅通过您展示的重写技术支持属性类。我从来没有真正找到一个很好的用例,特别是使用绑定API,这使您有机会很容易地创建依赖值。

<强>更新

因此,对于您的具体示例,我将按以下方式实现它:

public class ViewModel {
    private StringProperty name = new SimpleStringProperty(this, "name");
    // usual JavaFX Property methods...
}

然后,无论你需要它:

Predicate<String> predicate = ... ;
BooleanBinding canExecute = Bindings.createBooleanBinding(() -> 
    predicate.test(viewModel.getName()),
    viewModel.nameProperty());

然后你可以做像

这样的事情
Button nextButton = new Button("Next");
nextButton.disableProperty().bind(canExecute.not());

如果谓词可能会改变,你甚至可以

ObjectProperty<Predicate<String>> predicate = new SimpleObjectProperty<>(s -> true);
BooleanBinding canExecute = Bindings.createBooleanBinding(() -> 
    predicate.get().test(viewModel.getName()),
    predicate, viewModel.nameProperty());

Bindings class中有许多工厂方法用于创建绑定,如果需要,您还可以将抽象ObjectBindingStringBinding等类子类化。

请注意,这种方法与问题中提出的方法之间存在着微妙的变化:在问题中的方法(子类化SimpleStringProperty)中,确定操作是否可执行的逻辑由字符串属性。在这种方法中,它被分解为一个不同的对象,它观察字符串属性(通过绑定,实际上在字符串属性上注册WeakInvalidationListener)。

<强>通知

您可以向InvalidationListenersChangeListeners注册一个属性。 addListener(InvalidationListener)方法继承自Observable,表示上次观察到的值可能不再有效。这允许“延迟评估”可观察量,其仅在请求时计算新值。 addListener(ChangeListener)方法继承自ObservableValue,其(如其名称所示)是一个显式包装值的可观察对象。注册ChangeListener强制执行评估,因为侦听器会收到新值的通知。虽然它确实为高性能的实现提供了很多很多的灵活性,但API对我来说有点过于细微。

所以在你的测试应用中,你可以做到

viewModel.nameProperty().addListener(new ChangeListener<String>() {
    @Override
    public void changed(ObservableValue<? extends String> obs, String oldValue, String newValue) {
        System.out.println("Name changed from "+oldValue+" to "+newValue);
    }
});

或者,在Java 8中,更加简洁

viewModel.nameProperty().addListener((obs, oldName, newName) -> 
    System.out.println("Name changed from "+oldValue+" to "+newValue));

如果您的ObservableValue依赖于计算,则失效侦听器和更改侦听器之间的区别才会变得明显。比较:

IntegerProperty x = new SimpleIntegerProperty(2);
IntegerProperty y = new SimpleIntegerProperty(3);
ObservableNumberValue sum = x.add(y);

sum.addListener(obs -> System.out.println("Invalidated")); // invalidation listener
x.set(3);
y.set(5);

使用相同的代码,但使用更改侦听器:

sum.addListener((obs, oldSum, newSum) -> System.out.println("Changed"));

而不是失效监听器。

tutorial中有更多详细信息。