0

I know this question is asked many times, but I can't find a solution, that works for me (actually I can't even see what I am doing wrong).

The basic idea is to load GUI-components when needed. So I structured the GUI in various FXML-Files and implemented controller-classes. Both - FXML-files and classes - are stored in the same package but ther is a package for every component. Every FXML-file is loading and added to the GUI as long as I do not define the controller-class within the FXML-file (fx:controller). If it is defined I will get a LoadException.

For a better understanding here is my code (simplified):

Main.java:

package application;

import application.a.ControllerA;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

public class Main extends Application
{
    // Button aus MainLayout.fxml
    @FXML
    private Button button;

    @Override
    public void start(Stage primaryStage)
    {
        try
        {
            BorderPane root = new BorderPane();

            Parent contentMain = FXMLLoader.load(getClass().getResource("MainLayout.fxml"));
            ControllerA contentA = new ControllerA(root);

            root.setTop(contentA.getContent());
            root.setCenter(contentMain);

            Scene scene = new Scene(root, 400, 400);
            scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
            primaryStage.setScene(scene);
            primaryStage.show();
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }

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

    // Event-Handler für den Button -> funktioniert!
    @FXML
    public void buttonClicked(ActionEvent e)
    {
        if (!button.getText().equals("NEW"))
        {
            button.setText("NEW");
        }
        else
        {
            button.setText("OLD");
        }
    }
}

This class is also a controller for the following layout (and it works fins so far):

MainLayout.fxml:

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

<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.GridPane?>

<Pane xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.Main">
   <children>
      <Button fx:id="button" mnemonicParsing="false" onAction="#buttonClicked" text="Button" />
   </children>
</Pane>

In a sub-package (called a) of application you will find this:

ControllerA.java:

package application.a;

import java.net.URL;

import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;

public class ControllerA
{
    private Parent content;

    @FXML
    private Button buttonA;

    public ControllerA(BorderPane root)
    {
        String sceneFile = "A.fxml";
        URL url = null;
        try
        {
            url = getClass().getResource(sceneFile);
            content = FXMLLoader.load(url);
        }
        catch (Exception ex)
        {
                // TODO Auto-generated catch block
                e.printStackTrace();
        }
    }

    public Parent getContent()
    {
        return content;
    }

    @FXML
    public void clickedA(ActionEvent e)
    {
        buttonA.setText("Clicked already");
    }
}

A.fxml:

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

<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.GridPane?>

<Pane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.a.ControllerA">
   <children>
      <Button fx:id="buttonA" layoutX="274.0" layoutY="188.0" mnemonicParsing="false" onAction="#clickedA" text="A" />
   </children>
</Pane>

And this is, where it all went wrong:

javafx.fxml.LoadException: 
/Z:/BachelorArbeit/Projektdateien/Entwicklung/EclipseWorkspace/Sandbox/bin/application/a/A.fxml:8

    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.loadImpl(FXMLLoader.java:3214)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3175)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3148)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3124)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3104)
    at javafx.fxml.FXMLLoader.load(FXMLLoader.java:3097)
    at application.a.ControllerA.<init>(ControllerA.java:26)
    at application.Main.start(Main.java:35)
    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)
    at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.InstantiationException: application.a.ControllerA
    at java.lang.Class.newInstance(Class.java:427)
    at sun.reflect.misc.ReflectUtil.newInstance(ReflectUtil.java:51)
    at javafx.fxml.FXMLLoader$ValueElement.processAttribute(FXMLLoader.java:927)
    ... 23 more
Caused by: java.lang.NoSuchMethodException: application.a.ControllerA.<init>()
    at java.lang.Class.getConstructor0(Class.java:3082)
    at java.lang.Class.newInstance(Class.java:412)
    ... 25 more

I've tried to fiddle around with the path-String like

  • "./a/A.fxml"
  • "/application/a/A.fxml"
  • "A.fxml"
  • "a/A.fxml"
  • ...

but nothing worked. I would be quite relieved if someone can halp me with this problem.

Marco
  • 59
  • 8
  • which is line 35 in `Main.java` ? – dumbPotato21 May 16 '17 at 10:38
  • Ups, I'm sorry: `ControllerA contentA = new ControllerA(root);` – Marco May 16 '17 at 10:41
  • and line 26 in `ControllerA.java` ? – dumbPotato21 May 16 '17 at 10:42
  • `content = FXMLLoader.load(url);` – Marco May 16 '17 at 10:42
  • give your project structure a bit. It seems as if Java can't locate the fxml file. Also, have a look [here](http://stackoverflow.com/questions/31135192/fxmlloader-constructloadexception-when-trying-to-run-java-fx-application) – dumbPotato21 May 16 '17 at 10:46
  • There are no more packages than `application` and `application.a` and the classes/fxml-file I posted. I have got this problem in any structure. I used SceneBuilder 2.0 where some people addressed several issues with. But even if I write the FXML-file in a simple editor it goes wrong. The project itself is built in a "standard" Eclipse structure. – Marco May 16 '17 at 10:50
  • It doesen't even work, if there is only one package with all classes and FXML-files in it. I've tried it all out. – Marco May 16 '17 at 10:56

2 Answers2

0

I'll show you an minimal working example project and I suggest that you adapt your structure to it.

