0

I am trying to use multiple fxml files in an application I am making, and in doing some research, I found that using Custom Controllers for the fxml files is the best approach towards doing this type of application.

I followed an Oracle Docs tutorial on "Mastering FXML" and set the root and controller as "this" in the CustomController.java file.

The problem arises when intellij discovers there is no controller specified in the fxml file for the onAction handler, while I am specifying the controller programmatically.

tester.java

package task01;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class tester extends Application
{
    @Override
    public void start(Stage stage) throws Exception
    {
        CustomController customController = new CustomController();

        customController.getStylesheets().add("/task01/stylize.css");

        stage.setScene(new Scene(customController,1920,1080));
        stage.setTitle("Seneca ATM Program");
        stage.setWidth(1920);
        stage.setHeight(1080);
        stage.show();
    }

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

CustomContoller.java

package task01;

import javafx.event.ActionEvent;
import javafx.scene.control.Button;
import javafx.scene.layout.GridPane;
import javafx.fxml.*;
import javafx.scene.layout.Pane;
import java.io.IOException;

public class CustomController extends GridPane
{

    @FXML
    private Pane viewableContent;

    @FXML
    private Button vigilanteButton;

    public CustomController()
    {
        FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("root.fxml"));
        fxmlLoader.setRoot(this);
        fxmlLoader.setController(this);

        try
        {
            fxmlLoader.load();
        } catch (IOException exception)
        {
            throw new RuntimeException(exception);
        }
    }

    @FXML
    private void vigilanteAction(ActionEvent actionEvent)
    {
        System.out.println("Hello, World");
    }
}

root.fxml

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

<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.GridPane?>

<?import task01.MainMenuController?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.Pane?>
<fx:root type="javafx.scene.layout.GridPane" xmlns:fx="http://javafx.com/fxml" alignment="CENTER">
    <ImageView fitWidth="229.67" fitHeight="149.67" GridPane.columnIndex="0" GridPane.rowIndex="0" GridPane.halignment="CENTER">
        <Image url="/task01/logo.png"/>
    </ImageView>
    <Pane fx:id="viewableContent" GridPane.columnIndex="0" GridPane.rowIndex="1" GridPane.halignment="CENTER">
        <MainMenuController/>
    </Pane>
    <Button fx:id="vigilanteButton">Vigilante</Button>
</fx:root>

MainMenuController.java

package task01;

import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.Button;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import java.io.IOException;

public class MainMenuController extends GridPane
{
    private CustomController customController = new CustomController();

    public MainMenuController()
    {
        FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("mainmenu.fxml"));
        fxmlLoader.setRoot(this);
        fxmlLoader.setController(this);

        try
        {
            fxmlLoader.load();
        } catch (IOException exception)
        {
            throw new RuntimeException(exception);
        }
    }

    @FXML
    private VBox buttonSet;
    @FXML
    private HBox buttonSetOne;
    @FXML
    private HBox buttonSetTwo;
    @FXML
    private  Button changePinButton;
    @FXML
    private Button accountInquiryButton;
    @FXML
    private Button withdrawMoneyButton;
    @FXML
    private Button depositMoneyButton;
    @FXML
    private Button balanceInquiryButton;
    @FXML
    private Button createAccountButton;
    @FXML
    private GridPane gridpane;

    @FXML
    public void createAccountAction(ActionEvent actionEvent)
    {

    }
}

mainmenu.fxml

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

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

<?import javafx.geometry.Insets?>
<GridPane fx:id="gridpane" alignment="CENTER" vgap="50" hgap="50" xmlns:fx="http://javafx.com/fxml">
    <padding><Insets top="10" bottom="10" left="10" right="10"/></padding>
    <VBox fx:id="buttonSet" spacing="25" GridPane.columnIndex="0" GridPane.rowIndex="1">
        <HBox fx:id="buttonSetOne" spacing="25">
            <Button styleClass="menuButton" fx:id="createAccountButton" onAction="#createAccountAction">Create account</Button>
            <Button styleClass="menuButton" fx:id="changePinButton">Change PIN</Button>
            <Button styleClass="menuButton" fx:id="accountInquiryButton">Account Inquiry</Button>
        </HBox>
        <HBox fx:id="buttonSetTwo" spacing="25">
            <Button styleClass="menuButton" fx:id="withdrawMoneyButton">Withdraw Money</Button>
            <Button styleClass="menuButton" fx:id="depositMoneyButton">Deposit Money</Button>
            <Button styleClass="menuButton" fx:id="balanceInquiryButton">Balance Inquiry</Button>
        </HBox>
    </VBox>
