低级双向绑定

时间:2018-05-25 20:42:19

标签: javafx binding low-level bidirectional

我最近发现了绑定,看起来很棒。然而,我偶然发现了一个我想做的约束,我似乎无法弄明白。我有一个textfield,我想以双向方式绑定到double属性。但是,我只希望绑定从字段到double属性,如果字段中的文本可以转换为double,如果它转换为double的范围落在某个范围内。在另一个方向上,我希望绑定没有限制地绑定(我也希望能够为int执行此操作,但是一旦修复了double,这应该很容易)。我认为这必须通过低级绑定来完成,不是吗?怎么办呢?

我刚刚开始使用绑定并且对它们并不好,所以对我很轻松。

非常感谢。

1 个答案:

答案 0 :(得分:2)

在JavaFX绑定中,只需添加侦听器并做出相应的反应。像这样思考你可以说听众是API的“低级”方面。要做你想做的事,你必须创建自己的听众。我不知道任何你想要的东西“开箱即用”。

准备好“生产使用”的示例:

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的实现,此代码也可以输入无限循环的更新,如评论中所述。

编辑:我提供的第一个“健壮”示例遇到了一些问题,并没有提供一种方法来取消绑定彼此的属性。我已经更改了示例以使其更正确并且还提供了所述解除绑定功能。这个新示例基于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;
        }

    }

}