The folder structure is as follows

enter image description here

Here is the main class package dynamic.content.javafx;

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

public final class Main extends Application {
    @Override
    public void start(Stage stage) throws Exception {
        Parent root = FXMLLoader.load(getClass().getResource("main.fxml"));
        Scene scene = new Scene(root, 300, 200);

        stage.setTitle("FXML Welcome");
        stage.setScene(scene);
        stage.show();
    }

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

The main controller

package dynamic.content.javafx;

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

import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.Parent;
import javafx.scene.control.Button;
import javafx.scene.layout.AnchorPane;

public final class MainController implements Initializable{

    @FXML
    private Button btnSwitch;

    @FXML
    private AnchorPane contentPane;

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

    private boolean swtch = false;
    @FXML
    public void handleContentSwitch() throws IOException{
        Parent contentNode = null;

        if(swtch){
            System.out.println("loading content A");
            contentNode = FXMLLoader.load(getClass().getResource("./content/content_a.fxml"));          
        }else{
            System.out.println("loading content B");
            contentNode = FXMLLoader.load(getClass().getResource("./content/content_b.fxml"));
        }
        contentPane.getChildren().clear();
        contentPane.getChildren().add(contentNode);

        swtch = !swtch;
    }

}

The main FXML

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

<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>


<VBox alignment="TOP_CENTER" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="200.0" prefWidth="300.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="dynamic.content.javafx.MainController">
   <children>
      <Button fx:id="btnSwitch" onAction="#handleContentSwitch" alignment="CENTER" contentDisplay="CENTER" mnemonicParsing="false" text="Switch Content" />
      <AnchorPane fx:id="contentPane" prefHeight="150.0" prefWidth="300.0" />
   </children>
</VBox>

Let's say we have content A and B we need to create a controller and a FXML every one of them

package dynamic.content.javafx.content;

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

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

public class ContentAController implements Initializable{

    @FXML
    private Label labl;

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        labl.setText("Content A");
    }

}

And now the corresponding FXML

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

<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>

<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="150.0" prefWidth="300.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="dynamic.content.javafx.content.ContentAController">
   <children>
      <Label fx:id="labl" alignment="CENTER" contentDisplay="CENTER" text="Label" textAlignment="CENTER" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
   </children>
</AnchorPane>

Here follows the second controller

package dynamic.content.javafx.content;

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

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

public class ContentBController implements Initializable{

    @FXML
    private Label labl;

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        labl.setText("Content B");
    }

}

and the second FXML

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

<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>


<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="150.0" prefWidth="300.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="dynamic.content.javafx.content.ContentBController">
   <children>
      <Label fx:id="labl" alignment="CENTER" contentDisplay="CENTER" text="Label" textAlignment="CENTER" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
   </children>
</AnchorPane>

If you know press the button the content will be loaded dynamically

If you have any questions, just let me know :-)

Westranger
  • 1,308
  • 19
  • 29
  • For this minimal working example it works fine for me, too. But what I want to do, is loading the GUI-Elements only if needed. So far I understand there is no other way as loading these files seperately. In another way: If I want to load a HBox (with some content that is controlled by a controller-class) into the VBox (in yout example) how do I do that, when both (the VBox and the HBox) are in seperate FXML-files)? What I did is working for the Main-class but why isn't it working for ControllerA and A.fxml? – Marco May 16 '17 at 11:15
  • ...even if I put them in the same package as Main/MainLayout.fxml and just correct the fx:controller in A.fxml – Marco May 16 '17 at 11:18
  • Thanks a lot for your help. – Marco May 16 '17 at 13:22
0

You're mixing two different ways of using FXML and controllers. If your FXML file has fx:controller="SomeClass", then the FXMLLoader will instantiate that class and use the instance as a controller: in other words, it makes the FXML create the controller.

On the other hand, your controller's constructor loads the FXML, so you also have the controller creating the UI defined in the FXML.

The reason for the exception is that when the FXMLLoader encounters the fx:controller attribute, it calls the no-argument constructor of the specified class: i.e. in this case it tries to call new ControllerA(). Since there is no such constructor you get the exception:

java.lang.NoSuchMethodException: application.a.ControllerA.<init>()

It's not really clear what the purpose of the BorderPane parameter to the controller's constructor is, as you never use it. However, even if you had an appropriate constructor here, it would cause another problem: loading the FXML would invoke the controller's constructor, which loads the FXML, which would invoke the controller's constructor, etc: you would get a StackOverflowException. If you want to load the FXML from the controller's constructor:

  1. remove the fx:controller attribute from the FXML file
  2. Explicitly set the controller on the FXMLLoader to the current controller instance:

    public ControllerA(BorderPane root)
    {
        String sceneFile = "A.fxml";
        URL url = null;
        try
        {
            url = getClass().getResource(sceneFile);
            FXMLLoader loader = new FXMLLoader(url);
            loader.setController(this);
            content = loader.load();
        }
        catch (Exception ex)
        {
                // TODO Auto-generated catch block
                e.printStackTrace();
        }
    }
    
James_D
  • 201,275
  • 16
  • 291
  • 322
  • This is the solution I was looking for! Exactly this point I seemed to be confused about. Thank you very much. – Marco May 16 '17 at 13:21