Javafx 低电平双向绑定
我最近发现了绑定,它们看起来很棒。然而,我无意中发现了一个我想制作的、似乎无法理解的绑定。我有一个textfield,我想以双向方式绑定到double属性。但是,如果字段中的文本可以转换为double,并且它转换为的double在某个范围内,我只希望绑定从字段到double属性。在另一个方向上,我希望绑定不受限制(我也希望能够对int进行绑定,但一旦修复了双精度绑定,这应该很容易)。我相信这必须通过低级别绑定来完成,不是吗?如何做到这一点 我刚开始使用绑定,对它们不是很在行,所以请对我放松点Javafx 低电平双向绑定,javafx,binding,low-level,bidirectional,Javafx,Binding,Low Level,Bidirectional,我最近发现了绑定,它们看起来很棒。然而,我无意中发现了一个我想制作的、似乎无法理解的绑定。我有一个textfield,我想以双向方式绑定到double属性。但是,如果字段中的文本可以转换为double,并且它转换为的double在某个范围内,我只希望绑定从字段到double属性。在另一个方向上,我希望绑定不受限制(我也希望能够对int进行绑定,但一旦修复了双精度绑定,这应该很容易)。我相信这必须通过低级别绑定来完成,不是吗?如何做到这一点 我刚开始使用绑定,对它们不是很在行,所以请对我放松点 非
非常感谢。在JavaFX绑定中,只需添加侦听器并做出相应的反应。如果这样想,您可能会认为侦听器是API的“低级”方面。要想做你想做的事情,你必须创建自己的侦听器。我不知道有什么东西是你想要的“开箱即用” 未准备好“生产使用”的示例: 如果我正确理解您的要求,这将满足您的要求。不过,我相信这段代码可能会导致内存泄漏。理想情况下,您希望侦听器不要阻止另一个被垃圾收集。例如,如果
属性
不再被强引用,则字段
不应阻止属性
被GC引用。编辑:根据ObservableValue
s的实现情况,此代码也可能进入注释中指出的无限更新循环
Edit:我给出的第一个“健壮”示例有一些问题,没有提供一种方法来解除属性之间的绑定。我已经修改了示例,使其更加正确,并提供了所述的解除绑定功能。这个新示例基于JavaFX开发人员如何在内部处理双向绑定。 我上面给出的一个更健壮的代码示例。这在很大程度上受到了标准JavaFX内部API使用的代码的“启发”。特别是类
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.beans.WeakListener;
导入javafx.beans.property.DoubleProperty;
导入javafx.beans.property.StringProperty;
导入javafx.beans.value.ChangeListener;
导入javafx.beans.value.observeValue;
导入java.lang.ref.WeakReference;
导入java.util.Objects;
公共类自定义绑定{
//这段代码主要基于标准JavaFXAPI如何处理双向绑定,
//类“com.sun.javafx.binding.BidirectionalBinding”。
公共静态void bindproduction(StringProperty StringProperty、DoubleProperty DoubleProperty){
如果(stringProperty==null | | doubleProperty==null){
抛出新的NullPointerException();
}
双向绑定绑定=新的双向绑定(stringProperty,doubleProperty);
stringProperty.addListener(绑定);
addListener(绑定);
}
公共静态无效取消绑定双向(StringProperty StringProperty、DoubleProperty DoubleProperty){
如果(stringProperty==null | | doubleProperty==null){
抛出新的NullPointerException();
}
//双向绑定的equals(Object)方法被重写以考虑
//仅帐户属性。这意味着侦听器将被删除
//虽然它不是*相同的*(==)实例。
双向绑定绑定=新的双向绑定(stringProperty,doubleProperty);
stringProperty.removeListener(绑定);
doubleProperty.removeListener(绑定);
}
私有静态类双向绑定实现ChangeListener、WeakListener{
私有最终WeakReference stringRef;
私人最终WeakReference doubleRef;
//需要缓存它,因为我们无法保存强引用
//此外,更改哈希代码从来都不是一个问题
//好主意,它需要与事实“隔离”
//属性可以是GC'd。
专用最终int cachedHashCode;
私有布尔更新;
私有双向绑定(StringProperty StringProperty、DoubleProperty DoubleProperty){
stringRef=新的WeakReference(stringProperty);
doubleRef=新的WeakReference(doubleProperty);
cachedHashCode=Objects.hash(stringProperty,doubleProperty);
}
@凌驾
公共布尔值wasGarbageCollected(){
返回stringRef.get()==null | | doubleRef.get()==null;
}
@凌驾
公共无效已更改(可观察值可观察、对象旧值、对象新值){
如果(!正在更新){
StringProperty StringProperty=stringRef.get();
DoubleProperty DoubleProperty=doubleRef.get();
如果(stringProperty==null | | doubleProperty==null){
if(stringProperty!=null){
stringProperty.removeListener(此);
}
if(doubleProperty!=null){
doubleProperty.removeListener(此);
}
}否则{
更新=真;
试一试{
if(可观察==stringProperty){
updateDoubleProperty(doubleProperty,(String)newValue);
}else if(可观察==doubleProperty){
updateStringProperty(stringProperty,(Number)newValue);
}否则{
抛出新的断言错误(“我们是如何来到这里的?”);
}
}最后{
更新=假;
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;
}
}
}