37

I am working on JavaFX application right now. All my gui is in .fxml format and through controller class manages all GUI components. However, I have difficulties with instantiating the controller class before I load FXML loader. I was unable to find a good solution for my question from others on stackoverflow, therefore this is not a duplicate question.

The reason why I am instantiating the controller class is that I want to pass some parameters so that these parameters will be displayed in GUI.

I am loading an FXML file the following way:

/*
 * for Work Order button
 */
@FXML
private void pressWorkOrder() throws Exception{ 
    WorkOrderController wo = new WorkOrderController("ashdkjhsahd");    //instantiating constructor     

    Parent parent = FXMLLoader.load(getClass().getResource("/fxml/WorkOrder.fxml"));        
    Scene scene = new Scene(parent);
    Stage stage = new Stage();
    stage.setScene(scene);
    stage.setTitle("Word Order");
    stage.setResizable(false);
    stage.show();
}

And here is my actual Controller class:

public class WorkOrderController implements Initializable{

     @FXML
     private Button summary;
     private String m,n;

     public WorkOrderController(String str) {
         // TODO Auto-generated constructor stub
         m = str;
     }  

     //for testing
     public void set(String str){
         m = str;
     }  

     @FXML
     public void check(){
         System.out.println("Output after constructor was initialized " + m);
     }

     @Override
     public void initialize(URL location, ResourceBundle resources) {
        // TODO Auto-generated method stub
     }
 }

And I get this Exception:

at javafx.fxml.FXMLLoader.processStartElement(Unknown Source)
at javafx.fxml.FXMLLoader.loadImpl(Unknown Source)
at javafx.fxml.FXMLLoader.loadImpl(Unknown Source)
at javafx.fxml.FXMLLoader.loadImpl(Unknown Source)
at javafx.fxml.FXMLLoader.loadImpl(Unknown Source)
at javafx.fxml.FXMLLoader.loadImpl(Unknown Source)
at javafx.fxml.FXMLLoader.loadImpl(Unknown Source)
at javafx.fxml.FXMLLoader.loadImpl(Unknown Source)
at javafx.fxml.FXMLLoader.load(Unknown Source)
at MainController.pressWorkOrder(MainController.java:78)
... 57 more
Caused by: java.lang.InstantiationException: WorkOrderController
at java.lang.Class.newInstance(Unknown Source)
at sun.reflect.misc.ReflectUtil.newInstance(Unknown Source)
... 71 more
Caused by: java.lang.NoSuchMethodException: WorkOrderController.<init>()
at java.lang.Class.getConstructor0(Unknown Source)
... 73 more
Zafar
  • 1,111
  • 2
  • 11
  • 23
  • 3
    Possible duplicate of [Passing Parameters JavaFX FXML](http://stackoverflow.com/questions/14187963/passing-parameters-javafx-fxml) – fabian Jan 01 '17 at 10:07
  • Better answer below the accepted one: https://stackoverflow.com/a/66190527/12643240 – Nand Nov 27 '21 at 15:54

2 Answers2

84

The two easiest ways of doing it for small applications are :

  1. Do not specify the fx:controller in the fxml. Create a controller instance by passing data to it and then pass it to the FXMLLoader.

  2. Specify the fx:controller in the fxml. Fetch the controller instance from the FXMLLoader and pass the data to the controller.

The following are the examples for both the above said types. Each of the example have 3 components :

  • FXML - The FXML file, which doesn't have the fx:controller declaration for the first type and has it for the second type.
  • Controller - Has a constructor for the first type. Has setter methods for the second type.
  • Main - Used for loading FXML and pass data to the controller. For first case, it sets the controller to FXMLLoader. While in second, it fetches the controller from the FXMLLoader.

1. Create a controller instance manually

FXML - Do not specify the fx:controller

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

<?import javafx.scene.layout.FlowPane?>
<?import javafx.scene.control.Label?>

<FlowPane fx:id="root" xmlns:fx="http://javafx.com/fxml">
    <children>
        <Label fx:id="firstName" text="" />
        <Label fx:id="lastName" text="" />
    </children>
</FlowPane>

Controller - create a Constructor to accept default values

import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;

import java.net.URL;
import java.util.ResourceBundle;

public class SampleController implements Initializable {

    private StringProperty firstNameString = new SimpleStringProperty();
    private StringProperty lastNameString = new SimpleStringProperty();

    /**
     * Accepts the firstName, lastName and stores them to specific instance variables
     * 
     * @param firstName
     * @param lastName
     */
    public SampleController(String firstName, String lastName) {
        firstNameString.set(firstName);
        lastNameString.set(lastName);
    }

    @FXML
    Label firstName;

    @FXML
    Label lastName;

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        firstName.setText(firstNameString.get());
        lastName.setText(lastNameString.get());
    }
}