</GridPane>

3 Answers 3

1

Here's the problem, you can bind a FXML file to a Controller from the controller, but when you do this the IDE doesn't know it until it's up and running. That's why the IDE causes you troubles. If you want to set the onAction handler you'll have to do it from the controller. You have to create a method like this and add the onAction listener to the button:

@FXML
public void initialize() {
    createAccountButton.setOnAction(new EventHandler<ActionEvent>() {

        @Override
        public void handle(ActionEvent event) {
            // TODO Auto-generated method stub
            createAccountAction(event);
        }
    });
}
Sign up to request clarification or add additional context in comments.

3 Comments

Once I implement this and add the onAction listener to the button; will I be handling the event inside the setOnAction, or will I need to implement another method to handle the event?
you can handle it inside the setOnAction method, I just put your createAccountAcction method in it so you could see how it works
This code can be shortened by using either a lambda expression or a method reference: createAccountButton.setOnAction(event -> createAccountAction(event)); or createAccountButton.setOnAction(this::createAccountAction);
0

Divide and conquer: break complex problems to smaller ones. Start with a simple setup like the following.

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.Pane;
import javafx.stage.Stage;

public class tester extends Application
{
    @Override
    public void start(Stage stage) throws Exception
    {
        CustomController customController = new CustomController();

        FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("root.fxml"));
        fxmlLoader.setController(customController);
        Parent root = (Parent)fxmlLoader.load();

        stage.setScene(new Scene(root,1920,1080));
        stage.setTitle("Seneca ATM Program");
        stage.show();
    }

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

//controller should be just that. It is not a pane 
class CustomController /* extends GridPane */
{

    @FXML
    private Pane viewableContent;

    @FXML
    private Button vigilanteButton;

    @FXML
    private void vigilanteAction(ActionEvent actionEvent)
    {
        System.out.println("Hello, World");
    }
}

root.fxml:

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

<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.GridPane?>
<!--  ?import task01.MainMenuController?-->
<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.Pane?>

<GridPane xmlns:fx="http://javafx.com/fxml" alignment="CENTER">
    <ImageView fitWidth="229.67" fitHeight="149.67" GridPane.columnIndex="0" GridPane.rowIndex="0"
     GridPane.halignment="CENTER">
        <Image url="task01/logo.png"/>  <!-- todo: set correct path -->
    </ImageView>
    <Button fx:id="vigilanteButton" onAction="#vigilanteAction">Vigilante</Button>
</GridPane>

I hope this helps you to structure your application.

2 Comments

Why comment out the MainMenuController in root.fxml? How to show mainmenu.fxml content without adding it in root.fxml?
Please see minimal reproducible example. The idea is to post the Minimal code that reproduces the error (or cure in this case)
0

There's no way for the IDE to know about you setting the controller yourself somewhere in your java code. Autocompletion features and the like are off for this reason. The fact that the IDE shows these kind of "issues" is unfortunate in this case, but they could indicate an issue that can only be recognized at runtime.

In your case assuming you don't have any typos in your fxml, it's safe to ignore these warnings.

You could of course simply add the fx:controller attribute temporarily while editing the fxml and remove it when you're done.

Another way of dealing with this issue would be specifying the fx:controller attribute in the fxml and setting the controllerFactory property instead of the controller property for the loader. I don't really like this approach though:

<GridPane fx:id="gridpane" alignment="CENTER" vgap="50" hgap="50" xmlns:fx="http://javafx.com/fxml" fx:controller="task01.MainMenuController">
    ...
public MainMenuController() {
    FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("mainmenu.fxml"));
    fxmlLoader.setRoot(this);

    fxmlLoader.setControllerFactory(clazz -> {
        if (!clazz.isInstance(this)) {
            throw new IllegalArgumentException(String.format("class of controller (%s) not assignable to class specified in fxml (%s)", this.getClass().getName(), clazz.getName()));
        }
        return this;
    });

    try {
        fxmlLoader.load();
    } catch (IOException exception) {
        throw new RuntimeException(exception);
    }
}

If you don't want to check assignment compatibility between the controller type specified in the fxml and the type of the controller you specify, the controllerFactory could be simplified to clazz -> this.

If you want to use this approach for multiple classes I strongly recommend creating a class for the controller factory to avoid repetitions, assuming you want to do the check...

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.