【问题标题】:Low Level Bidirectional Bind低级双向绑定
【发布时间】:2018-05-25 20:42:18
【问题描述】:

我最近发现了绑定,它们看起来很棒。然而,我偶然发现了一个我想制作的绑定,我似乎无法弄清楚。我有一个文本字段,我想以双向方式绑定到双属性。但是,如果字段中的文本可以转换为双精度并且它转换为的双精度在某个范围内,我只希望绑定从字段到双属性。在另一个方向上,我希望绑定不受限制地绑定(我也希望能够为 int 执行此操作,但一旦修复了双精度值,这应该很容易)。我相信这必须通过低级别绑定来完成,不是吗?如何做到这一点?

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

非常感谢。

【问题讨论】:

    标签: javafx binding low-level bidirectional


    【解决方案1】:

    在 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'd。 编辑:这段代码,取决于ObservableValues 的实现,也可能进入一个无限循环的更新,如 cmets 中指出的那样


    编辑:我给出的第一个“健壮”示例存在一些问题,并且没有提供一种方法来解除彼此的属性。我已更改示例以使其更正确,并提供上述 unbinding 功能。这个新示例基于 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;
            }
    
        }
    
    }
    

    【讨论】:

    • 啊,好吧。这基本上就是我已经在做的事情。这个例子是不是引入了一个无限循环的绑定(不幸的是我现在没有我的电脑来检查它)?
    • @IvarEriksson 在标准 API 中实现属性的方式是这样的,因此它们仅在值 真正 更改时触发事件。所以,我认为在这种情况下不会有任何无限循环。但你是对的,这段代码很脆弱。我会努力解决的。
    • 好的,很好。我希望我知道更多这样的东西,但我想这是有经验的。感谢您的宝贵时间。
    • 不用担心,我只是感谢您的帮助。
    猜你喜欢
    • 2015-04-26
    • 2016-04-19
    • 2012-05-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-02-11
    相关资源
    最近更新 更多