How to switch scenes in JavaFX
Asked Answered
C

8

35

I have looked on many pages to try and find out how to switch scenes but I have been unsuccessful.

I have a calculator and my goal is to select a menu option to change Calculators(ie: basic and scientific). Right now I am just testing so here is my code relevant to this question thus far (I am using Scene Builder):

@FXML private MenuItem basic;
@FXML private MenuItem testSwitch;


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

}
@Override
public void start(Stage primaryStage) throws Exception
{
   Parent pane = FXMLLoader.load(
           getClass().getResource( "calculator.fxml" ) );

   Scene scene = new Scene( pane );
   primaryStage.setScene(scene);
   primaryStage.setTitle( "Calculator" );
   primaryStage.show();

}
@FXML
public void handleMenuOption(ActionEvent e) 
{
    if(e.getSource()==basic)
    {
        changeScene("calculator.fxml");
    }
    else if(e.getSource()==testSwitch)
    {
        changeScene("TestSwitch.fxml");
    }
}
public void changeScene(String fxml) 
{
    //this prints out
    System.out.println(fxml);
}

EDIT I've tried quite a few things already. No matter what, I always get this NullPointerException. I have a feeling it may have to do with setting something in scene builder but I just have not been able to find an answer

Exception in thread "JavaFX Application Thread" java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
at javafx.fxml.FXMLLoader$MethodHandler.invoke(Unknown Source)
at javafx.fxml.FXMLLoader$ControllerMethodEventHandler.handle(Unknown Source)
at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(Unknown Source)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(Unknown Source)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(Unknown Source)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(Unknown Source)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(Unknown Source)
at com.sun.javafx.event.EventUtil.fireEventImpl(Unknown Source)
at com.sun.javafx.event.EventUtil.fireEvent(Unknown Source)
at javafx.event.Event.fireEvent(Unknown Source)
at javafx.scene.control.MenuItem.fire(Unknown Source)
at com.sun.javafx.scene.control.skin.ContextMenuContent$MenuItemContainer.doSelect(Unknown Source)
at com.sun.javafx.scene.control.skin.ContextMenuContent$MenuItemContainer.lambda$createChildren$343(Unknown Source)
at com.sun.javafx.event.CompositeEventHandler$NormalEventHandlerRecord.handleBubblingEvent(Unknown Source)
at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(Unknown Source)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(Unknown Source)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(Unknown Source)
at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(Unknown Source)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(Unknown Source)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(Unknown Source)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(Unknown Source)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(Unknown Source)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(Unknown Source)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(Unknown Source)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(Unknown Source)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(Unknown Source)
at com.sun.javafx.event.EventUtil.fireEventImpl(Unknown Source)
at com.sun.javafx.event.EventUtil.fireEvent(Unknown Source)
at javafx.event.Event.fireEvent(Unknown Source)
at javafx.scene.Scene$MouseHandler.process(Unknown Source)
at javafx.scene.Scene$MouseHandler.access$1500(Unknown Source)
at javafx.scene.Scene.impl_processMouseEvent(Unknown Source)
at javafx.scene.Scene$ScenePeerListener.mouseEvent(Unknown Source)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(Unknown Source)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleMouseEvent$354(Unknown Source)
at com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(Unknown Source)
at    com.sun.javafx.tk.quantum.GlassViewEventHandler.handleMouseEvent(Unknown Source)
at com.sun.glass.ui.View.handleMouseEvent(Unknown Source)
at com.sun.glass.ui.View.notifyMouse(Unknown Source)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.lambda$null$148(Unknown Source)
at java.lang.Thread.run(Unknown Source)
Caused by: java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at sun.reflect.misc.Trampoline.invoke(Unknown Source)
at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at sun.reflect.misc.MethodUtil.invoke(Unknown Source)
... 44 more
Caused by: java.lang.NullPointerException
at CalculatorMain.changeScene(CalculatorMain.java:75)
at CalculatorMain.handleMenuOption(CalculatorMain.java:64)
... 53 more




at CalculatorMain.changeScene(CalculatorMain.java:75)
This is at:stage . getScene() . setRoot(pane);


at CalculatorMain.handleMenuOption(CalculatorMain.java:64)
This is at:changeScene ("TestSwitch.fxml");

WORKING CODE:

I played around using suggestions below and used this code to make it work:

private Stage stage;

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

