Привязать свойство JavaFx к нескольким наблюдаемым

Я пишу элемент управления JavaFx, который состоит из подчиненного элемента управления, который получает пользовательский ввод, например TextField. Основной компонент содержит свойство, которое представляет проанализированное представление текста, например LocalDateTime. Когда пользователь вводит что-то, это свойство должно быть обновлено, поэтому я связываю его со свойством значения дочернего элемента. Также должно быть возможно изменить текущее значение извне посредством привязки свойства value, поэтому это должно быть двунаправленное связывание для автоматического обновления дочернего элемента. Элемент управления работает нормально, пока клиентский код не привязывается к свойству. Следующий код показывает мою проблему:

import 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 всегда идентичны (плюс некоторая трансформация, примененная в отображении), и в этом я использую двунаправленное отображение. снаружи может меняться независимо и может отличаться от значения, поэтому я использую однонаправленную привязку.

1 ответ

Поняв, что JavaFx Bindings - это не то, что я думал, и семантика не позволяет этого, я переключился на ReactFx.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
Другие вопросы по тегам