4

While trying to load an FXML file, one usually does something like the following:

FXMLLoader loader = FXMLLoader.load(getClass().getResource("File.fxml"));
Region root = loader.load();
Stage stage = new Stage();
stage.setScene(new Scene(root));
stage.show();

However, as I tried to put the loading code into the controller for "caller convenience" I did the following:

public Controller()
{
   FXMLLoader loader = new FXMLLoader(getClass().getResource(fxml));
   loader.setController(this);
   Parent root = loader.load();
   Stage stage = new Stage();
   stage.setScene(new Scene(root));
   stage.show();
}

This worked quite good, because now I just had to call the constructor to create the new window.

But I had to remove the

fx:controller="package.Class"

attribute in the FXML file, because otherwise an Exception ("javafx.fxml.LoadException: controller is already set") was thrown when I invoked the

fxmlloader.setController(this);

method in the constructor. Since I'm using NetBeans and its "Make Controller" feature (right-click on the FXML file), the controller class can't be created because of the missing attribute.

Summary:

What I want to achieve is a way to have the "fx:controller" attribute still set in the FXML (for NetBeans) and also being able to load the FXML conveniently inside the Controller class.

Is this possible, or do I need some kind of wrapper class which does create the FXML window(s)?

Thanks in advance.

Ignatiamus
  • 296
  • 1
  • 12

1 Answers1

5

You can do this:

public Controller()
{
   FXMLLoader loader = new FXMLLoader(getClass().getResource(fxml));
   loader.setControllerFactory(type -> {
       if (type == Controller.class) {
           return this ;
       } else {
           try {
               return type.newInstance();
           } catch (RuntimeException e) {
               throw e ;
           } catch (Exception e) {
               throw new RuntimeException(e);
           }
       }
   });
   Parent root = loader.load();
   Stage stage = new Stage();
   stage.setScene(new Scene(root));
   stage.show();
}

which will allow you to (in fact, you will need to) have the fx:controller attribute in the FXML file. Basically what this does is specifies a function that the FXMLLoader can use to get the controller instance from the specified class. In this case, if the FXML Loader is looking for an object of the Controller class, it returns the current object, otherwise just creates a new object of the specified class.

James_D
  • 201,275
  • 16
  • 291
  • 322
  • Great, this works! However, when I try to nest another FXML file into the first one with ``, the `IllegalArgumenException` is thrown, because the controller factory I set is being used for the creation of the second controller as well! So first time, `type = Controller.class`, and second time `type = SecondController.class`. Why is this happening? – Ignatiamus Jun 12 '17 at 06:40
  • 2
    @Ignatiamus The `FXMLLoader` will use the same controller factory for loading the included FXML files as it uses for the "main" FXML file. I updated the answer to provide code that addresses this use case. – James_D Jun 12 '17 at 12:03
  • Aahh! I didn't think of just calling the `newInstance()` method, thanks for that. It's good to have some experts around. – Ignatiamus Jun 12 '17 at 12:59
  • Using JavaFX 20 this still works! FYI, if you want to add this element to an existing Stage (like when you add your custom component in another FXML file somewhere), replace the last 3 lines of the `new Stage()` portion with `getChildren().add(root);` – Justin Anthony Jun 07 '23 at 20:19