【问题标题】:JavaFX Binding Setter ConventionJavaFX 绑定设置器约定
【发布时间】:2014-12-17 06:50:52
【问题描述】:

我知道 Java 和 C#,但我只知道 C# MVVM 中的属性绑定。 我正在尝试使用属性值的自定义 getter 和 setter 来理解 JavaFX 中的属性绑定(如在 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 从未使用过。

相反,绑定直接使用属性的 get/set 方法,无需我的交互。 我读到我可以使用以下代码,但必须有比这更好的方法:

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

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

编辑:

我想要做的是对最终设置到我的属性的内容更具选择性,因为我想稍后使用它的值来填充我的数据模型(此处省略)。

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

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

【问题讨论】:

  • 更新答案以响应编辑。
  • 我实际上喜欢覆盖属性集。出于某种原因,在我的代码中,当我像下面的答案那样做时,setter 永远不会被调用。我只用 FXML 和我的模型做我的整个应用程序,但我的控制器没有代码。

标签: binding javafx javabeans


【解决方案1】:

JavaFX 属性

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

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

有一些变体:特别是如果你想让事情变得可覆盖,你可以从nameProperty()中删除final修饰符,并在get和set方法中用this.nameProperty()替换this.name。关键是要确保调用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)。

通知

您可以使用属性注册InvalidationListenersChangeListenersaddListener(InvalidationListener) 方法继承自 Observable,表示最后观察到的值可能不再有效。这允许“惰性评估”可观察对象,仅在请求时计算新值。 addListener(ChangeListener) 方法继承自 ObservableValue,它(如其名称所示)是一个显式包装值的 observable。注册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有更多详情。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-10-13
    • 1970-01-01
    • 2013-02-18
    • 1970-01-01
    • 2010-12-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多