9

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?

Garog
  • 315
  • 1
  • 5
  • 15

1 Answers1

26

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.

James_D
  • 201,275
  • 16
  • 291
  • 322
  • 1
    Okay, i need a lot more knowledge, i don't understand your answer now. Edit. i hoped it could be easy like "startpagecontroller extends menucontroller" or something. i think i will still leave all the methodes for the menubar in each controller and the fxml code too.. :/ – Garog Mar 25 '15 at 14:50
  • 1
    I can't really help unless I know which parts you don't understand. Each fxml file has a single controller *instance* associated with it. The fxml file can specify a *class* for the controller. So the controller factory is just a function that tells the `FXMLLoader` how to get the controller instance from the specified class. Duplicating methods, or making one controller a subclass of the other, won't help because you still have two different controller instances (so they won't be referencing the same data). – James_D Mar 25 '15 at 15:11
  • 1
    The biggest problems for me are to understand your solution (just a java beginner) and/or adapt your example to my code. I will try to explain a little bit more why i need this all. First, as i started the project, every fxml file had its own controller, that was fine, i could include every fxml in each other without losing any functions. for example, i had a page in the application "createproject" this page contains some textfields and included the menubar. when i used the menuitems to switch the page (scene) to "settingsforcreateproject" i included the menubar in the new fxml again. – Garog Mar 25 '15 at 15:45
  • take e look here https://gist.github.com/Garog/ee979f4c50c76c554f86 everything was fine and more pleasant then working with swing or awt but then i needed data in scene a from scene b, like settings from the settings scene in the projectcreate scene. i created a model in my main class and then.. the problems started, how to share the model between the controller. i looked around and found some solutions that sound good, one was to set the controller http://stackoverflow.com/a/14190310/3710098 but this solution doesn't work with fxml include fxml.. and here i stand now.. – Garog Mar 25 '15 at 15:45
  • Well, surely my solution does exactly that: shares a model between the "main" controller and the controller for the included fxml. Which parts of the code don't you understand? – James_D Mar 25 '15 at 15:48
  • atm, most of it :/ Callback.. need to read about it first to understand the function. and i need to play with the code first, try to get it working in a single project to get an "overview" whats happening, where and why. How to use it with my onActions from the MenuBar. Im sure your code is helpful but i need to work with it to understand it. I don't know where to add your code and use it. I need to test it first and maybe i will see more clearly – Garog Mar 25 '15 at 16:04
  • I changed it complete. take a look here. it should also come near the mvc pattern and should be more understandable to beginners like me :) https://gist.github.com/Garog/59f45f4b1e7acb642c1c – Garog Mar 31 '15 at 08:32
  • it took me some time to get used to it but i'm very glad you helped out here! – Martin Frank Jun 25 '19 at 04:44