3

I have designed a scene using JavaFX Scene Builder/FXML and I want to create many instances of that scene, but each scene with different behavior. Is there a way to change the controller of a scene/FXML dynamically?

What I want is to design one scene and reuse it, but with different behaviors for each instance.


Currently I am loading the FXML and its controller like this:

FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(fxmlFile));
Parent root = (Parent) fxmlLoader.load();
Scene scene = new Scene(root);
Controller controller = fxmlLoader.getController();
ceklock
  • 6,143
  • 10
  • 56
  • 78
  • 2
    [this](http://stackoverflow.com/questions/15964832/javafx-1-fxml-file-with-multiple-different-controllers) might help. – bcorso Sep 15 '13 at 02:44

1 Answers1

0

There is the possibility to use a custom Controllerfactory with the FXML loader. The setControllerFactory method 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.

Dávid Tóth
  • 2,788
  • 1
  • 21
  • 46