Двунаправленная привязка низкого уровня - PullRequest
0 голосов
/ 25 мая 2018

Я недавно обнаружил привязки, и они кажутся великолепными.Однако я наткнулся на обязательство, которое я хочу сделать, что я, кажется, не могу понять.У меня есть текстовое поле, которое я хочу связать с двойным свойством двунаправленным способом.Но я хочу, чтобы привязка происходила из поля к свойству double, если текст в поле можно преобразовать в тип double, и если тип double, в который он преобразуется, попадает в некоторый диапазон.В другом направлении я хочу, чтобы связывание было связано без ограничений (я также хочу иметь возможность сделать это для целых, но это должно быть легко, как только двойное будет зафиксировано).Я считаю, что это должно быть сделано с привязкой низкого уровня, не так ли?Как это можно сделать?

Я только начал использовать привязки и не очень хорошо с ними справляюсь, поэтому будьте осторожны со мной.

Большое спасибо.

1 Ответ

0 голосов
/ 26 мая 2018

В привязках 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. Редактировать: Этот код, в зависимости от реализаций ObservableValue s, может также вводить бесконечный цикл обновлений, как указано в комментариях .


Редактировать: Первый «надежный» пример, который я привел, имел некоторые проблемы и не позволял отсоединять свойства друг от друга.Я изменил пример, чтобы сделать его более корректным, а также добавил упомянутую функцию unbinding .Этот новый пример основан на том, как разработчики JavaFX внутренне обрабатывали двунаправленное связывание.

Более надежный пример кода, который я привел выше.Это в значительной степени "вдохновлено" кодом, используемым стандартными внутренними API JavaFX.Конкретно класс 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;
        }

    }

}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...