Main - Create a Controller instance, by passing value into it and then pass it to the FXMLLoader

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

public class Main extends Application {
    @Override
    public void start(Stage primaryStage) throws Exception {
        FXMLLoader loader = new FXMLLoader(getClass().getResource("Sample.fxml"));

        // Create a controller instance
        SampleController controller = new SampleController("itachi", "uchiha");
        // Set it in the FXMLLoader
        loader.setController(controller);
        FlowPane flowPane = loader.load();
        Scene scene = new Scene(flowPane, 200, 200);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

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

2. Fetch a controller instance from FXMLLoader

FXML - Has specified the fx:controller

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

<?import javafx.scene.layout.FlowPane?>
<?import javafx.scene.control.Label?>

<!-- Controller Specified -->
<FlowPane fx:id="root" xmlns:fx="http://javafx.com/fxml" fx:controller="SampleController">
    <children>
        <Label fx:id="firstName" text="" />
        <Label fx:id="lastName" text="" />
    </children>
</FlowPane>

Controller - Has Setter methods to accept input

import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;

import java.net.URL;
import java.util.ResourceBundle;

public class SampleController implements Initializable {

    @FXML
    Label firstName;

    @FXML
    Label lastName;

    @Override
    public void initialize(URL location, ResourceBundle resources) {

    }

    /**
     * Accepts a String and sets it to the firstName Label
     *
     * @param firstNameString
     */
    public void setFirstName(String firstNameString) {
        firstName.setText(firstNameString);
    }

    /**
     * Accepts a String and sets it to the lastName Label
     *
     * @param lastNameString
     */
    public void setLastName(String lastNameString) {
        lastName.setText(lastNameString);
    }
}

Main - Fetches the Controller instance from FXMLLoader after calling the load() and then calls the setter methods to pass data.

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

public class Main extends Application {
    @Override
    public void start(Stage primaryStage) throws Exception {
        FXMLLoader loader = new FXMLLoader(getClass().getResource("Sample.fxml"));
        FlowPane flowPane = loader.load();
        // Get the Controller from the FXMLLoader
        SampleController controller = loader.getController();
        // Set data in the controller
        controller.setFirstName("itachi");
        controller.setLastName("uchiha");
        Scene scene = new Scene(flowPane, 200, 200);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}
ItachiUchiha
  • 36,135
  • 10
  • 122
  • 176
  • For larger applications, I prefer using a DI framework. This will also work, but DI makes your life easy ;) – ItachiUchiha Jun 14 '15 at 05:05
  • I haven`t used DI framework yet, can you give me advice from what should I start to learn about DI framework? – Zafar Jun 15 '15 at 15:36
  • 2
    It is not necessary to always use DI. Since you are new to this, lets keep it simple and use one of the two above methods. But, in case you want to know about [DI](https://en.wikipedia.org/wiki/Dependency_injection), you can look up for the two most famous DI framework - [Spring](http://docs.spring.io/spring/docs/current/spring-framework-reference/html/overview.html) and [Guice](https://github.com/google/guice/wiki/GettingStarted). You might also want to accept my answer :P – ItachiUchiha Jun 15 '15 at 15:42
  • @diegomatos-keke Correction: Leaf Village Coding* – TheLittleNaruto Jan 13 '17 at 09:05
  • 18
    Good answer, but both solutions are trade-off. If we do not specify the controller in the FXML we lose the ability to validate the event bindings, autocomplete etc. If we inject the parameters after JavaFX created the component we lose the ability to have a clean constructor with the parameters injected. Our fields can't be final any more. I wish there was a way to manually create it AND still have the FXML file refer to the class. – Pierre Henry Sep 04 '17 at 09:58
  • 1
    @PierreHenry Realize this may no longer be relevant to you, but you can use a [controller factory](https://openjfx.io/javadoc/12/javafx.fxml/javafx/fxml/FXMLLoader.html#setControllerFactory(javafx.util.Callback)). – Slaw Apr 01 '19 at 22:03
  • Using the first method, I am getting an error that reads `No controller specified for top level element`. Is there any way to resolve this? – qwerty Jul 16 '20 at 16:04
9

As a p has pointed out in the comments you can use a controller factory, and it's easier with a lambda function:

String data = "hsusanoo";

FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/WorkOrder.fxml"));

loader.setControllerFactory(controllerClass -> new WorkOrderController(data));

Parent root = loader.load();
Nand
  • 568
  • 3
  • 18
hsusanoo
  • 774
  • 7
  • 18
  • This should be the accepted answer. ControllerFactory is a very convenient way to get the best of both worlds. – Jody Sowald Sep 29 '22 at 17:36
  • I know this is old, but it should be pointed out that if you have `` elements, the same controller factory would be used for the included FXMLs which should have different controller classes. The controller factory would need to be considerably more complex in this case. – James_D Jan 27 '23 at 15:48