问题
Yet another binding issue from me. I already know that bindings are lazy, so they don't update when they don't need to, but this case is weird.
The code below gives me the following output:
minWidth calculation [150.0]
minHeight calculation [150.0]
minWidth calculation [0.0]
minWidth calculation [0.0]
minHeight calculation [0.0]
minHeight calculation [0.0]
It looks like Bindings.select()
doesn't trigger binding calculation when when ClassTwo
changes width
or height
. I think so, because when I execute classTwo.getWidth()
at the end of the start()
method, it returns the correct value (not 0). Any ideas?
Main.java
public class Main extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
ClassOne classOne = new ClassOne();
ClassTwo classTwo = new ClassTwo();
ObjectProperty<ClassTwo> classTwoProperty = classOne.classTwoProperty();
classTwoProperty.set(classTwo);
primaryStage.setScene(new Scene(classOne.getRootNode()));
primaryStage.show();
primaryStage.minWidthProperty().bind(Bindings.createDoubleBinding(() -> {
ClassTwo value = classTwoProperty.get();
double minWidth = value == null ? 75 : value.getWidth();
System.out.println("minWidth calculation [" + minWidth + "]");
return minWidth;
}, classTwoProperty, Bindings.select(classTwoProperty, "width")));
primaryStage.minHeightProperty().bind(Bindings.createDoubleBinding(() -> {
ClassTwo value = classTwoProperty.get();
double minHeight = value == null ? 75 : value.getWidth();
System.out.println("minHeight calculation [" + minHeight + "]");
return minHeight;
}, classTwoProperty, Bindings.select(classTwoProperty, "height")));
ClassTwo classTwoSecondInstance = new ClassTwo();
classTwoSecondInstance.setForcedWidth(100);
classOne.setClassTwo(classTwoSecondInstance);
}
}
ClassOne.java
public final class ClassOne {
private final HBox rootNode = new HBox();
public ClassOne() {
classTwo.addListener((observable, oldValue, newValue) -> {
if (oldValue != null) {
rootNode.getChildren().remove(oldValue.getRootNode());
}
if (newValue != null) {
rootNode.getChildren().add(newValue.getRootNode());
}
});
}
public HBox getRootNode() {
return rootNode;
}
//classTwo
private final ObjectProperty<ClassTwo> classTwo = new SimpleObjectProperty<>
(ClassOne.this, "classTwo", null);
public ObjectProperty<ClassTwo> classTwoProperty() {
return classTwo;
}
public void setClassTwo(ClassTwo value) {
classTwo.set(value);
}
public ClassTwo getClassTwo() {
return classTwo.get();
}
}
ClassTwo.java
public final class ClassTwo {
private final VBox rootNode = new VBox();
public ClassTwo() {
rootNode.minWidthProperty().bind(forcedWidth);
rootNode.maxWidthProperty().bind(forcedWidth);
width.bind(rootNode.widthProperty());
height.bind(rootNode.heightProperty());
}
public VBox getRootNode() {
return rootNode;
}
//width
private final ReadOnlyDoubleWrapper width = new ReadOnlyDoubleWrapper
(ClassTwo.this, "width", 50);
public ReadOnlyDoubleProperty widthProperty() {
return width.getReadOnlyProperty();
}
public double getWidth() {
return width.get();
}
//height
private final ReadOnlyDoubleWrapper height = new ReadOnlyDoubleWrapper
(ClassTwo.this, "height", 50);
public ReadOnlyDoubleProperty heightProperty() {
return height.getReadOnlyProperty();
}
public double getHeight() {
return height.get();
}
//forcedWidth
private final DoubleProperty forcedWidth = new SimpleDoubleProperty
(ClassTwo.this, "forcedWidth", 150.0);
public DoubleProperty forcedWidthProperty() {
return forcedWidth;
}
public void setForcedWidth(double value) {
forcedWidth.set(value);
}
public double getForcedWidth() {
return forcedWidth.get();
}
}
回答1:
The issue is my fault. When I gave a solution to your other question I forgot to take into account that the objects returned by the selectXXX
methods are bindings themselves. Bindings (and really all observables) are as lazy as possible in core JavaFX. When they're invalidated they simply notify any registered InvalidationListener
and will remain invalid until their value is queried1. If a binding is invalid it can't become "more invalid" so (potentially) changing the value of an already-invalid binding does not notify any listeners.
And that's where the issue lies: The createDoubleBinding
is dependent on a select
binding that is never queried.
The following should solve the problem:
DoubleBinding width = Bindings.selectDouble(classTwoProperty, "width");
primaryStage.minWidthProperty().bind(Bindings.createDoubleBinding(() -> {
ClassTwo value = classTwoProperty.get();
if (value == null) {
return 75.0;
}
// query the 'width' binding in order to validate it
return width.get();
}, classTwoProperty, width));
I have also updated the solution in my answer to your other question.
1. Querying an Observable
makes it valid again. Note that if an ObservableValue
has a ChangeListener
registered then it stops being lazy (i.e. becomes eager). That's due to the fact that a ChangeListener
requires both the old value and the new value in order to function properly.
来源:https://stackoverflow.com/questions/65482519/bindings-not-updating-properly