【问题标题】:Bind a JavaFx property to more than one observable将 JavaFx 属性绑定到多个可观察对象
【发布时间】:2017-07-12 19:41:00
【问题描述】:

我正在编写一个 JavaFx 控件,该控件由一个获取用户输入的子控件组成,例如一个文本字段。主要组件拥有一个属性,该属性表示文本的解析表示,例如一个本地日期时间。当用户输入一些东西时,这个属性应该被更新,所以我将它绑定到孩子的 value 属性。还应该可以通过绑定 value 属性从外部更改当前值,因此这必须是双向绑定才能自动更新子项。该控件工作正常,直到客户端代码绑定到该属性。以下代码显示了我的问题:

导入 javafx.beans.property.SimpleIntegerProperty;

public class Playbook {

    // The internal control with a property p
    static class Child {
        public SimpleIntegerProperty p = new SimpleIntegerProperty();

        public void set(int i) {p.set(i);}
    }

    // My new control with a property value which is bound
    // bidirectionally to Child.p
    static class Parent {
        public Child a1;

        public SimpleIntegerProperty value = new SimpleIntegerProperty();

        public Parent() {
            a1 = new Child();

            value.bindBidirectional(a1.p);
        }
    }

    public static void main(String[] args) {
        Parent p = new Parent();

        // some client code wants to keep the 
        // value updated and thus binds to it
        SimpleIntegerProperty outside = new SimpleIntegerProperty();
        p.value.bind(outside);

        // simulate a change in the child control
        p.a1.p.set(10);         
    }
}

运行代码时,出现无法设置绑定属性的异常:

Caused by: java.lang.RuntimeException: A bound value cannot be set.
    at javafx.beans.property.IntegerPropertyBase.set(IntegerPropertyBase.java:143)
    at com.sun.javafx.binding.BidirectionalBinding$BidirectionalIntegerBinding.changed(BidirectionalBinding.java:467)

我确信这一定是一个常见问题,我只是没有看到明显的解决方案。我正在使用 ReactFx,因此欢迎使用纯 JavaFx 或 ReactFx 的任何解决方案。真实代码使用Var.mapBidirectional内部绑定Parent和Child属性。

我想要实现的目标如下: 1.如果outside的值发生变化,这应该传播到p.value,然后传播到p.a1.p 2.如果p.a1.p发生变化,这应该传播到p.value

由此我得出结论,Parent.value 和 Parent.a1.p 总是相同的(加上映射中应用的一些转换),我使用双向映射。 outside 可以独立更改,并且可以与 value 不同,因此我使用单向绑定。

【问题讨论】:

  • p.value.bind(outside); 表示p.value 将始终与value 具有相同的值。 p.value.bindBidirectional(p.a1.p) 表示p.valuep.a1.p 将始终具有相同的值。显然,这些规则不能同时执行——特别是如果您将p.a1.p 显式设置为与outside 不同的值。你得到了一个例外,因为你试图同时执行相互矛盾的规则。没有解决办法;你也可以声明int i;,并询问如何同时生成i=0i=10
  • @James_D 谢谢,我只是在寻找那个参考。那么如何在没有手动侦听器的情况下使用属性来编写控件呢?客户端可以双向绑定,但这在类型系统中并未强制执行,并且似乎暴露了内部结构。我认为像我这样的情况很常见。文档中是否有示例?
  • @James_D 我的意思是,如果我做 outside.bind(p.value) 我会得到一个例外,但如果我 outside.bindBidirectional(p.value) 它工作。获取属性的方法的签名或告诉我这一点的属性类型中没有任何内容。我必须依赖公开实现细节的文档。看来我的设计不合适。
  • @James_D 我也觉得很难。但是WritableValue 的合约是它是可写的。所有子类型都应该遵守该约定,但Property 显然不遵守。对于在 JavaFX 中实际上可以完成的事情,我将把它留在那里。我可以推测如何在 compile 时确保 single 编写器原则上,但这将超出 Java 中的实际/可能范围,并且完全是“非 JavaFX”。跨度>
  • @James_D 我们的想法是我们编写组件的不可变描述。当一个组件的属性被绑定时,我们会得到一个组件的新描述(具有不同的类型),其中该属性不再是可写的(具有不同的类型)。整个应用程序的描述最终将由平台解释。多次绑定到同一个描述的同一个属性会产生多个描述,但最终只会执行一个描述。

标签: java javafx reactfx


【解决方案1】:

在了解 JavaFx 绑定不是我想象的那样并且语义不允许这样做之后,我切换到 ReactFx's EventStream/Val/Var。 ReactFx 的模型似乎更符合我的预期,它允许我创建一个行为如下的系统:

  1. 如果外部的值发生变化,这应该传播到 p.value,然后传播到 p.a1.p
  2. 如果 p.a1.p 发生变化,这应该传播到 p.value

以下代码实现了这一点。它使用两个Vars,它们是双向绑定的,因此其中一个的变化被馈送到另一个Var。来自外部的更改被定义为EventStream,它被馈送到Vars 之一。

import org.reactfx.EventStream;
import org.reactfx.EventStreams;
import org.reactfx.value.Var;

import javafx.beans.property.SimpleIntegerProperty;

public class Playbook {

    static class Child {
        public Var<Integer> p = Var.newSimpleVar(0);

        public void set(int i) {this.p.setValue(i);}
    }

    static class Parent {
        public Child a1;

        public Var<Integer> value = Var.newSimpleVar(0);

        public Parent() {
            this.a1 = new Child();

            this.value.bindBidirectional(this.a1.p);
        }
    }

    public static void main(String[] args) {
        Parent p = new Parent();

        // some client code wants to keep the
        // value updated and thus binds to it
        SimpleIntegerProperty outside = new SimpleIntegerProperty();

        // A SimpleIntegerProperty is not a Property<Integer> but a 
        // Property<Number> so we have to use map to translate to Integer
        EventStream<Integer> stream = EventStreams.valuesOf(outside).map(Number::intValue);

        stream.feedTo(p.value);

        // simulate a change in the child control
        p.a1.p.setValue(10);
        System.out.println( p.value.getValue() );
        System.out.println(p.a1.p.getValue() );
        System.out.println(outside.get());

        // feed new value from outside
        outside.set(42);

        System.out.println( p.value.getValue() );
        System.out.println(p.a1.p.getValue() );
        System.out.println(outside.get());
    }
}

程序运行并打印

10
10
0
42
42
42

【讨论】:

    猜你喜欢
    • 2021-11-22
    • 2020-06-05
    • 2013-10-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-03-05
    • 1970-01-01
    相关资源
    最近更新 更多