将JavaFx属性绑定到多个可观察对象

将JavaFx属性绑定到多个可观察对象,java,javafx,reactfx,Java,Javafx,Reactfx,我正在编写一个JavaFx控件,它由一个子控件组成,该子控件获取用户输入,例如TextField。主组件包含一个属性,该属性表示文本的已解析表示形式,例如LocalDateTime。当用户输入某些内容时,该属性应该更新,因此我将其绑定到子级的value属性。还可以通过绑定value属性从外部更改当前值,因此这必须是双向绑定才能自动更新子级。在客户端代码绑定到属性之前,该控件可以正常工作。以下代码显示了我的问题: 导入javafx.beans.property.SimpleIntegerPrope

我正在编写一个JavaFx控件,它由一个子控件组成,该子控件获取用户输入,例如TextField。主组件包含一个属性,该属性表示文本的已解析表示形式,例如LocalDateTime。当用户输入某些内容时,该属性应该更新,因此我将其绑定到子级的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.mapBidirective
在内部绑定父属性和子属性

我希望实现以下目标: 1.如果
外部的
的值发生变化,则应将其传播到p.value,然后再传播到p.a1.p
2.如果p.a1.p发生变化,则应将其传播到p.value


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

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

  • 如果外部的值发生变化,则应将其传播到p.value,然后再传播到p.a1.p
  • 如果p.a1.p发生变化,则应将其传播到p.value
  • 下面的代码实现了这一点。它使用两个双向绑定的
    Var
    s,因此其中一个变量中的变化会反馈给另一个
    Var
    。来自外部的更改被定义为一个
    EventStream
    ,它被馈送到一个
    Var
    s

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

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