1

I'm relatively new to Java and espacially JavaFX. I'm trying to make a menu, which switches the displayed content on buttonclick. I've done this now by clearing the Pane and asigning a new fxml-file to it. This is one method from my Controller:

protected void CustomStart(ActionEvent event) {

        content.getChildren().clear();
        try {

            content.getChildren().add(
                    (Node) FXMLLoader.load(getClass().getResource(
                            "/view/CustomStartStructure.fxml")));
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

It works just fine so far but I wuld like to to it by changing the scenes as well.

I want to initiate the scenes whit a fxml-file in the Constructor. It works within another method. But if I try to initiate it in the constructor I get an InvocationTargetException caused by a RuntimeException caused by a StackOverflow error. If I do it in the other method, I get a NullPointerException when I try to change the Scene.

This is the constructor

public Game() throws IOException {

        this.MainMenu = new Scene((GridPane) FXMLLoader.load(getClass()
                .getResource("/view/MainMenuStructure.fxml")), 400, 400);
        this.stage = new Stage();
        this.stage.setScene(MainMenu);

    }

This is the method in whicht the invocation works:

public void run() throws Exception {
        /**
         * Set the Scenes for the different menus by using the panels from the
         * fxml-files
         */

        this.MainMenu = new Scene((GridPane) FXMLLoader.load(getClass()
                .getResource("/view/MainMenuStructure.fxml")), 400, 400);
        MainMenu.getStylesheets().add(
                getClass().getResource("/view/MainMenuDesign.css")
                        .toExternalForm());
        this.SingleRaceMenu = new Scene((GridPane) FXMLLoader.load(getClass()
                .getResource("/view/CustomStartStructure.fxml")), 400, 400);
        /** Giving the Stage a Scene */
        this.setStage(new Stage());
        this.stage.setScene(MainMenu);
        this.stage.show();
}

This is the Buttoncontroller:

protected void CustomStart(ActionEvent event) {
        this.getStage().setScene(getSingleRaceMenu());

    }

I hope you can give me an advice!

James_D
  • 201,275
  • 16
  • 291
  • 322
Felix
  • 41
  • 1
  • 2
  • 6
  • Is Game the controller class for `MainMenuStructure.fxml`? – ItachiUchiha Jun 19 '15 at 19:01
  • When I change the Scene content it is MenuController, when I try to change the scene, it is the Game – Felix Jun 19 '15 at 20:36
  • You are to load the fxml inside the `Game()`, which leads to constructor call again and this goes on, until you receive a SO error. In the second case, if you can pin point the line where the NullPointerException occurs, may be I can guide you with the reason. – ItachiUchiha Jun 20 '15 at 04:54
  • Thank you again for the fast answer. Which call in the loader is it, that leads to the constructor call? I tried only using the getClass() command with a print which worked fine but when I use the Loader it crashes. – Felix Jun 20 '15 at 06:48
  • The NullPointerException occurs, when I invoke this.getStage().setScene(getSingleRaceMenu()); – Felix Jun 20 '15 at 06:49
  • and allready at the stage I tried that using a printline with getStage.toString() – Felix Jun 20 '15 at 06:49
  • Ok I think I know, whats happening, but I dont know why. I inistantiate the scenes inside the run() application. In this application they do just what I want them to. But after the application they stop existing so their value is null. Same for the Stage attribute. So here is the question: How can I Instantiate the Scenes and the Stage with my fxml-file and use them later in the controller? – Felix Jun 20 '15 at 07:03
  • Your question is not very clear, but I would give you 2 advice. First, do not use the `constructor of the controller` to load the `fxml to which the class is the controller`. Second, if you need the current Scene or Stage reference in a controller, always try to get the them from the controllers or layouts references injected in the controller. For example, if you have a `@FXML Button button`, the you can get the scene using `button.getScene()` and stage using `(Stage)button.getScene().getWindow()`. – ItachiUchiha Jun 20 '15 at 07:31
  • Thank you for the advice, that helps alot. Can you tell me how can I load the fxml-file into the scene without using the constructor of the class? – Felix Jun 20 '15 at 08:19

2 Answers2

0

Here is a simple example which has two fxml files, both loaded into separate scenes and the scenes are set to the same Stage.

Controller is defined for only scene1.fxml, since this is a basic example of how you can change scene using a button event on a controller.

The important part in the example is to see how I fetch the current stage reference using the button reference, which is already a part of the scene graph :

((Stage)button.getScene().getWindow())

If you want to learn about how to switch scenes, and go back to previous scene you can go implement the following example, by loading the fxml's in their respective scene :

Loading new fxml in the same scene

Example

scene1.fxml

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

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.VBox?>

<VBox alignment="CENTER" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" spacing="10.0" style="-fx-background-color: goldenrod;" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1" fx:controller="Controller">
    <children>
        <Label text="Scene 1" />
        <Button fx:id="button" mnemonicParsing="false" onAction="#changeScene" text="Change Scene" />
    </children>
</VBox>

scene2.fxml

<?import javafx.scene.layout.VBox?>

<VBox alignment="CENTER" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" spacing="10.0" style="-fx-background-color: cyan;" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1">
    <children>
        <Label text="You have switched to Scene 2" />
    </children>
</VBox>

Scene1 Controller

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

import java.io.IOException;

public class Controller {

    @FXML
    private Button button;

    @FXML
    public void initialize() {
    }

    @FXML
    private void changeScene(ActionEvent event) {
        try {
            FXMLLoader loader = new FXMLLoader(getClass().getResource("/scene2.fxml"));
            Parent parent = loader.load();
            ((Stage)button.getScene().getWindow()).setScene(new Scene(parent, 200, 200));
        } catch (IOException eox) {
            eox.printStackTrace();
        }
    }
}

Main

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

public class Main extends Application {

    @Override
    public void start(Stage primaryStage) {
        try {
            FXMLLoader fxmlloader = new FXMLLoader(Main.class.getResource("/scene1.fxml"));
            VBox root = fxmlloader.load();
            Scene scene = new Scene(root, 200, 200);
            primaryStage.setScene(scene);
            primaryStage.show();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

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

Output

enter image description here

enter image description here

Community
  • 1
  • 1
ItachiUchiha
  • 36,135
  • 10
  • 122
  • 176
0

On the matter of Scene change vs Pane change: Since the Scene change closes and opens a new window, if you are on full screen, I ruled it out for my purpose. Instead I allways load a new Parent into my scene which is smooth and quick. Since I use FXML the only difference between the scenes is in fact the parent given by an FXML file. So it is sufficient for me to stick with different Parents. Here is a snippet of my Controller Class:

public class GameController {

private Parent mainMenu;
    private Stage stage;
    private Scene scene;

/** Constructor which receives a Stage */
    public GameController(Stage stage) {
        this.stage = stage;

    }

public void start() {

        /** Initialize the MainMenu */
        initializeMenu(mainMenu, "/view/MainMenuStructure.fxml");
this.setScene(new Scene(mainMenu));
        stage.setScene(scene);
        stage.setFullScreen(true);
        stage.setFullScreenExitHint("");
        stage.show();
    }

    @FXML
    private void MainMenu(ActionEvent event) {
        setRoot(mainMenu);

    }

    /** Initialize the menus and the in game screen */
    private void initializeMenu(Parent parent, String path) {
        FXMLLoader loader = new FXMLLoader(getClass().getResource(path));
        loader.setController(this);

        if (parent == mainMenu) {
            try {
                this.setMainMenu(loader.load());
            } catch (IOException e) {

                e.printStackTrace();
            }
        }
}

private void setRoot(Parent parent) {
        this.getStage().getScene().setRoot(parent);
    }
}

I'm very confortable with my solution. But since I'm relatively new the Java and Javafx I hope this is helps a little and is not quick and dirty. Thanks for the comments whicht actually helped a lot!

Felix
  • 41
  • 1
  • 2
  • 6