JavaFX Root loading problems

Viewed 1183

I am relatively new to JavaFX. I am trying to test design pattern where each fxml has its own controller with loader.

Example:.

text.fxml

<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Label?>

<VBox>
    <children>
        <Label text="Hello world FXML"/>
    </children>
</VBox>

MainController

public class MainController extends VBox {

    public MainController() {
        FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/test.fxml"));
        loader.setController(this);
        try {
            loader.load();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

App

public class App extends javafx.application.Application {

    @Override
    public void start(Stage primaryStage) {
        MainController mainController = new MainController();
        Stage stage = new Stage();
        stage.setScene(new Scene(mainController));
        stage.show();
    }

    public static void main(String[] args) {
        launch();
    }
}

This does not work, I get an empty window when application is started:

enter image description here

If make some changes to controller and App:

controller

private Parent root;

public MainController() {
    FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/test.fxml"));
    loader.setController(this);
    try {
        root = loader.load();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

public Parent getRoot() {
    return root;
}

App

stage.setScene(new Scene(mainController.getRoot()));

After this everything works as expected, Label with text can be seen in window.

If I try to set root in controller:

loader.setRoot(this);

I get an error:

javafx.fxml.LoadException: Root value already specified.
/C:/.../target/classes/fxml/test.fxml

    at javafx.fxml.FXMLLoader.constructLoadException(FXMLLoader.java:2621)
    ...

Question: Can anyone with more experience in JavaFX advise me on this? What am I doing wrong? Is there a better way to achieve this?

2 Answers

Since you're going for the custom component approach you need to make sure to:

  • set controller and root for the FXMLLoader before loading the fxml in the constructor of your component
  • use <fx:root> as root element of the fxml
  • not specify fx:controller in the fxml
<fx:root type="javafx.scene.layout.VBox" xmlns:fx="http://javafx.com/fxml">
    <children>
        <Label text="Hello world FXML"/>
    </children>
</fx:root>
public class MainController extends VBox {

    public MainController() {
        FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/test.fxml"));
        loader.setController(this);
        loader.setRoot(this);
        try {
            loader.load();
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }
}

In your first example you call 'loader.load()' but you're not using the return value of the method. You can try this.getChildren().add(loader.load())

As for the exception when calling setRoot, can you show all the relevant code?

Related