@Override
public void start(Stage primaryStage) throws Exception
{
    this.stage = primaryStage;
    FXMLLoader loader = new FXMLLoader(getClass()
            .getResource("calculator.fxml"));
    Parent root = (Parent)loader.load();
    BasicCalculatorView controller = (BasicCalculatorView)loader.getController();
    controller.setModel(new BasicCalculatorModelTest(controller));
    controller.setLogic(this);
    primaryStage.setTitle("Calculator");
    primaryStage.setScene(new Scene(root));
    primaryStage.show();
}

public void switchScene(String fxmlFile)
{

    FXMLLoader loader = new FXMLLoader(getClass()
            .getResource(fxmlFile));
    Parent root;
    try 
    {
        root = (Parent)loader.load();
        if(fxmlFile.equals("calculator.fxml"))
        {
            BasicCalculatorView controller = (BasicCalculatorView)loader.getController();
            controller.setModel(new BasicCalculatorModelTest(controller));
            controller.setLogic(this);
        }
        else if(fxmlFile.equals("TestSwitch.fxml"))
        {
            TestSwitch controller = (TestSwitch)loader.getController();
            controller.setLogic(this);
        }
        this.stage.setScene(new Scene(root));
    } 
    catch (IOException e)
    {
        e.printStackTrace();
    }

}
Coolidge answered 13/5, 2016 at 3:40 Comment(0)
C
38

I wrote this controller to keep track of the different scenegraphes.

public class ScreenController {
    private HashMap<String, Pane> screenMap = new HashMap<>();
    private Scene main;

    public ScreenController(Scene main) {
        this.main = main;
    }

    protected void addScreen(String name, Pane pane){
         screenMap.put(name, pane);
    }

    protected void removeScreen(String name){
        screenMap.remove(name);
    }

    protected void activate(String name){
        main.setRoot( screenMap.get(name) );
    }
}

So I can write:

ScreenController screenController = new ScreenController(scene);
screenController.add("calculator", FXMLLoader.load(getClass().getResource( "calculator.fxml" )));
screenController.add("testSwitch", FXMLLoader.load(getClass().getResource( "TestSwitch.fxml" )));
screenController.activate("calculator");

This was a workaround for a fullscreen application, where the MacOS fullscreen transition was shown every time a stage switches its scene.

Curate answered 17/5, 2016 at 12:23 Comment(0)
N
13

Instead of switching Scenes, switch a root node on already existing Scene

Nutty answered 13/5, 2016 at 3:54 Comment(3)
I have tried this but I keep getting a null pointer exception. After getting two comments about the same thing I decided to post my exception message, see above.Coolidge
In your code you're mixing Application and FXML controller concepts. Each have different initialization lifecycle. FXML controllers are based on dependency injection principle and initialization should be done in the init method. Read up on FXML controllers. You can also see the following #34785917Nutty
So how would I switch a scene. I tried using the init method and setting a string parameter. If I passed in a string such as "test" and use a syso to print out the result, it would print out test. But if I passed in a string with the FXML file and tried to set a scene, it would give me a null pointer exceptionCoolidge
O
6

If you want to go along with changing the scene you would do it like this (note that the Stage is a member of the application):

private Stage primaryStage;

@Override
public void start(Stage primaryStage) throws Exception {
    this.primaryStage = primaryStage;
    ...
}

public void changeScene(String fxml){
    Parent pane = FXMLLoader.load(
           getClass().getResource(fxml));

   Scene scene = new Scene( pane );
   primaryStage.setScene(scene);
}

However as already pointed out by @Eugene_Ryzhikov it is a better solution to just change the root content of the existing scene:

public void changeScene(String fxml){
    Parent pane = FXMLLoader.load(
           getClass().getResource(fxml));

   primaryStage.getScene().setRoot(pane);
}
Oolite answered 13/5, 2016 at 5:58 Comment(6)
I have tried this but I keep getting a null pointer exception. After getting two comments about the same thing I decided to post my exception message, see above.Coolidge
What is the code at the line where you get the NullPointerException?Oolite
Above I have added the exception I got and there are notes at the bottom. But I tired a couple similar examples without using Scene Builder and it worked perfectly fine so it is for sure something with Scene BuilderCoolidge
Is it possible that the stage that is passed as parameter in the start method is not assigned to the member primaryStage? This would result in a NullPointerException on line primaryStage.getScene().setRoot(pane). I updated the code example to include the fragment for the start method.Oolite
Sorry, I know this is an old post. Why do you do this.primaryStage = primaryStage;? Why not just ignore the start() method argument and do everything with this.primaryStage?Impeditive
To change the scene you need a method (changeScene) in that method you need a reference to the Stage, that is passed in when the application is started. The start method is called on application start.Oolite
S
2

