Bindings not updating properly

[亡魂溺海] 提交于 2021-01-07 02:48:29

问题


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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!