低级双向绑定
Low Level Bidirectional Bind
我最近发现了绑定,它们看起来很棒。然而,我偶然发现了一个我想制作的绑定,但我似乎无法弄清楚。我有一个文本字段,我想以双向方式将其绑定到双 属性。但是,如果字段中的文本可以转换为双精度,并且转换为的双精度在某个范围内,我只希望绑定从字段到双精度 属性。在另一个方向上,我希望绑定不受限制地绑定(我也希望能够对整数执行此操作,但是一旦修复了双精度,这应该很容易)。我相信这必须通过低级别绑定来完成,不是吗?如何做到这一点?
我刚开始使用绑定,但对它们不是很好,请放轻松。
非常感谢。
在 JavaFX 绑定中,只需添加侦听器并做出相应的反应。这样想,您可能会争辩说听众是 API 的 "low-level" 方面。要做你想做的事,你必须创建自己的听众。我不知道有什么可以满足您的要求 "out of the box"。
未准备"production use"的示例:
public static void bind(TextField field, DoubleProperty property) {
field.textProperty().addListener((observable, oldText, newText) -> {
try {
// If the text can't be converted to a double then an
// exception is thrown. In this case we do nothing.
property.set(Double.parseDouble(newText));
} catch (NumberFormatException ignore) {}
});
property.addListener((observable, oldNumber, newNumber) -> {
field.setText(Double.toString(newNumber.doubleValue()));
});
}
如果我正确理解了您的要求,这将满足您的要求。但是,我相信这段代码会带来内存泄漏的可能性。理想情况下,您希望侦听器不阻止其他侦听器被垃圾收集。例如,如果 property
不再被强引用,那么 field
不应阻止 property
被 GC。 编辑:根据 ObservableValue
的实现,此代码也可能进入无限更新循环,如评论中所指出的。
编辑:我给出的第一个 "robust" 示例存在一些问题,并且没有提供 解除绑定 属性彼此的方法。我更改了示例以使其更正确,并且还提供了上述 unbinding 功能。这个新示例基于 JavaFX 的开发人员如何在内部处理双向绑定。
我上面给出的更强大的代码示例。标准 JavaFX 内部 API 所使用的代码在很大程度上 "inspired"。具体来说 class com.sun.javafx.binding.BidirectionalBinding
.
import javafx.beans.WeakListener;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import java.lang.ref.WeakReference;
import java.util.Objects;
public class CustomBindings {
// This code is based heavily on how the standard JavaFX API handles bidirectional bindings. Specifically,
// the class 'com.sun.javafx.binding.BidirectionalBinding'.
public static void bindBidirectional(StringProperty stringProperty, DoubleProperty doubleProperty) {
if (stringProperty == null || doubleProperty == null) {
throw new NullPointerException();
}
BidirectionalBinding binding = new BidirectionalBinding(stringProperty, doubleProperty);
stringProperty.addListener(binding);
doubleProperty.addListener(binding);
}
public static void unbindBidirectional(StringProperty stringProperty, DoubleProperty doubleProperty) {
if (stringProperty == null || doubleProperty == null) {
throw new NullPointerException();
}
// The equals(Object) method of BidirectionalBinding was overridden to take into
// account only the properties. This means that the listener will be removed even
// though it isn't the *same* (==) instance.
BidirectionalBinding binding = new BidirectionalBinding(stringProperty, doubleProperty);
stringProperty.removeListener(binding);
doubleProperty.removeListener(binding);
}
private static class BidirectionalBinding implements ChangeListener<Object>, WeakListener {
private final WeakReference<StringProperty> stringRef;
private final WeakReference<DoubleProperty> doubleRef;
// Need to cache it since we can't hold a strong reference
// to the properties. Also, a changing hash code is never a
// good idea and it needs to be "insulated" from the fact
// the properties can be GC'd.
private final int cachedHashCode;
private boolean updating;
private BidirectionalBinding(StringProperty stringProperty, DoubleProperty doubleProperty) {
stringRef = new WeakReference<>(stringProperty);
doubleRef = new WeakReference<>(doubleProperty);
cachedHashCode = Objects.hash(stringProperty, doubleProperty);
}
@Override
public boolean wasGarbageCollected() {
return stringRef.get() == null || doubleRef.get() == null;
}
@Override
public void changed(ObservableValue<?> observable, Object oldValue, Object newValue) {
if (!updating) {
StringProperty stringProperty = stringRef.get();
DoubleProperty doubleProperty = doubleRef.get();
if (stringProperty == null || doubleProperty == null) {
if (stringProperty != null) {
stringProperty.removeListener(this);
}
if (doubleProperty != null) {
doubleProperty.removeListener(this);
}
} else {
updating = true;
try {
if (observable == stringProperty) {
updateDoubleProperty(doubleProperty, (String) newValue);
} else if (observable == doubleProperty) {
updateStringProperty(stringProperty, (Number) newValue);
} else {
throw new AssertionError("How did we get here?");
}
} finally {
updating = false;
}
}
}
}
private void updateStringProperty(StringProperty property, Number newValue) {
if (newValue != null) {
property.set(Double.toString(newValue.doubleValue()));
} else {
// set the property to a default value such as 0.0?
property.set("0.0");
}
}
private void updateDoubleProperty(DoubleProperty property, String newValue) {
if (newValue != null) {
try {
property.set(Double.parseDouble(newValue));
} catch (NumberFormatException ignore) {
// newValue is not a valid double
}
}
}
@Override
public int hashCode() {
return cachedHashCode;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
StringProperty stringProperty1 = stringRef.get();
DoubleProperty doubleProperty1 = doubleRef.get();
if (stringProperty1 == null || doubleProperty1 == null) {
return false;
}
if (obj instanceof BidirectionalBinding) {
BidirectionalBinding other = (BidirectionalBinding) obj;
StringProperty stringProperty2 = other.stringRef.get();
DoubleProperty doubleProperty2 = other.doubleRef.get();
if (stringProperty2 == null || doubleProperty2 == null) {
return false;
}
return stringProperty1 == stringProperty2 && doubleProperty1 == doubleProperty2;
}
return false;
}
}
}
我最近发现了绑定,它们看起来很棒。然而,我偶然发现了一个我想制作的绑定,但我似乎无法弄清楚。我有一个文本字段,我想以双向方式将其绑定到双 属性。但是,如果字段中的文本可以转换为双精度,并且转换为的双精度在某个范围内,我只希望绑定从字段到双精度 属性。在另一个方向上,我希望绑定不受限制地绑定(我也希望能够对整数执行此操作,但是一旦修复了双精度,这应该很容易)。我相信这必须通过低级别绑定来完成,不是吗?如何做到这一点?
我刚开始使用绑定,但对它们不是很好,请放轻松。
非常感谢。
在 JavaFX 绑定中,只需添加侦听器并做出相应的反应。这样想,您可能会争辩说听众是 API 的 "low-level" 方面。要做你想做的事,你必须创建自己的听众。我不知道有什么可以满足您的要求 "out of the box"。
未准备"production use"的示例:
public static void bind(TextField field, DoubleProperty property) {
field.textProperty().addListener((observable, oldText, newText) -> {
try {
// If the text can't be converted to a double then an
// exception is thrown. In this case we do nothing.
property.set(Double.parseDouble(newText));
} catch (NumberFormatException ignore) {}
});
property.addListener((observable, oldNumber, newNumber) -> {
field.setText(Double.toString(newNumber.doubleValue()));
});
}
如果我正确理解了您的要求,这将满足您的要求。但是,我相信这段代码会带来内存泄漏的可能性。理想情况下,您希望侦听器不阻止其他侦听器被垃圾收集。例如,如果 property
不再被强引用,那么 field
不应阻止 property
被 GC。 编辑:根据 ObservableValue
的实现,此代码也可能进入无限更新循环,如评论中所指出的。
编辑:我给出的第一个 "robust" 示例存在一些问题,并且没有提供 解除绑定 属性彼此的方法。我更改了示例以使其更正确,并且还提供了上述 unbinding 功能。这个新示例基于 JavaFX 的开发人员如何在内部处理双向绑定。
我上面给出的更强大的代码示例。标准 JavaFX 内部 API 所使用的代码在很大程度上 "inspired"。具体来说 class com.sun.javafx.binding.BidirectionalBinding
.
import javafx.beans.WeakListener;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import java.lang.ref.WeakReference;
import java.util.Objects;
public class CustomBindings {
// This code is based heavily on how the standard JavaFX API handles bidirectional bindings. Specifically,
// the class 'com.sun.javafx.binding.BidirectionalBinding'.
public static void bindBidirectional(StringProperty stringProperty, DoubleProperty doubleProperty) {
if (stringProperty == null || doubleProperty == null) {
throw new NullPointerException();
}
BidirectionalBinding binding = new BidirectionalBinding(stringProperty, doubleProperty);
stringProperty.addListener(binding);
doubleProperty.addListener(binding);
}
public static void unbindBidirectional(StringProperty stringProperty, DoubleProperty doubleProperty) {
if (stringProperty == null || doubleProperty == null) {
throw new NullPointerException();
}
// The equals(Object) method of BidirectionalBinding was overridden to take into
// account only the properties. This means that the listener will be removed even
// though it isn't the *same* (==) instance.
BidirectionalBinding binding = new BidirectionalBinding(stringProperty, doubleProperty);
stringProperty.removeListener(binding);
doubleProperty.removeListener(binding);
}
private static class BidirectionalBinding implements ChangeListener<Object>, WeakListener {
private final WeakReference<StringProperty> stringRef;
private final WeakReference<DoubleProperty> doubleRef;
// Need to cache it since we can't hold a strong reference
// to the properties. Also, a changing hash code is never a
// good idea and it needs to be "insulated" from the fact
// the properties can be GC'd.
private final int cachedHashCode;
private boolean updating;
private BidirectionalBinding(StringProperty stringProperty, DoubleProperty doubleProperty) {
stringRef = new WeakReference<>(stringProperty);
doubleRef = new WeakReference<>(doubleProperty);
cachedHashCode = Objects.hash(stringProperty, doubleProperty);
}
@Override
public boolean wasGarbageCollected() {
return stringRef.get() == null || doubleRef.get() == null;
}
@Override
public void changed(ObservableValue<?> observable, Object oldValue, Object newValue) {
if (!updating) {
StringProperty stringProperty = stringRef.get();
DoubleProperty doubleProperty = doubleRef.get();
if (stringProperty == null || doubleProperty == null) {
if (stringProperty != null) {
stringProperty.removeListener(this);
}
if (doubleProperty != null) {
doubleProperty.removeListener(this);
}
} else {
updating = true;
try {
if (observable == stringProperty) {
updateDoubleProperty(doubleProperty, (String) newValue);
} else if (observable == doubleProperty) {
updateStringProperty(stringProperty, (Number) newValue);
} else {
throw new AssertionError("How did we get here?");
}
} finally {
updating = false;
}
}
}
}
private void updateStringProperty(StringProperty property, Number newValue) {
if (newValue != null) {
property.set(Double.toString(newValue.doubleValue()));
} else {
// set the property to a default value such as 0.0?
property.set("0.0");
}
}
private void updateDoubleProperty(DoubleProperty property, String newValue) {
if (newValue != null) {
try {
property.set(Double.parseDouble(newValue));
} catch (NumberFormatException ignore) {
// newValue is not a valid double
}
}
}
@Override
public int hashCode() {
return cachedHashCode;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
StringProperty stringProperty1 = stringRef.get();
DoubleProperty doubleProperty1 = doubleRef.get();
if (stringProperty1 == null || doubleProperty1 == null) {
return false;
}
if (obj instanceof BidirectionalBinding) {
BidirectionalBinding other = (BidirectionalBinding) obj;
StringProperty stringProperty2 = other.stringRef.get();
DoubleProperty doubleProperty2 = other.doubleRef.get();
if (stringProperty2 == null || doubleProperty2 == null) {
return false;
}
return stringProperty1 == stringProperty2 && doubleProperty1 == doubleProperty2;
}
return false;
}
}
}