I just ran to the same issue and this answer solved my issue perfectly while being short and clean.

@FXML
private void handleButtonAction(ActionEvent event) {
System.out.println("You clicked me!");
label.setText("Hello World!");
//Here I want to swap the screen!

Stage stageTheEventSourceNodeBelongs = (Stage) ((Node)event.getSource()).getScene().getWindow();
// OR
Stage stageTheLabelBelongs = (Stage) label.getScene().getWindow();
// these two of them return the same stage
// Swap screen
stage.setScene(new Scene(new Pane()));

}
Saprophagous answered 27/11, 2017 at 21:31 Comment(0)
C
0

I wrote a library that makes switching Scenes really simple. You just pick a unique ID for each scene of type Integer, then you add scenes with one line of code. When you need to show a scene, you can do so from any class in your project by calling that method and passing it the the ID of the scene.

You can find the library here along with the Maven import xml.

Chari answered 14/2, 2021 at 19:49 Comment(0)
B
-1

The simplest way (This avoids re-loading scene):

In main Application class:

  1. Create a private static attribute to store your stage:

    private static Stage primaryStage;
    
  2. Assign stage to your primaryStage in overridden start(Stage stage) method:

    primaryStage = stage;
    
  3. Create static method changeScene

     public static void  changeScene(String fxml) throws IOException {
       Parent pane = FXMLLoader.load(Objects.requireNonNull(GameApplication.class.getResource(fxml)));
       primaryStage.getScene().setRoot(pane);
    

This method you can use wherever you want, in any class:

Example:

MainApplication.changeScene("next-scene-view.fxml");
Butz answered 3/6, 2022 at 16:47 Comment(0)
P
-2
TypesController.java
package todoapp;
import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
import javafx.stage.Stage;

public class TypesController implements Initializable {
@FXML
private CheckBox c1;
@FXML
private CheckBox c2;
public void clicked(ActionEvent e) throws IOException {
Parent home_page_parent =FXMLLoader.load(getClass().getResource("AddDcuFXML.fxml"));
Scene home_page_scene = new Scene(home_page_parent);
Stage app_stage = (Stage) ((Node) e.getSource()).getScene().getWindow();
app_stage.hide(); //optional
app_stage.setScene(home_page_scene);
app_stage.show();
}
public void clicked1(ActionEvent e) throws IOException {
Parent home_page_parent =   FXMLLoader.load(getClass().getResource("AddDcuFXML.fxml"));
    Scene home_page_scene = new Scene(home_page_parent);
    Stage app_stage = (Stage) ((Node)e.getSource()).getScene().getWindow();
   app_stage.hide(); //optional
   app_stage.setScene(home_page_scene);
   app_stage.show();
   }
  @Override
  public void initialize(URL arg0, ResourceBundle arg1) {
 // TODO Auto-generated method stub
  } }
Persuade answered 9/3, 2018 at 11:21 Comment(2)
Some explanation would be cool with such a long code example.Preamble
@Preamble I like the way you have given feedback on this answer..:-)Wilow
P
-2
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.image.*?>
 <?import javafx.scene.text.*?>
 <?import java.lang.*?>
 <?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<AnchorPane id="AnchorPane" prefHeight="491.0" prefWidth="386.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="todoapp.TypesController">
<children>
  <CheckBox fx:id="c1" layoutX="55.0" layoutY="125.0" mnemonicParsing="false" onAction="#clicked" text="ADD dcu" />
  <CheckBox fx:id="c2" layoutX="55.0" layoutY="177.0" mnemonicParsing="false" onAction="#clicked1" text="Display dcu" />
  <Label layoutX="31.0" layoutY="58.0" prefHeight="37.0" prefWidth="276.0" text="Choose any one of the options" textFill="#1b29cd">
     <font>
        <Font name="Arial Bold" size="18.0" />
     </font>
  </Label>
</children>

Persuade answered 9/3, 2018 at 11:26 Comment(2)
please edit your first answer and add the snippets from the other answers (plus explain how/why they solve the problem as suggested in the comment)Libbie
Just so you know I posted this like 2 years ago. I think all I did was take suggestions from the answers, did some more searching on google, and then came up with the answer. I was still very new to programming and had no idea what I was doingCoolidge

© 2022 - 2024 — McMap. All rights reserved.