I have a JavaFX application that uses FXML alongside a controller class written in Java. In the Java controller I need to take care not to operate on an FXML Node
element until it's been initialized (otherwise I'll get a NullPointerException), which isn't guaranteed until the initialize
method is run. So I find myself doing this a lot:
The controller is set in the FXML file like this:
<Pane fx:controller="Controller" ...>
...
</Pane>
And then here's the controller in the Java file.
class Controller{
@FXML
Pane aPane;
int globalValue;
public void setSomething(int value){
globalValue = value;
if(!(aPane == null)){ //possibly null if node not initialized yet
aPane.someMethod(globalValue)
}
}
@FXML
void initialize(){
aPane.someMethod(globalValue) //guaranteed not null at this point
}
}
This works, but it's clunky and repetitive. I have to create the globalValue
attribute just in case the setSomething
method is called before initialize
has been called, and I have to make sure the operations in my setSomething
method are identical to the operations in initialize
.
Surely there's a more elegant way to do this. I know that JavaFX has the Platform.runlater(...)
method that guarantees something will be run on the main application thread. Perhpas there's something like Platform.runAfterInitialize(...)
that waits until initialization, or runs immediately if initialization already happened? Or if there's another way to do it I'm open to suggestions.
If you specify the controller in the FXML file with fx:controller="Controller"
, then when you call FXMLLoader.load(...)
, the FXMLLoader
:
Controller
by (effectively) calling its no-arg constructor (or, in advanced usage, by invoking the controller factory if you set one)fx:id
into matching fields in the controller instanceinitalize()
on the controller instance (if such a method is defined)Only after load()
completes (i.e. after the @FXML
-annotated fields are injected) can you get a reference to the controller with loader.getController()
. So it is not possible (aside from doing something extremely unusual in a controller factory implementation) for you to invoke any methods on the controller instance until after the @FXML
-injected fields are initialized. Your null checks here are redundant.
On the other hand, if you use FXMLLoader.setController(...)
to initialize your controller, in which case you must not use fx:controller
, you can pass the values to the constructor. Simply avoiding calling a set
method on the controller before passing the controller to the FXMLLoader
means you can assume any @FXML
-annotated fields are initialized in the controller's public methods:
class Controller{
@FXML
Pane aPane;
int globalValue;
public Controller(int globalValue) {
this.globalValue = globalValue ;
}
public Controller() {
this(0);
}
public void setSomething(int value){
globalValue = value;
aPane.someMethod(globalValue)
}
@FXML
void initialize(){
aPane.someMethod(globalValue) //guaranteed not null at this point
}
}
and
FXMLLoader loader = new FXMLLoader(getClass().getResource("path/to/fxml"));
Controller controller = new Controller(42);
loader.setController(controller);
Node root = loader.load();
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