В привязках 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;
}
}
}