Inside the FXML file multiple other fxml files can be referenced by the <fx:include>
tag. Each fxml file can define their own controller with the fx:controller
attribute.
Inside the fx:controller
tag a type is given not an instance, so a custom Controllerfactory might be used when parsing it with the FXML loader.
The setControllerFactor
function expects a Callback
type, which is an interface with only one callable function: call
which the factory uses. The function (called by the FXMLLoader class) expects to get a Class
object as an input parameter and provides an instantiated object based on the type. Above Java8 lambdas can be used to provide the factory:
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(fxmlFile));
loader.setControllerFactory((Class<?> controllerType) ->{
if(controllerType == Controller.class){
return new Controller();
}
});
Parent root = (Parent) fxmlLoader.load();
The argument controllerType
in the above code is the the type provided by the fxml fx:controller
attribute, which is determined by the Java Class loader. When the Controllerfactory is called, nothing is instantiated yet, that's why even abstract classes can be given in the fxml. To achieve different behavior, inheritance can be used.
An example would be:
class Controller{...}
class FirstController extends Controller{...}
class SecondController extends Controller{...}
And the factory can be used like so:
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(fxmlFile));
final int[] instantiatedClasses = {0};
loader.setControllerFactory((Class<?> controllerType) ->{
if(controllerType == Controller.class){
if(0 == instantiatedClasses[0]){
++instantiatedClasses[0];
return new FirstController();
} else return new SecondController();
}
});
Parent root = (Parent) fxmlLoader.load();
Please Note, that this way different arguments can also be supplied to the controller, so inheritance might be an overkill.
For example the primaryStage can be provided to the controller, e.g. for eliminating the need for different setters in the controllers.
class Controller{
public Controller(int behaviorParam){...}
}
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(fxmlFile));
final int[] instantiatedClasses = {-1}; /* so it would start at 0 */
loader.setControllerFactory((Class<?> controllerType) ->{
if(controllerType == Controller.class){
++instantiatedClasses[0];
return new Controller(instantiatedClasses[0]);
}
});
Parent root = (Parent) fxmlLoader.load();
The challenges of this method, however is that it is still not straightforward to differentiate between the different fxml instances of the same controller type. Counting the number of instantiated classes is one way that works, but it doesn't give much control, compared to anything identifier based.
@FXML
tags. You have to set them all manually, which can be umbersome when having nested views. – Harwood