JavaFX 1 FXML File with Multiple Different Controllers?
Asked Answered
G

2

7

There are two different stages in my application that are help screens that use the same FXML file. Rather than create 2 FXML files, I would like to use just one and have two controllers that call the same fxml.

The only problem is that the Controller is assigned in the FXML file. So, is there a way to change the assigned controller with code in the Controller class itself?

I'd really like to avoid duplicating an FXML file just to change the Controller in each. Thanks in advance.


Greenebaum answered 12/4, 2013 at 6:46 Comment(0)
E
7

You can remove the fx:controller="" assignment from the FXML file and assign the controller via the FXMLLoader during the load.

FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("Your.fxml"));
fxmlLoader.setController(this);

try
{
    fxmlLoader.load();
}
catch (IOException exception)
{
    throw new RuntimeException(exception);
}

Check out the Introduction to FXML section on custom components.

Existence answered 12/4, 2013 at 13:3 Comment(1)
Using this approach prevents you from using @FXML tags. You have to set them all manually, which can be umbersome when having nested views.Harwood
D
0

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.

Defensible answered 17/3, 2021 at 7:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.