Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JavaFX include fxml in fxml without controller

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?

like image 489
Garog Avatar asked Mar 25 '15 13:03

Garog


People also ask

How do I add FXML to another FXML?

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.

Can you do JavaFX without FXML?

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.

What is FXML loader?

public class FXMLLoader extends Object. Loads an object hierarchy from an XML document. Since: JavaFX 2.0.


Video Answer


1 Answers

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.

like image 74
James_D Avatar answered Sep 29 '22 02:09

James_D