Search code examples
javajavafxfxmlscene

Switch FXML scene


I'm finishing a part of my program that involves users being sent from a Login screen (FIRST fxml) to a second screen. This is supposed to happen whenever the program is loaded (based on remembrance).

When I run the program, it loads the FIRST and SECOND fxml, but continues to display the FIRST fxml (when it should display the SECOND). I can use an input (I.E button) with the same code to get from the first to the second screen.

I've tried using different loaders and using the .show() method every time I set a scene but none of these (or many variations thereof) have worked.

I printed when the methods were called - That's how I'm sure they're loaded (printline):

  1. START METHOD (Main Application start(Stage) method)
  2. INIT1 METHOD (First Controller Initialize Method)
  3. INIT2 METHOD (Beginning of Second Controller Initialize Method)
  4. Loaded Input UI (End of Second Controller Initialize Method)
  5. INIT2 METHOD (These two lines occur again whenever I press enter on the text)
  6. Loaded Input UI (At which point it does switch to the new FXML)

Here is a minimal representation of my code structure:

public class Main extends Application {

    private static final FXMLLoader loader = new FXMLLoader();
    private static Stage mainStage;

    @Override
    public void start(Stage primaryStage) throws Exception {
        System.out.println("START METHOD");
        mainStage = primaryStage; //Copy Reference
        primaryStage.setScene(new Scene(Main.getLoader().load(Main.class.getResource("/res/screenOne.fxml"))));
        primaryStage.show();
    }

    public static FXMLLoader getLoader() {
        return loader;
    }

    public static Stage getStage() {
        return mainStage;
    }
}

/**
 * FXML Controller class
 */
public class firstUI implements Initializable {

    @Override
    public void initialize(URL url, ResourceBundle rb) {
        System.out.println("INIT1 METHOD");
        try {
            Parent root = Main.getLoader().load(getClass().getResource("/res/screenTwo.fxml"));
            Main.getStage().setScene(new Scene(root));
        } catch (Exception ex) {
            Logger.getLogger(mainUI.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}

/**
 * FXML Controller class
 */
public class secondUI implements Initializable {

    @Override
    public void initialize(URL url, ResourceBundle rb) {
        System.out.println("INIT2 METHOD");
        System.out.println("Loaded Input UI");
    }
}

Again, I want the code to load the firstUI, and then from the firstUI load the second.

The output instead (in debug if stage.show() comes first) seems to be:

  1. firstUI code is ran (from start) and initialized (not shown).
  2. secondUI code is ran (from firstUI init) and initialized (not shown).
  3. secondUI initialize code completes and firstUI is shown.
  4. Only if the input (which has the same switch code) is pressed does it switch from firstUI to secondUI.

Solution

  • You do not need a reference of the loader to change scene.
    You can have a simple Main like:

    import javafx.application.Application;
    import javafx.fxml.FXMLLoader;
    import javafx.scene.Scene;
    import javafx.stage.Stage;
    
    public class Main extends Application {
    
        @Override
        public void start(Stage primaryStage) throws Exception {
            primaryStage.setScene(new Scene(new FXMLLoader().load(getClass().getResource("/res/screenOne.fxml"))));
            primaryStage.show();
        }
    
        public static void main(String[] args) {
            launch(null);
        }
    }
    

    Where screenOne.fxml can be (note the fx-id of the pane).
    (To test you'll need to edit with the right controller path):

    <?xml version="1.0" encoding="UTF-8"?>
    
    <?import javafx.scene.control.Button?>
    <?import javafx.scene.control.Label?>
    <?import javafx.scene.layout.AnchorPane?>
    <?import javafx.scene.text.Font?>
    
    <AnchorPane fx:id="main" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="300.0" prefWidth="400.0" xmlns="http://javafx.com/javafx/10.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="FirstUI">
       <children>
          <Label layoutX="155.0" layoutY="132.0" text="Screen 1">
             <font>
                <Font size="24.0" />
             </font>
          </Label>
          <Button layoutX="264.0" layoutY="246.0" mnemonicParsing="false" onAction="#changeScene" prefHeight="26.0" prefWidth="102.0" text="Change Scene" />
       </children>
    </AnchorPane>
    

    FirstUI uses main to update the scene:

    import javafx.event.ActionEvent;
    import javafx.fxml.FXML;
    import javafx.fxml.FXMLLoader;
    import javafx.scene.layout.Pane;
    
    public class FirstUI  {
    
        @FXML Pane main;
    
        public void changeScene(ActionEvent e) {
            try {
                main.getScene().setRoot(new FXMLLoader().load(getClass().getResource("/res/screenTwo.fxml")));
            } catch (Exception ex) {
              ex.printStackTrace();
            }
        }
    }
    

    To make the code MRE here is screenTwo.fxml:

    <?xml version="1.0" encoding="UTF-8"?>
    
    <?import javafx.scene.control.Label?>
    <?import javafx.scene.layout.AnchorPane?>
    <?import javafx.scene.text.Font?>
    
    <AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" 
    prefHeight="300.0" prefWidth="400.0" xmlns="http://javafx.com/javafx/10.0.1" 
    xmlns:fx="http://javafx.com/fxml/1">
       <children>
          <Label layoutX="155.0" layoutY="132.0" text="Screen 2">
             <font>
                <Font size="24.0" />
             </font>
          </Label>
       </children>
    </AnchorPane>