从 ObjectProperty 更新事件(就像在 ObservableList 中一样)

Update events from ObjectProperty (just like in ObservableList)

如果其中一个元素更改了其属性之一(更新事件),我可以使用提取器 (Callback<E, Observable[]> extractor) 生成 ListProperty 触发更改事件。

Update Change Event in ObservableList

是否有 ObjectProperty<> 的等效项?我有一个 SimpleObjectProperty,当它的值(另一种 bean 类型)的属性发生变化(更新变化事件)时,我想触发事件。

示例代码:

public class TestBean {

    public static <T extends TestBean> Callback<T, Observable[]> extractor() {

    return (final T o) -> new Observable[] { o.testPropertyProperty() };
    }

    private final StringProperty testProperty = new SimpleStringProperty();

    public final StringProperty testPropertyProperty() {
    return this.testProperty;
    }

    public final String getTestProperty() {
    return this.testPropertyProperty().get();
    }

    public final void setTestProperty(final String testProperty) {
    this.testPropertyProperty().set(testProperty);
    }

}

public class SomeType {

    /**
     * How can I listen for changes of TestBean#testProperty?
     */
    private final ObjectProperty<TestBean> property = new SimpleObjectProperty<>();

}

我想在 SomeType#property 的值发生变化时接收变化事件,而且,如果 SomeType#property#testProperty 发生变化。

我不能只监听 SomeType#property#testProperty,因为当 SomeType#property 更改时我不会收到通知(然后我会监听错误的对象以进行更改)。

我认为您需要为您的对象添加一个侦听器。这可以简单地完成。首先,你应该用构造函数和吸气剂这样写你的class:

import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;

public class SomeType {

    public ObjectProperty<TestProperty> property;

    public SomeType(TestProperty testProperty) {
        this.property = new SimpleObjectProperty<>(testProperty);
    }

    public TestProperty getProperty() {
        return property.get();
    }

    public ObjectProperty<TestProperty> propertyProperty() {
        return property;
    }
}

然后在任何有 SomeType 实例的地方,您都可以链接属性,这样您就可以获得 property 属性 的 testProperty(),然后只需添加一个倾听者。

someType.getProperty().testProperty().addListener((observable, oldValue, newValue) -> {
        // Do whatever you want if the its value changed.
        // You can also use its old or new value.
    });

我得出以下结论:

public class ObservableValueProperty<T> extends SimpleObjectProperty<T> {

    private InvalidationListener listener = null;

    private final Callback<T, Observable[]> extractor;

    public ObservableValueProperty() {
    this(null);
    }

    public ObservableValueProperty(final Callback<T, Observable[]> extractor) {
    this.extractor = extractor;
    }

    @Override
    protected void fireValueChangedEvent() {
    super.fireValueChangedEvent();
    }

    @Override
    public void setValue(final T v) {
    if (extractor != null) {
        final T oldValue = super.get();
        if (oldValue != null) {
        for (final Observable o : extractor.call(oldValue)) {
            o.removeListener(listener);
        }
        }
        listener = o -> fireValueChangedEvent();
        for (final Observable o : extractor.call(v)) {
        o.addListener(listener);
        }
    }
    super.setValue(v);
    }
}

public class ObservableValuePropertyTest4 implements ChangeListener<Object> {

    @BeforeClass
    public static void setUpBeforeClass() throws Exception {
    }

    @AfterClass
    public static void tearDownAfterClass() throws Exception {
    }

    @Before
    public void setUp() throws Exception {
    }

    @After
    public void tearDown() throws Exception {
    }

    static class NestedBean {

    StringProperty nestedProperty = new SimpleStringProperty("hans");

    public static <T extends NestedBean> Callback<T, Observable[]> extractor() {

        return (final T o) -> new Observable[] { o.nestedProperty };
    }

    @Override
    public boolean equals(final Object obj) {
        if (obj instanceof NestedBean) {
        System.err.println(this.nestedProperty.get() + " " + ((NestedBean) obj).nestedProperty.get());
        return Objects.equal(this.nestedProperty.get(), ((NestedBean) obj).nestedProperty.get());
        }
        return false;
    }

    }

    private ObservableValueProperty<NestedBean> p;

    private NestedBean nestedBean;

    private String newNestedValue = null;

    @Test
    public void test01() {
    p = new ObservableValueProperty<>(NestedBean.extractor());
    nestedBean = new NestedBean();
    p.setValue(nestedBean);
    p.addListener(this);
    nestedBean.nestedProperty.set("peter");
    assertEquals("peter", newNestedValue);
    }

    @Override
    public void changed(final ObservableValue<? extends Object> observable, final Object oldValue,
        final Object newValue) {
    System.err.println("Changed");
    newNestedValue = nestedBean.nestedProperty.get();

    }

}

不幸的是,这不会触发任何更改事件,因为 ExpressionHelper$SingleChange:

@Override
        protected void fireValueChangedEvent() {
            final T oldValue = currentValue;
            currentValue = observable.getValue();
            final boolean changed = (currentValue == null)? (oldValue != null) : !currentValue.equals(oldValue);
            if (changed) {
                try {
                    listener.changed(observable, oldValue, currentValue);
                } catch (Exception e) {
                    Thread.currentThread().getUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), e);
                }
            }
        }

这会检查是否相等,只有在不相等时才通知所有侦听器。当我触发 fireValueChangedEvent() 时,值已经改变,新旧值相等,因此不会通知监听器。

