I'm programming an application with javafx. It's a "multiscreen" application with a mainmenu from where I can switch my scene.
My scenes are defined in different fxml files.
Because I try to use the mvc pattern I don't set the controller in the fxml files, I use setController on the FXMLloader.
Everything is working fine but I have the mainmenu and all its functions for the onActions in separate controller and in separate fxml file.
I've tried it with
<fx:include source="Menubar.fxml"/>
and created a controller for the fxml file, when I set the controller in the fxml file I can't compile the source. How do I set the controller for the included fxml file?
startpage.fxml gets its controller "Startpage" with
FXMLLoader loader = new FXMLLoader(getClass().getResource("../fxml/startpage.fxml"));
loader.setController(new Startpage(m));
Pane mainPane = loader.load();
startpage.fxml
includes menubar.fxml
, how to set a controller for the menubar controls now? Or how to include the menubarController easily in every other controller?
The <fx:include> tag can be used to include one fxml file into another. The controller of the included fxml can be injected into the controller of the including file just as any other object created by the FXMLLoader . This is done by adding the fx:id attribute to the <fx:include> element.
You don't have to use FXML or SceneBuilder. You can simply create the objects yourself and add them to your Scene/Stage yourself. It's entirely open as to how you implement it.
public class FXMLLoader extends Object. Loads an object hierarchy from an XML document. Since: JavaFX 2.0.
I think you need to use a controllerFactory
in the loader to achieve what you want here. When you use a controllerFactory
, you specify the classname of the controller in the FXML files, but the controller factory allows you to control how that is mapped to an object (so you can still construct it passing in a model, etc). When you specify a controllerFactory
for an FXMLLoader
, that factory is also used to create the controllers for any <fx:include>
s that you have in the FXML file.
Finally, note that you can inject the controller for the included fxml file into the "main" fxml file, as documented in the "Nested controllers" section of the FXML documentation.
So if startpage.fxml looks like this:
<!-- imports etc -->
<BorderPane fx:controller="com.example.Startpage" ... >
<top>
<fx:include source="Menubar.fxml" fx:id="menubar" />
</top>
<!-- etc ... -->
</BorderPane>
and Menubar.fxml looks like
<!-- imports etc -->
<MenuBar fx:controller="com.example.MenubarController" ... >
<!-- etc... -->
</MenuBar>
Then you can control the instantiation of the controller classes with:
FXMLLoader loader = new FXMLLoader(getClass().getResource("../fxml/startpage.fxml"));
Model m = ... ;
Startpage startpageController = new Startpage(m);
MenubarController menubarController = new MenubarController(m);
Callback<Class<?>, Object> controllerFactory = type -> {
if (type == Startpage.class) {
return startpageController ;
} else if (type == MenubarController.class) {
return menubarController ;
} else {
// default behavior for controllerFactory:
try {
return type.newInstance();
} catch (Exception exc) {
exc.printStackTrace();
throw new RuntimeException(exc); // fatal, just bail...
}
}
};
loader.setControllerFactory(controllerFactory);
Pane mainPane = loader.load();
Now you actually have references to both controllers in your application code, if you need, but you can also do
public class Startpage {
public final Model m ;
// note the name of this field must be xController,
// where x is the fx:id set on the <fx:include>:
@FXML
private final MenubarController menubarController ;
public Startpage(Model m) {
this.m = m ;
}
// ...
}
So the main controller now has a reference to the menu bar controller.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With