13

I use JavaFX NumberBindings in order to calculate certain values. Initially everything works as expected. After a rather small amount of time, however, the binding just stops working. I don't receive an Exception, either.

I've tried several bindings, as well as high- and low-level approaches. Even the calculation itself (when overridden) just stops and isn't called anymore. I've also updated to the latest JDK (1.8.0_05) and rebuilt/restarted everything.

The following Minimal Working Example illustrates the problem. It should System.out.println the current width of the main window to STDOUT. After resizing the window for about 10 seconds, the output simply stops. I've also tried to bind the resulting property to a JavaFX control, in order to ensure the Property's continued usage, but that was of no avail. I believe I'm missing some very basic behaviour of the Property/Bindings here, Google doesn't seem to know that behaviour at all.

import javafx.application.Application;
import javafx.beans.binding.NumberBinding;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class BindingsProblem extends Application {

@Override
public void start(Stage primaryStage) {
    // Initialization...
    StackPane root = new StackPane();
    Scene scene = new Scene(root, 300, 250);
    primaryStage.setScene(scene);
    primaryStage.show();


    // Binding - The problem occurrs here!
    NumberBinding currentWidthPlusTen = primaryStage.widthProperty().add(10);
    IntegerProperty boundNumberProperty = new SimpleIntegerProperty();
    boundNumberProperty.bind(currentWidthPlusTen);
    boundNumberProperty.addListener(new ChangeListener<Number>() {

        @Override
        public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
            System.out.println(newValue.toString());
        }

    });
}


public static void main(String[] args) {
    launch(args);
}

}
underkuerbis
  • 325
  • 3
  • 9
  • I can reproduce the problem. It looks like a bug. Have you searched on jira? – assylias May 21 '14 at 14:21
  • I can't seem to find existing bug reports. It seems to be something so basic, it's hard to imagine it being a bug in the JDK/JRE itself. – underkuerbis May 21 '14 at 15:09
  • The question (just as James_D's answer) can be simplified by removing the `boundNumberProperty` variable and adding the listener directly to `currentWidthPlusTen `. The problem persists and the solution still fixes it. – bruno Mar 25 '23 at 13:56

1 Answers1

24

The binding uses a WeakListener to observe the value of currentWidthPlusTen. Since you don't keep a reference to the boundNumberProperty, it is eligible for garbage collection as soon as the start(...) method exits. When the garbage collector kicks in, the reference is lost entirely and the binding no longer works.

To see this directly, add the line

root.setOnMousePressed( event -> System.gc());

to the start(...) method. You can force the listener to "stop working" by clicking on the window.

Obviously, that's not what you want: the fix is to retain the reference to boundNumberProperty after start(...) exits. For example:

import javafx.application.Application;
import javafx.beans.binding.NumberBinding;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class BindingsProblem extends Application {

    IntegerProperty boundNumberProperty;

    @Override
    public void start(Stage primaryStage) {
        // Initialization...
        StackPane root = new StackPane();
        Scene scene = new Scene(root, 300, 250);
        primaryStage.setScene(scene);
        primaryStage.show();

        // Binding - The problem occurrs here!
        NumberBinding currentWidthPlusTen = primaryStage.widthProperty()
                .add(10);

        boundNumberProperty = new SimpleIntegerProperty();
        boundNumberProperty.bind(currentWidthPlusTen);
        boundNumberProperty.addListener(new ChangeListener<Number>() {

            @Override
            public void changed(ObservableValue<? extends Number> observable,
                    Number oldValue, Number newValue) {
                System.out.println(newValue.toString());
            }

        });

    }

    public static void main(String[] args) {
        launch(args);
    }

}

Update

Anyone running into this issue might also want to look at Tomas Mikula's ReactFX, which provides a cleaner workaround for this (at the expense of using a third-party library, which you would need to spend some time learning). Tomas explains this issue and how ReactFX resolves it in this blog and the subsequent post.

James_D
  • 201,275
  • 16
  • 291
  • 322
  • Very good! This solved my problem. I tend to trust and rely on automatic memory/reference management in Java. Obviously too much :D – underkuerbis May 21 '14 at 17:35
  • Interestingly, most times you actually use this for anything real, outside the simple types of tests in your sample code, the problem goes away. For example, if you create a label and bind its text to your `IntegerProperty`, then of course the label effectively keeps a reference the the property, so it doesn't get garbage collected. Just occasionally this problem shows up in a real scenario, but it's very rare. – James_D May 21 '14 at 20:10
  • This issue is driving me nuts as a ton of my code that worked in 2.x now "mysteriously" stops working. Wish one could at least control when `WeakListeners` are used... – metasim Jan 09 '15 at 15:41