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.

1 Answer 1

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.

Sign up to request clarification or add additional context in comments.

4 Comments

Great, this works! However, when I try to nest another FXML file into the first one with <fx:include source="Second.fxml" />, 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 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.
Aahh! I didn't think of just calling the newInstance() method, thanks for that. It's good to have some experts around.
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);

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.