-1

I'm currently trying to implements a very basic application with JavaFX just to do some tests. The final goal here is to implement an interface separated in severals parts, and each part will have its own .fxm and controller.

For the beginning, I've tried to develop a basic application with this architecture :

Project explorer

I have a main VueGlobale.fxml file which include another .fxml file clavier.fxml :

VueGlobale.fxml

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>

<VBox
maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="529.0" xmlns="http://javafx.com/javafx/8.0.121" xmlns:fx="http://javafx.com/fxml/1">
   <children>
        <fx:include source="clavier.fxml" fx:id="clavier" />
   </children>
</VBox>

clavier.fxml

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

<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1" fx:controller="controllers.ClavierController">
   <children>
      <Button layoutX="274.0" layoutY="188.0" mnemonicParsing="false" onAction="#ajouterDo" text="Button" />
   </children>
</AnchorPane>

An here's the ClavierController.fxml : package controllers;

import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import model.Model;

public class ClavierController  {

    private Model model;

    public ClavierController(Model m) {
        this.model = m;
    }

    @FXML
    public void ajouterDo(ActionEvent e){
        System.out.println("Click !");
        this.model.doSomething();
    }
}

Model package model; public class Model {

public void doSomething() {
    System.out.println("Model !");
}

}

Main

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class Main extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception{
        FXMLLoader loader = new FXMLLoader();
        loader.setLocation(getClass().getResource("views/VueGlobale.fxml"));

        Parent root = loader.load();
        primaryStage.setTitle("Test");
        primaryStage.setScene(new Scene(root, 300, 275));
        primaryStage.show();
    }

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

The problem is : I don't know what to do in my main so that I can give to my clavier.fxml a ClavierController(Model m). (It's working with a controller without parameters, but what if I need to precise parameters ?)

Here's the StackTrace if it could help you :

Exception in Application start method
java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at com.sun.javafx.application.LauncherImpl.launchApplicationWithArgs(LauncherImpl.java:389)
    at com.sun.javafx.application.LauncherImpl.launchApplication(LauncherImpl.java:328)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at sun.launcher.LauncherHelper$FXHelper.main(Unknown Source)
Caused by: java.lang.RuntimeException: Exception in Application start method
    at com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:917)
    at com.sun.javafx.application.LauncherImpl.lambda$launchApplication$155(LauncherImpl.java:182)
    at java.lang.Thread.run(Unknown Source)
Caused by: javafx.fxml.LoadException: 
/J:/Programming/Telecom%20Nancy/S3/Test/bin/views/clavier.fxml:6
/J:/Programming/Telecom%20Nancy/S3/Test/bin/views/VueGlobale.fxml:9

    at javafx.fxml.FXMLLoader.constructLoadException(FXMLLoader.java:2601)
    at javafx.fxml.FXMLLoader.access$700(FXMLLoader.java:103)
    at javafx.fxml.FXMLLoader$ValueElement.processAttribute(FXMLLoader.java:932)
    at javafx.fxml.FXMLLoader$InstanceDeclarationElement.processAttribute(FXMLLoader.java:971)
    at javafx.fxml.FXMLLoader$Element.processStartElement(FXMLLoader.java:220)
    at javafx.fxml.FXMLLoader$ValueElement.processStartElement(FXMLLoader.java:744)
    at javafx.fxml.FXMLLoader.processStartElement(FXMLLoader.java:2707)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2527)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2441)
    at javafx.fxml.FXMLLoader.access$2700(FXMLLoader.java:103)
    at javafx.fxml.FXMLLoader$IncludeElement.constructValue(FXMLLoader.java:1143)
    at javafx.fxml.FXMLLoader$ValueElement.processStartElement(FXMLLoader.java:746)
    at javafx.fxml.FXMLLoader.processStartElement(FXMLLoader.java:2707)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2527)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2441)
    at javafx.fxml.FXMLLoader.load(FXMLLoader.java:2409)
    at Main.start(Main.java:14)
    at com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$162(LauncherImpl.java:863)
    at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$175(PlatformImpl.java:326)
    at com.sun.javafx.application.PlatformImpl.lambda$null$173(PlatformImpl.java:295)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.sun.javafx.application.PlatformImpl.lambda$runLater$174(PlatformImpl.java:294)
    at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
    at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at com.sun.glass.ui.win.WinApplication.lambda$null$148(WinApplication.java:191)
    ... 1 more
Caused by: java.lang.InstantiationException: controllers.ClavierController
    at java.lang.Class.newInstance(Unknown Source)
    at sun.reflect.misc.ReflectUtil.newInstance(Unknown Source)
    at javafx.fxml.FXMLLoader$ValueElement.processAttribute(FXMLLoader.java:927)
    ... 23 more
