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);
}
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With