【问题标题】:JavaFX: Bidirectional Binding in FXMLJavaFX:FXML 中的双向绑定
【发布时间】:2020-04-04 12:06:15
【问题描述】:

我正在使用 JavaFX,并且正在研究数据绑定。 我发现我可以在我的 FXML 中定义单向绑定,如下所示:

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

这意味着,usernameTextField 的文本正在“观察”controller.userName 属性。

但这会创建一种单向绑定。如果 controller.userName 属性发生变化,我会在文本字段中看到更新的文本,该部分有效。但是我不能再在文本字段中插入文本,因为我已经进行了单向绑定。

我能找到的所有关于这个的帖子都已经有四年多了,但我不知道 JavaFX 是否已经更新以支持更精细的绑定。

【问题讨论】:

    标签: java javafx data-binding fxml two-way-binding


    【解决方案1】:

    这样做的方法是:

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

    但此功能尚未启用(上次在 OpenJFX 13 上检查),使用它会导致 FXMLLoader 抛出 UnsupportedOperationException("This feature is not currently enabled.")

    【讨论】:

    • 我在另一篇文章的某个地方偶然发现了这个,评论者发现这是一个错误,应该被删除。
    • @Troels 他们已经搁置了在 FXML 中声明双向绑定的语法,但实际实现从未实现。有一个现有的 enhancement request 很遗憾一年多没有更新。
    • 如此半心半意地做这件事似乎很奇怪,并提供部分功能然后放弃它。我不喜欢在控制器中引用我的 GUI 组件。谢谢你的意见,虽然。
    • @Troels 我还有一堆代码行应该从我的控制器中删除到 FXML。等待该功能在未来版本中启用。
    • @Troels 注意 JavaFX 是开源的,这意味着任何有足够时间和动力的人都可以contribute this feature。不建议您成为贡献者,我只是将信息放在那里。
    【解决方案2】:

    正如已经说过的,没有简单的方法可以从 FXML 进行双向绑定。然而,它可以从 FXML 完成,但不能使用 FXML 本身,可以这么说。这是我的发现。

    &lt;fx:script&gt;

    第一种也是最明显的方法是使用&lt;fx:script&gt; 通过 JavaScript 进行所需的任何绑定。例如,可以使用以下 sn-p 进行双向绑定:

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

    这里的someProperty() 是一个名为some 的属性的控制器方法。

    自定义帮助类

    第二个选项是创建一个辅助类,然后在 &lt;fx:define&gt; 块中使用它(使用上面的约定):

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

    下面是这样一个类的示例实现:

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

    它使用反射来访问属性,因为我还没有找到让 FXMLLoader 将属性而不是其值传递给 setter 的方法。

    总结一下

    这两种方式都不理想,但可以从 FXML 进行双向绑定,尽管方式不方便。当然,像

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

    会更好,但唉。我也不知道这两种提议的方法在 Scene Builder 上的效果如何,因为我不使用它。

    【讨论】:

      【解决方案3】:

      这段代码完美运行:

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

      要操作,控制器必须包含一个getter:

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

      【讨论】:

      • 不,此代码根本不起作用,并给出“此功能当前未启用”。错误。
      • 已编辑,将“#{controller.userName}”替换为“${controller.userName}”
      • 然后它将是单向绑定,而不是双向绑定。
      猜你喜欢
      • 2017-05-24
      • 2010-09-24
      • 1970-01-01
      • 2020-06-03
      • 1970-01-01
      • 2015-04-26
      • 2016-04-19
      • 2012-05-25
      • 1970-01-01
      相关资源
      最近更新 更多