Caused by: java.lang.NoSuchMethodException: controllers.ClavierController.<init>()
    at java.lang.Class.getConstructor0(Unknown Source)
    ... 26 more
Exception running application Main

Thank you in advance for your help and for your time, have a good day.

EDIT :

By including several fxml, i mean this :

VueGlobale.fxml

<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<VBox
maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="529.0" xmlns="http://javafx.com/javafx/8.0.121" xmlns:fx="http://javafx.com/fxml/1">
   <children>
        <fx:include source="viewtop.fxml" fx:id="top" />
   </children>
<children>
        <fx:include source="clavier.fxml" fx:id="clavier" />
   </children>
<children>
        <fx:include source="viewbottom.fxml" fx:id="bottom" />
   </children>
</VBox>

So I have three .fxml file included in my VueGlobale.fxml. Let us supposed that all this .fxml have their own controller (ClavierController.java, TopController.java, BottomController.java). All these controllers needs the model. What should I do in my main factory ? Something like this doesn't work :

import controllers.ClavierController;
import controllers.TopController;
import controllers.BottomController;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import model.Model;

public class Main extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception{
        Model m = new Model();

        FXMLLoader loader = new FXMLLoader();
        loader.setLocation(getClass().getResource("views/VueGlobale.fxml"));
        loader.setControllerFactory(ic -> new ClavierController(m));
        loader.setControllerFactory(ic -> new TopController(m));
        loader.setControllerFactory(ic -> new BottomController(m));

