简体   繁体   English

JavaFX:FXML 中的双向绑定

[英]JavaFX: Bidirectional Binding in FXML

I'm working with JavaFX, and am investigating data binding.我正在使用 JavaFX,并且正在研究数据绑定。 I've found that I can define a one-way binding in my FXML, like this:我发现我可以在 FXML 中定义单向绑定,如下所示:

<TextField fx:id="usernameTextField" text="${controller.userName}" GridPane.columnIndex="1" />

This means, that the text of the usernameTextField is 'observing' the controller.userName property.这意味着, usernameTextField 的文本正在“观察” controller.userName 属性。

But this creates a one-way binding.但这会创建一种单向绑定。 If the controller.userName property changes, I see the updated text in the text field, that part works.如果 controller.userName 属性发生变化,我会在文本字段中看到更新的文本,该部分有效。 But I can no longer insert text into the text field, because I've made the one-way binding.但是我不能再在文本字段中插入文本,因为我已经进行了单向绑定。

All I can find about this are posts which are now more than four years old, but I can't figure out, if JavaFX has been updated to support more elaborate bindings.我能找到的所有关于此的帖子现在已经有四年多了,但我不知道 JavaFX 是否已更新以支持更复杂的绑定。

The way to do that is:这样做的方法是:

<TextField fx:id="usernameTextField" text="#{controller.userName}"/>

But this feature is not enabled yet (last checked on OpenJFX 13) and using it will cause FXMLLoader to throw an UnsupportedOperationException("This feature is not currently enabled.") .但是此功能尚未启用(最后在 OpenJFX 13 上检查),使用它会导致FXMLLoader抛出UnsupportedOperationException("This feature is not currently enabled.") FXMLLoader UnsupportedOperationException("This feature is not currently enabled.")

As was already said there's no simple way to make a bidirectional binding from FXML.正如已经说过的,没有简单的方法可以从 FXML 进行双向绑定。 However it can be done from FXML, but not using FXML per se, so to say.然而,它可以从 FXML 完成,但不能使用 FXML 本身,可以这么说。 And here are my findings.这是我的发现。

An <fx:script> block <fx:script>

The first and the most obvious way is to use <fx:script> to make whatever bindings are needed via JavaScript.第一种也是最明显的方法是使用<fx:script>通过 JavaScript 进行任何需要的绑定。 For example one can use the following snippet to make a bidirectional binding:例如,可以使用以下代码段进行双向绑定:

<?language javascript?>
<!-- ... -->
<TextField fx:id="aControl"/>
<fx:script>
javafx.beans.binding.Bindings.bindBidirectional(
    controller.someProperty(),
    aControl.textProperty()
);
</fx:script>

Here someProperty() is a controller method for a property named some .这里someProperty()是一个名为some的属性的控制器方法。

A custom helper class自定义助手类

The second option is to create a helper class and then use it in an <fx:define> block (using the conventions from above):第二个选项是创建一个辅助类,然后在<fx:define>块中使用它(使用上面的约定):

<?import u7n.examples.BidiBind?>
<!-- ... -->
<TextField fx:id="aControl"/>
<fx:define>
<BidiBind o1="$controller" p1="some" o2="$aControl" p2="text"/>
</fx:define>

And here is an example implementation of such a class:这是此类的示例实现:

package u7n.examples;
import javafx.beans.binding.Bindings;
import javafx.beans.property.Property;
public class BidiBind {
    private Object o1;
    private String p1;
    public Object getO1() { return o1; }
    public void setO1(Object o1) { this.o1 = o1; bind(); }
    public String getP1() { return p1; }
    public void setP1(String p1) { this.p1 = p1; bind(); }

    private Object o2;
    private String p2;
    public Object getO2() { return o2; }
    public void setO2(Object o2) { this.o2 = o2; bind(); }
    public String getP2() { return p2; }
    public void setP2(String p2) { this.p2 = p2; bind(); }

    private <T> void bind() {
        if (o1 == null || p1 == null || o2 == null || p2 == null) return;
        try {
            @SuppressWarnings("unchecked")
            Property<T> property1 = (Property<T>)o1.getClass().getMethod(p1 + "Property").invoke(o1);
            @SuppressWarnings("unchecked")
            Property<T> property2 = (Property<T>)o2.getClass().getMethod(p2 + "Property").invoke(o2);
            Bindings.bindBidirectional(property1, property2);
        }
        catch (Exception e) {
            e.printStackTrace();
        }
        o1 = o2 = null;
        p1 = p2 = null;
    }
}

It uses reflection to access properties because I haven't found a way to make FXMLLoader pass a property instead of its value to a setter.它使用反射来访问属性,因为我还没有找到让 FXMLLoader 将属性而不是其值传递给 setter 的方法。

To sum it up把它们加起来

Neither way is ideal, but a bidirectional binding can be done from FXML, although not in a convenient way.两种方式都不是理想的,但是可以从 FXML 完成双向绑定,尽管不是很方便。 Of course, something like当然,像

<TextField fx:id="aControl" text="#{controller.some}"/>

would be even better, but alas.会更好,但唉。 Also I have no idea how well both proposed methods will work with Scene Builder as I don't use it.此外,我不知道这两种建议的方法与 Scene Builder 的配合效果如何,因为我不使用它。

This code works perfectly :此代码完美运行:

<TextField fx:id="usernameTextField" text="${controller.userName}"/>

To operate, the controller must contain a getter :要操作,控制器必须包含一个 getter :

public String getUserName() { return "mystringvalue"; }

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

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