Let's assume we have a root window with fx:include
:
<?import javafx.scene.layout.VBox?>
<VBox fx:controller="sample.StartWindowController"
xmlns:fx="http://javafx.com/fxml" alignment="center">
<fx:include source="startwindow.fxml"/>
</VBox>
Code of startwindow.fxml
:
<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.VBox?>
<VBox fx:id="mainPane" fx:controller="sample.StartWindowController"
xmlns:fx="http://javafx.com/fxml" alignment="center">
<Button text="Go to New Window" onAction="#goToNewWindow"/>
</VBox>
Clicking Button
changes window to the new new one. Its controller, StartWindowController
:
package sample;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import java.io.IOException;
public class StartWindowController {
@FXML
VBox mainPane;
@FXML
private void goToNewWindow() {
Pane parentPane = (Pane) mainPane.getParent();
parentPane.getChildren().clear();
try {
parentPane.getChildren().add(FXMLLoader.load(getClass().getResource("newwindow.fxml")));
} catch (IOException e) {
e.printStackTrace();
}
}
}
Before I show you view and controller of a 'New Window', you must know that an application has a Singleton class with BooleanProperty
field. Code of MySingleton
:
package sample;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
public class MySingleton {
private static MySingleton instance;
private BooleanProperty booleanProperty;
private MySingleton() {
booleanProperty = new SimpleBooleanProperty(false);
}
public static MySingleton getInstance() {
if (instance == null) {
instance = new MySingleton();
}
return instance;
}
public boolean isBooleanProperty() {
return booleanProperty.get();
}
public BooleanProperty booleanPropertyProperty() {
return booleanProperty;
}
}
Code of newwindow.fxml
:
<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.VBox?>
<VBox fx:id="mainPane" fx:controller="sample.NewWindowController"
xmlns:fx="http://javafx.com/fxml" alignment="center">
<Button text="Change BooleanProperty" onAction="#changeBooleanProperty"/>
<Button text="Back" onAction="#goBack"/>
</VBox>
During 'New Window' creation I add a listener to the MySingleton
's BooleanProperty
in controller's initialize
method. New listener's code refers to the non-static, controller's private method, printMessage
. NewWindowController
's code:
package sample;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import java.io.IOException;
public class NewWindowController {
private MySingleton mySingleton;
@FXML VBox mainPane;
private int duplicateCounter = 1;
@FXML private void initialize() {
System.out.println("Initializing New Window's Controller.");
System.out.println("Duplicate counter: " + duplicateCounter);
mySingleton = MySingleton.getInstance();
mySingleton.booleanPropertyProperty().addListener(
(observable, oldValue, newValue) -> printMessage());
duplicateCounter++;
}
@FXML private void changeBooleanProperty() {
mySingleton.booleanPropertyProperty().setValue(!mySingleton.booleanPropertyProperty().getValue());
}
@FXML private void goBack() {
Pane parentPane = (Pane) mainPane.getParent();
parentPane.getChildren().clear();
try {
parentPane.getChildren().add(FXMLLoader.load(getClass().getResource("startwindow.fxml")));
} catch (IOException e) {
e.printStackTrace();
}
}
private void printMessage() {
System.out.println("Boolean property changed!");
}
}
Now, the problem. Assuming the first step is going to the 'New Window':
After every "Back" -> "Go to New Window" sequence, when I click "Change BooleanProperty" there are i+1
prints of "Boolean property changed!", where i
is number of 'Start Window' -> 'New Window' transitions (starting from 0
). Why isn't there only one print?
I'm aware of the fact that every time I launch 'New Window', application adds new listener to the BooleanProperty
and problem is probably caused by a number of property's listeners. But how is it possible if in a new listener's code I refer to the non-static method of the object which is destroyed after window transition?
I thought "Maybe controller is not destroyed? Maybe initialize
method works in a way I don't understand and controller's object is still there?" So as you probably see, I added an extra variable, duplicateCounter
which increments at the end of the initialize
method. But every time it's 1
, so I assume the brand new NewWindowController
object is created.
How can I prevent BooleanProperty
from listeners' duplication?
But how is it possible if in a new listener's code I refer to the non-static method of the object which is destroyed after window transition?
I thought "Maybe controller is not destroyed? Maybe
initialize
method works in a way I don't understand and controller's object is still there?" So as you probably see, I added an extra variable,duplicateCounter
which increments at the end of theinitialize
method. But every time it's1
, so I assume the brand newNewWindowController
object is created.
Indeed a new controller is created every time the fxml is loaded, however the old one isn't "destroyed" (available for garbage collection), since there is still a reference to the object:
MySingleton
stores the instance in a static member, making it unavailable for garbage collection. This instance contains a reference to the BooleanProperty
which contains a reference to the listener which contains a reference to the NewWindowController
.
To only print the message once, you have to unregister the listener:
private final ChangeListener<Boolean> listener = (observable, oldValue, newValue) -> printMessage();
@FXML private void initialize() {
...
mySingleton.booleanPropertyProperty().addListener(listener);
...
}
...
@FXML private void goBack() {
// remove listener
MySingleton.getInstance().booleanPropertyProperty().removeListener(listener);
...
}