        Parent root = loader.load();
        primaryStage.setTitle("Test");
        primaryStage.setScene(new Scene(root, 300, 275));
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}
  • The code you posted won't compile. Even if you tell us what `model` is, there is no constructor in `ClavierController` except the default constructor. Even if you fix that, there are likely to be other issues because you are expecting a `VueGlobaleController` for the first FXML, but the controller factory is returning a `ClavierController` – James_D Nov 14 '17 at 23:06
  • Hi, well thank you for the answer, that's true for the model, i've modified the code before posting here and I forget to change this part. Ok I understand what you mean, but what should i put in the main so that this event can be affected ? (Because even if I change the ClavierController to VueGlobaleController, i still get the same error message) – Paul Thirozier Nov 15 '17 at 17:50
  • So can you fix it please? It's not really possible for anyone here to distinguish between errors you made in your actual code (the ones that are causing the exception - you should post the complete stack trace, btw) and the errors you made in posting the code into the question. – James_D Nov 15 '17 at 17:52
  • Ok my apologies I correct it and post it just below, thank you – Paul Thirozier Nov 15 '17 at 17:56
  • Do not post it below; just [edit] the actual question. – James_D Nov 15 '17 at 17:57
  • Well, i've updated my question I think it's more understandable, thank you guy – Paul Thirozier Nov 15 '17 at 18:16
  • Where is the model going to come from? Where else do you need it? From the code you have posted, there's no reason you couldn't just instantiate it in the `ClavierController` class, but maybe you need a reference to it somewhere else that prevents you doing that? This looks very much like a duplicate of https://stackoverflow.com/questions/14187963/passing-parameters-javafx-fxml – James_D Nov 15 '17 at 18:19
  • Your previous edit had a controller factory. It seems that controller factory would fix the problem in this version of the code? What is wrong with that solution? – James_D Nov 15 '17 at 18:26
  • I actually need the model in some other .fxml file which will compose my interface. Well, when I add this line in my main just after the .setLocation() : loader.setControllerFactory(ic -> new ClavierController(model)); It's working, but what should I do if i want to add another .fxml in my VueGlobal . The question is; Is it possible to include several .fxml with their own controllers in a global one ? – Paul Thirozier Nov 15 '17 at 18:57
  • What do you mean by "include" here? For example, if your `VueGlobale.fxml` has a controller of its own, you can [inject the controllers for the included FXMLs into the "main" controller](https://docs.oracle.com/javase/9/docs/api/javafx/fxml/doc-files/introduction_to_fxml.html#nested_controllers). Or, you can use your controller factory and just check the parameter that is passed to it (which is the requested controller class). – James_D Nov 15 '17 at 19:03
  • Oh yes, i've already read this article but it's not what i want to achieve here, I've edited my first post at the bottom with my idea of several fxml inclusion. – Paul Thirozier Nov 15 '17 at 19:11
  • The `controllerFactory` is just a property like any other. Setting it to three different values make no sense; it will just have the last value that it was set to. As suggested earlier, make make a (single) `controllerFactory` that checks the value of the parameters it is passed. I still don't see why the "nested controller" solution wouldn't work either (give the main controller a reference to the model, then in its `initialize()` method you can pass the model to the "nested" controllers). – James_D Nov 15 '17 at 20:02

2 Answers2

1
    ButtonPane.fxml
    ----------------
    <?xml version="1.0" encoding="UTF-8"?>

    <?import com.jfoenix.controls.*?>
    <?import java.lang.*?>
    <?import javafx.scene.layout.*?>
    <?import javafx.scene.layout.AnchorPane?>

    <AnchorPane fx:id="buttonPane" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" prefHeight="30.0" prefWidth="700.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.everest.amcu.ButtonPaneController">
        <children>
            <HBox fx:id="hboxButton" alignment="CENTER_LEFT" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" spacing="5.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
                <children>
                    <JFXButton fx:id="btnAdd" maxHeight="1.7976931348623157E308" maxWidth="100.0" prefWidth="50.0" text="Add" HBox.hgrow="ALWAYS" />
                    <JFXButton fx:id="btnEdit" layoutX="10.0" layoutY="10.0" maxHeight="1.7976931348623157E308" maxWidth="100.0" prefWidth="50.0" text="Edit" HBox.hgrow="ALWAYS" />
                    <JFXButton fx:id="btnDelete" layoutX="62.0" layoutY="10.0" maxHeight="1.7976931348623157E308" maxWidth="100.0" prefWidth="50.0" text="Delete" HBox.hgrow="ALWAYS" />
                    <JFXButton fx:id="btnClose" layoutX="114.0" layoutY="10.0" maxHeight="1.7976931348623157E308" maxWidth="100.0" prefWidth="50.0" text="Close" HBox.hgrow="ALWAYS" />
                </children>
            </HBox>
        </children>
    </AnchorPane>



    PaneOne.fxml
    ---------------    
            <?xml version="1.0" encoding="UTF-8"?>
                      <?import javafx.scene.layout.AnchorPane?>
                      <AnchorPane xmlns:fx="http://javafx.com/fxml/1"       fx:controller="com.everest.amcu.PaneOneController">     
             <fx:include source="ButtonPane.fxml" fx:id="buttonPane" /> </AnchorPane>

ButtonPaneController
--------------------

import java.net.URL;
import java.util.ResourceBundle;
import com.jfoenix.controls.JFXButton;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.HBox;

public class ButtonPaneController implements Initializable {

    @FXML
    public JFXButton btnClose;

    @FXML
    public JFXButton btnDelete;

    @FXML
    public JFXButton btnAdd;

    @FXML
    public JFXButton btnEdit;

    @FXML
    private AnchorPane buttonPane;

    @FXML
    private HBox hboxButton;

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        btnClose.setOnAction(event -> {
            System.out.println("In Button Pane");
        });
        btnAdd.setOnAction(event -> {
            System.out.println("In Button Pane");
        });
    }

    public void hideButton(JFXButton... jfxButton) {
        for (int i = 0; i < jfxButton.length; i++) {
            hboxButton.getChildren().remove(jfxButton[i]);
        }
    }

}

PaneOneController
------------------
import java.net.URL;
import java.util.ResourceBundle;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;

public class PaneOneController implements Initializable {

    @FXML
    ButtonPaneController buttonPaneController;

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        buttonPaneController.btnAdd.setOnAction(event -> {
            System.out.println("In Pane One");
        });
    }
}

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.stage.Stage;
import javafx.stage.StageStyle;

public class Main extends Application {

    public static Stage primaryStage;
    public static Label lblTitle;

