Search code examples
model-view-controllercontrollerfxmlloader

How to load an fxml file from a controller that is linked to a different fxml file?


I am building a JavaFX which has two windows: a signin window and a register window. The fxml file of the former has a "username" label and text field, a "password" label and text field, a "signin" button, a "close" button and a hyperlink that reads "or create a new user". I want that when users click on the hyperlink, a registration window pop-up with labels and text fields asking for the user to enter a user name, password, first name, etc.

I have a Login.fxml file and a LoginController.java file for the sign in window. And for the registration window I have a CreateUser.fxml file and a CreateUSerController.java file.

My problem is how to load the CreateUser.fxml file (and make the registration window pop-up) from the LoginController.java file. I handle the clicking on the "or create a new user" hyperlink event in LoginController.java, and basically the result I expect of the handling is to make the registration window to pop-up. I get a runtime error.

When I click on the "or create a new user" hyperlink in the login window I get the following error: The Eclipse debugger says "Thread[JavaFX Application Thread](Suspended(uncaught exception RuntimeException))". Also, when I check the value of the variables during execution there is a "runWithoutRenderLock() is throwing", and its value is RuntimeException. Also says that its cause is a "InvocationTargetException", and that the cause of this exception is a "null". Also, a "QuantumToolkit.class" tab appears in eclipse with the message "Source not found" "The JAR file ... has no source attachment". It also has a clickable button that reads "Attach source"

To load "CreateUser.fxml" from "LoginController.java" I used a handler which tries to load the fxml file with this line of code: "root = FXMLLoader.<Parent>load(CreateUserController.class.getResource("CreateUser.fxml"));". Here is where the exception occurs

Please find below my Main class which is where I load the "Login.fxml" file, the "Login.fxml", "LoginController.java", "CreateUser.fxml" and CreateUSerController.java files. Also, an image of my file structure.

File structure src /application -Main.java /controller -LoginController.java -CreateUserController.java /model /view -Login.fxml -CreateUser.fxml -module-info.java

File structure

Main.java

package application;
    
import java.io.IOException;

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


public class Main extends Application {
    @Override
    public void start(Stage signinStage) throws IOException {
        try {
            BorderPane root = (BorderPane)FXMLLoader.load(getClass().getResource("/view/Login.fxml"));
            Scene scene = new Scene(root,400,400);
            signinStage.setTitle("Welcome to MyHEalth");
            signinStage.setScene(scene);
            signinStage.show();
        } catch(Exception e) {
            e.printStackTrace();
        }
    }
    
    public static void main(String[] args) throws IOException {
        launch(args);
    }
}

Login.fxml

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

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Hyperlink?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>

<BorderPane xmlns="http://javafx.com/javafx/19" xmlns:fx="http://javafx.com/fxml/1" fx:controller="controller.LoginController">
   <center>
      <VBox alignment="CENTER" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="400.0" BorderPane.alignment="CENTER">
         <children>
            <Label maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="21.0" prefWidth="300.0" text="Username">
               <VBox.margin>
                  <Insets bottom="10.0" />
               </VBox.margin>
            </Label>
            <TextField fx:id="userNameField" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="25.0" prefWidth="300.0">
               <VBox.margin>
                  <Insets bottom="20.0" />
               </VBox.margin>
            </TextField>
            <Label prefHeight="17.0" prefWidth="300.0" text="Password">
               <VBox.margin>
                  <Insets bottom="10.0" />
               </VBox.margin>
            </Label>
            <TextField fx:id="passwordField" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="25.0" prefWidth="300.0" />
            <BorderPane prefHeight="100.0" prefWidth="200.0">
               <center>
                  <HBox maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="30.0" prefWidth="170.0" BorderPane.alignment="CENTER">
                     <children>
                        <Button fx:id="signinButton" maxWidth="-Infinity" mnemonicParsing="false" onAction="#signin" prefWidth="70.0" text="Sign in" />
                        <Button fx:id="closeButton" alignment="CENTER" maxWidth="-Infinity" mnemonicParsing="false" onAction="#close" prefWidth="60.0" text="Close" textAlignment="RIGHT">
                           <HBox.margin>
                              <Insets left="40.0" />
                           </HBox.margin>
                        </Button>
                     </children>
                  </HBox>
               </center>
               <bottom>
                  <Hyperlink fx:id="newUserLink" onAction="#openNewUserWindow" text="or create new user" BorderPane.alignment="CENTER" />
               </bottom>
            </BorderPane>
         </children>
      </VBox>
   </center>
</BorderPane>


LoginController.java

package controller;

import java.io.IOException;

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.control.Hyperlink;
import javafx.scene.control.TextField;
import javafx.stage.Stage;
import model.User;

public class LoginController {

    @FXML
    private Button closeButton;

    @FXML
    private Hyperlink newUserLink;

    @FXML
    private TextField passwordField;

    @FXML
    private Button signinButton;

    @FXML
    private TextField userNameField;
    
    private Stage stage;
    private Parent root;
    
    @FXML
    void signin(ActionEvent event) {
        passwordField.getText();
        userNameField.getText();
        User user = new User(passwordField.getText(), "first name", "last name", userNameField.getText(), "imagepath");
        //still have to get the rest of the information from the database
        
        System.out.print(user.getProfile().getFirstName());//debugging message
    }