I want to receive change events if value of SomeType#property changes, but also, if SomeType#property#testProperty changes.

I cannot just listen for SomeType#property#testProperty, since I would not be notified, when SomeType#property was changed (I would then listen on the wrong object for changes).

这是当前 JavaFX 迭代的各种限制。内置方式不可靠,最好使用 3rd 方库。有关详细信息,请参阅此

对于您的情况,可以以类似的方式使用 ReactFX:

import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;

import org.reactfx.value.Val;
import org.reactfx.value.Var;

class TestBean {

    private final StringProperty testProperty = new SimpleStringProperty();
    public final StringProperty testPropertyProperty()        { return testProperty; }
    public final String getTestProperty()                     { return testProperty.get(); }
    public final void setTestProperty(String newTestProperty) { testProperty.set(newTestProperty); }
}

public class SomeType {

    private final ObjectProperty<TestBean> property = new SimpleObjectProperty<>();
    public final ObjectProperty<TestBean> propertyProperty() { return property; }
    public final TestBean getProperty()                      { return property.get(); }
    public final void setProperty(TestBean newProperty)      { property.set(newProperty); }

    public static void main(String[] args) {
        SomeType someType = new SomeType();
        Var<String> chainedTestProperty = Val.selectVar(someType.propertyProperty(), TestBean::testPropertyProperty);
        chainedTestProperty.addListener((obs, oldVal, newVal) -> System.out.println(obs + " " + oldVal + "->" + newVal));

        //Tests
        someType.setProperty(new TestBean());
        someType.getProperty().setTestProperty("s1");
        TestBean bean2 = new TestBean();
        bean2.setTestProperty("s2");
        someType.setProperty(bean2);
        someType.setProperty(new TestBean());
    }
}

输出:

org.reactfx.value.FlatMappedVar@7aec35a null->s1 
org.reactfx.value.FlatMappedVar@7aec35a s1->s2 
org.reactfx.value.FlatMappedVar@7aec35a s2->null

关键线

Var<String> chainedTestProperty = Val.selectVar(someType.propertyProperty(), TestBean::testPropertyProperty);

是一种侦听器链接。第一个参数是某种类型 Type 的 属性 (OvservableValue)。第二个参数是 Type 中某些其他类型 Type2 的“子”-属性,它作为从 Type 到 属性 的函数给出。

现在,只要链中的任何“链接”发生变化,您都会收到通知。您可以通过以这种方式连续链接 ovservable 来继续监听 sub-sub-... 属性的变化。

上周我遇到了同样的问题,经过多次尝试,我找到了一个似乎按预期工作的解决方案:

  • 我创建了一个名为 ObjectXProperty<E> 的新 class,它与 ObjectProperty<E>;
  • 具有相同的界面
  • 它的构造函数可以接受 Callback<E,Observable[]>,我们的提取函数;
  • ObjectXProperty 中,我使用了一个委托所有方法的 SimpleObjectProperty
  • 魔术在于 set(E value) 方法:我创建了一个 ObjectBinding,它只是发送回 value,但是它使用提取器函数来决定它何时失效!

  • 如果之前在 ObjectXProperty 上使用了 bind 方法,则不会应用此技巧,让 "real" 绑定完成他的工作;如果调用 unbind 方法,它将再次工作;

这是我的新 class ObjectXProperty<E> :

import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.util.Callback;

/**
 *
 * @author Claude Bouchard - 2017
 */

public class ObjectXProperty<E> extends ObjectProperty<E> {

SimpleObjectProperty<E> p;
Callback<E, Observable[]> extractor;

boolean externalBound = false;

public ObjectXProperty(Callback<E, Observable[]> extractor) {
    this.extractor = extractor;
}

public ObjectXProperty(E init, Callback<E, Observable[]> extractor) {

    p = new SimpleObjectProperty();
    this.extractor = extractor;
    set(init);

}

public ObjectXProperty(Object bean, String name, Callback<E, Observable[]> extractor) {
    p = new SimpleObjectProperty(bean, name);
    this.extractor = extractor;
}

public ObjectXProperty(Object bean, String name, E init, Callback<E, Observable[]> extractor) {
    p = new SimpleObjectProperty(bean, name);
    this.extractor = extractor;
    set(init);

}

@Override
public void set(E value) {
    if (!externalBound) {
        if (value != null) {
            p.bind(Bindings.createObjectBinding(() -> {
                return value;
            }, extractor.call(value)));

        } else {
            p.bind(Bindings.createObjectBinding(() -> {
                return value;
            }, new Observable[]{}));
        }
    } else {
        p.set(value); //As expected, it will throw a java.lang.RuntimeException
    }
}

@Override
public E get() {
    return p.get();
}

@Override
public void addListener(ChangeListener<? super E> listener) {
    p.addListener(listener);
}

@Override
public void removeListener(ChangeListener<? super E> listener) {
    p.removeListener(listener);
}

@Override
public void addListener(InvalidationListener listener) {
    p.addListener(listener);
}

@Override
public void removeListener(InvalidationListener listener) {
    p.removeListener(listener);
}

@Override
public Object getBean() {
    return p.getBean();
}

@Override
public String getName() {
    return p.getName();
}

@Override
public void bind(ObservableValue<? extends E> observable) {
    p.bind(observable);
    externalBound = true;
}

@Override
public void unbind() {
    p.unbind();
    externalBound = false;
    set(get()); //to reactivate the extractor on the last value
}

@Override
public boolean isBound() {
    return externalBound;
}

}