从 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;
}
}
如果其中一个元素更改了其属性之一(更新事件),我可以使用提取器 (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, ifSomeType#property#testProperty
changes.I cannot just listen for
SomeType#property#testProperty
, since I would not be notified, whenSomeType#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;
}
}