    @FXML
    void close(ActionEvent event) {
        System.out.print("Thanks for visiting MyHealth. Bye");
        System.exit(0);
    }

    @FXML
    void openNewUserWindow(ActionEvent event) {
        try {
            System.out.print("1st line Inside 'try' block of 'openNewUserWindow' in LoginController");
            //System.out.println("\nJust after FXMLLoader Inside 'try' block of 'openNewUserWindow' in LoginController");
            root = FXMLLoader.<Parent>load(CreateUserController.class.getResource("CreateUser.fxml"));
            //System.out.print("last line Inside 'try' block of 'openNewUserWindow' in LoginController");
        }
        catch(IOException e){
            System.out.print("Problem getting 'CreateUSer.fxml'");
        }
        
        
        Stage createUserStage = new Stage();
        Scene createUserScene = new Scene(root, 400, 400);
        createUserStage.setScene(createUserScene);
        createUserStage.setTitle("Create a new user");
        createUserStage.show();
        
    }
}

CreateUser.fxml 

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

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>


<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="500.0" prefWidth="400.0" xmlns="http://javafx.com/javafx/19" xmlns:fx="http://javafx.com/fxml/1" fx:controller="controller.CreateUserController">
   <children>
      <VBox layoutY="30.0" prefHeight="440.0" prefWidth="400.0">
         <children>
            <ImageView fitHeight="82.0" fitWidth="100.0" pickOnBounds="true" preserveRatio="true">
               <VBox.margin>
                  <Insets left="150.0" top="10.0" />
               </VBox.margin>
            </ImageView>
            <Label fx:id="clickImage" text="Click to select profile picture" textFill="#00000093">
               <VBox.margin>
                  <Insets bottom="20.0" left="125.0" />
               </VBox.margin>
            </Label>
            <Label text="Username">
               <VBox.margin>
                  <Insets bottom="5.0" left="50.0" />
               </VBox.margin>
            </Label>
            <TextField fx:id="userName" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="25.0" prefWidth="280.0">
               <VBox.margin>
                  <Insets bottom="10.0" left="50.0" />
               </VBox.margin>
            </TextField>
            <Label text="First name">
               <VBox.margin>
                  <Insets bottom="5.0" left="50.0" />
               </VBox.margin>
            </Label>
            <TextField fx:id="firstName" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefWidth="280.0">
               <VBox.margin>
                  <Insets bottom="10.0" left="50.0" />
               </VBox.margin>
            </TextField>
            <Label text="Last name">
               <VBox.margin>
                  <Insets bottom="5.0" left="50.0" />
               </VBox.margin>
            </Label>
            <TextField fx:id="lastName" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefWidth="280.0">
               <VBox.margin>
                  <Insets bottom="10.0" left="50.0" />
               </VBox.margin>
            </TextField>
            <Label text="Password">
               <VBox.margin>
                  <Insets bottom="5.0" left="50.0" />
               </VBox.margin>
            </Label>
            <TextField fx:id="password" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefWidth="280.0">
               <VBox.margin>
                  <Insets bottom="10.0" left="50.0" />
               </VBox.margin>
            </TextField>
            <VBox prefHeight="200.0" prefWidth="100.0">
               <children>
                  <HBox prefHeight="100.0" prefWidth="200.0">
                     <children>
                        <Button fx:id="createButton" mnemonicParsing="false" onAction="#create" text="Create user">
                           <HBox.margin>
                              <Insets left="100.0" top="15.0" />
                           </HBox.margin>
                        </Button>
                        <Button fx:id="closeButton" mnemonicParsing="false" onAction="#close" text="Close">
                           <HBox.margin>
                              <Insets left="30.0" top="15.0" />
                           </HBox.margin>
                        </Button>
                     </children>
                  </HBox>
                  <Label fx:id="createdLabel" text="Created user" textFill="#2b784f">
                     <VBox.margin>
                        <Insets left="125.0" top="20.0" />
                     </VBox.margin>
                  </Label>
               </children>
            </VBox>
         </children>
      </VBox>
   </children>
</AnchorPane>


CreateUSerController.java

package controller;

import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.stage.Stage;

public class CreateUserController {

    @FXML
    private Label clickImage;

    @FXML
    private Button closeButton;

    @FXML
    private Button createButton;

    @FXML
    private Label createdLabel;

    @FXML
    private TextField firstName;

    @FXML
    private TextField lastName;

    @FXML
    private TextField password;

    @FXML
    private TextField userName;

    @FXML
    void close(ActionEvent event) {

    }

    @FXML
    void create(ActionEvent event) {

    }

}

Solution

  • First of all, I personally prefer to instantiate a new FXMLLoader instance to load a fxml file. Then you can also specify a location and some other kind of stuff like a controller factory. You can lookup the setter methods here: https://docs.oracle.com/javase/8/javafx/api/javafx/fxml/FXMLLoader.html

    But in this case your only mistake is that you load the fxml from the wrong class location. When you use "CreateUserController.class.getResource("CreateUser.fxml")", it will try to find the fxml file in the same directory like the class. You have to use another path like you did before with the login fxml file. ("/view/CreateUser.fxml")

    I hope that helps.