    @Override
    public void start(Stage primaryStage) {
        try {
            Main.primaryStage = primaryStage;
            Parent root = FXMLLoader.load(Main.class.getResource("view/PaneOne.fxml"));
            Scene scene = new Scene(root);
            scene.getStylesheets().add(Main.class.getResource("view/css/application.css").toExternalForm());
            primaryStage.setScene(scene);
            primaryStage.initStyle(StageStyle.UNDECORATED);
            primaryStage.show();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

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

Steps of doing this:

  1. Create ButtonPane.fxml and declare id of anchor pane and all the button.
  2. Create PaneOne.fxml and include Button.fxml using
    ,important thing is when you include fxml must be declare id same as anchor pane id of ButtonPane.fxml.
  3. Create ButtonPaneController for initialize variable.
  4. Create PaneOneController and just declare ButtonPaneController using @FXML annotation.
  5. Run the program, it's working well with action event of button.
Himanshu
  • 31,810
  • 31
  • 111
  • 133
  • @jewelsea please now check the answer, i have posted all the required things. Thanks – Rashmin Mevada Aug 31 '19 at 05:31
  • Yep, it's much better now :-). Also note that the [events can be associated with an FXML attribute](https://docs.oracle.com/javafx/2/api/javafx/fxml/doc-files/introduction_to_fxml.html#controller_method_event_handlers) by including an appropriate `onAction=#actionMethodName` attribute in the FXML and defining `public void actionMethodName(ActionEvent event)` in the controller. But that is not strictly necessary, it is perfectly fine to also code `setOnAction` in the initializer as in Rasmin's answer. – jewelsea Sep 03 '19 at 17:19
0

The controllerFactory is a property like any other property. If you set its value, it has that value, so it makes no sense to set its value to three different things in three consecutive lines of code.

You can create a controller factory that simply checks the parameter and returns the appropriate controller:

public class Main extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception{
        Model m = new Model();

        FXMLLoader loader = new FXMLLoader();
        loader.setLocation(getClass().getResource("views/VueGlobale.fxml"));
        loader.setControllerFactory(ic -> {
            if (ic == ClavierController.class) {
                return new ClavierController(m);
            } else if (ic == TopController.class) {
                return new TopController(m);
            } else if (ic == BottomController.class) {
                return new BottomController(m) ;
            }
            throw new IllegalArgumentException("Unexpected controller type: "+ic.getName());
        });

        Parent root = loader.load();
        primaryStage.setTitle("Test");
        primaryStage.setScene(new Scene(root, 300, 275));
        primaryStage.show();
    }

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

You can also use reflection to implement logic like "If the supplied controller class has a constructor taking a model, call that constructor and pass the model in, otherwise call the default constructor":

loader.setControllerFactory(ic -> {
    try {
        for (Constructor<?> c : ic.getConstructors()) {
            if (c.getParameterCount() == 1 && c.getParameterTypes()[0]==Model.class) {
                return c.newInstance(m);
            }
        }
        return ic.newInstance();
    } catch (Exception e) {
        // fatal...
        throw new RuntimeException(e);
    }
});

This is all a bit heavy-handed for what you are trying to achieve, though. You are merely trying to pass a model to the nested controllers (the controllers for the FXML files loaded via <fx:include>). The documentation explicitly provides an injection-based approach for this.

Specifically, if your "main" FXML adds fx:ids to the <fx:include>s, then the controllers can be injected into the main controller. So you can do something like:

<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<VBox
maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="529.0" xmlns="http://javafx.com/javafx/8.0.121" xmlns:fx="http://javafx.com/fxml/1">
   <children>
        <fx:include source="viewtop.fxml" fx:id="top" />
   </children>
<children>
        <fx:include source="clavier.fxml" fx:id="clavier" />
   </children>
<children>
        <fx:include source="viewbottom.fxml" fx:id="bottom" />
   </children>
</VBox>

Now define a "main" controller for this FXML file:

public class MainController {

    @FXML
    private Parent top ;
    @FXML
    private Parent clavier ;
    @FXML
    private Parent bottom ;

    @FXML
    private TopController topController ;
    @FXML
    private ClavierController clavierController ;
    @FXML
    private BottomController bottomController ;

    private final Model model ;

    public MainController(Model model) {
        this.model = model ;
    }

    @FXML
    public void initialize() {
        topController.setModel(model);
        clavierController.setModel(model);
        bottomController.setModel(model);
    }

    // ...
}

Just define the "nested" controllers with the appropriate methods for setting the model:

public class TopController {

    private Model model ;

    public void setModel(Model model) {
        this.model = model ;
    }

    @FXML
    private void someHandlerMethod(ActionEvent event) {
        model.doSomething();
    }
}

And finally load your main fxml with:

Model model = new Model();
FXMLLoader loader = new FXMLLoader(getClass().getResource("/path/to/fxml"));
loader.setController(new MainController(model));
Parent root = loader.load();

and it should all be good to go.

James_D
  • 201,275
  • 16
  • 291
  • 322
  • Oh god thank you for this awesome help guy ! I was trying to implement my own factory as you suggested 30 minutes ago but i didn't manage to make it working. Now I see what you mean and it's working ! Thank's a lot for your patience and have a good evening !! :) – Paul Thirozier Nov 15 '17 at 20:27
  • @PaulThirozier I would consider using the simpler approach (the nested controller approach) here, especially if you are new to Java. – James_D Nov 15 '17 at 20:28
  • Yes thank you, I've read all your answer and that was very interesting, I will consider your nested controllers approach :) – Paul Thirozier Nov 15 '17 at 20:33