Stages and Scenes

Stage

The Stage class represents the window of a GUI application. On Windows, it will appear with a blue bar with sizing controls in the upper right (aka, a standard Windows window). On OS X, it appears as below:

Stage Example

When you write a JavaFX application, a Stage is automatically created for you; this is where the primaryStage we've been using in our start() functions comes from. As all other Java objects, Stage provides us some methods to use.

Note: While many are called, few are chosen. We will be using a subset of Stage's methods

Method Description
void close() Closes the stage
void initModality(Modality) If called before show(), will make the stage a modal
double getMaxHeight() Get the max height for the stage
double getMaxWidth() Get the max width for the stage
double getMinHeight() Get the min height for the stage
double getMinWidth() Get the min width for the stage
void setFullscreen(boolean) Sets the fullscreen status of the stage
void setMaximized(boolean) Sets the maximized status of the stage
void setMaxHeight(double) Sets the max height of the stage
void setMaxWidth(double) Sets the max width of the stage
void setMinHeight(double) Sets the min height of the stage
void setMinWidth(double) Sets the min width of the stage
void setResizable(boolean) Sets the resizability of the stage
void setScene(Scene) Sets the scene to be displayed
void setTitle(String) Sets the title of the stage
void show() Makes the stage visible
void showAndWait() Makes the stage visible and waits until closing to continue
void toFront() Forces the stage to the foreground
void toBack() Forces the stage to the background
Scene

Scenes are containers for GUI elements, like a layout pane or a Button. They are also used to generally set the size of the Stage containing them. Let's look at the methods we'll be using from Scene:

Method Description
Scene(Parent) Creates a Scene with a Parent as the root node
Scene(Parent, double, double) Creates a Scene with a specified width and height
double getHeight() Gets the height of the scene
double getWidth() Gets the width of the scene
double getX() Gets the horizontal position of the scene
double getY() Gets the vertical position of the scene
void setRoot(Parent) Sets the root node
Multiple Scenes

Each Stage can only display one Scene at a time. However, we have the setScene(Scene) method in Stage, which allows us to change the displayed Scene. Let's see how we can use multiple Scenes by creating an application that combines the two previous (ClickCounter and AddSubtract):

import javafx.application.*;
import javafx.event.*;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.stage.*;

public class SceneSwitcher extends Application
{
    // Elements of ClickCounter scene
    int clickCount = 0;
    Label lblClicks;
    Button btnClickMe;
    Button btnSwitch2;
    Scene scene1;

    // Elements of AddSubtract scene
    int count = 0;
    Label lblCounter;
    Button btnAdd;
    Button btnSub;
    Button btnSwitch1;
    Scene scene2;

    // The overall stage
    Stage stage;

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

    @Override
    public void start(Stage primaryStage)
    {
        stage = primaryStage;

        // Build the ClickCounter scene
        lblClicks = new Label();
        lblClicks.setText("You have not clicked the button.");
        
        btnClickMe = new Button();
        btnClickMe.setText("Click me please!");
        btnClickMe.setOnAction(e -> btnClickMe_Click());

        btnSwitch2 = new Button();
        btnSwitch2.setText("Switch!");
        btnSwitch2.setOnAction(e -> btnSwitch2_Click());

        VBox pane1 = new VBox(10);
        pane1.getChildren().addAll(lblClicks, btnClickMe, btnSwitch2);
        scene1 = new Scene(pane1, 250, 150);

        // Build the AddSubtract scene
        lblCounter = new Label();
        lblCounter.setText(Integer.toString(count));

        btnAdd = new Button();
        btnAdd.setText("Add");
        btnAdd.setOnAction(e -> btnAdd_Click());

        btnSub = new Button();
        btnSub.setText("Subtract");
        btnSub.setOnAction(e -> btnSub_Click());

        btnSwitch1 = new Button();
        btnSwitch1.setText("Switch!");
        btnSwitch1.setOnAction(e -> btnSwitch1_Click());

        HBox pane2 = new HBox(10);
        pane2.getChildren().addAll(lblCounter, btnAdd, btnSub, btnSwitch1);
        scene2 = new Scene(pane2, 300, 75);

        primaryStage.setScene(scene1);
        primaryStage.setTitle("Scene Switcher");
        primaryStage.show();
    }

    // ClickCounter event handlers
    private void btnClickMe_Click()
    {
        clickCount++;
        if (clickCount == 1) {
            lblClicks.setText("You have clicked once.");
        } else {
            lblClicks.setText("You have clicked " + clickCount + " times.");
        }
    }

    private void btnSwitch2_Click()
    {
        stage.setScene(scene2);
    }

    // AddSubtract event handlers
    private void btnAdd_Click()
    {
        count++;
        lblCounter.setText(Integer.toString(count));
    }

    private void btnSub_Click()
    {
        count--;
        lblCounter.setText(Integer.toString(count));
    }

    private void btnSwitch1_Click()
    {
        stage.setScene(scene1);
    }
}

Modality

Sometimes your application may need to display small dialog boxes to display information to the user, or even to gather information from the user. Earlier Java GUI APIs had built in modal dialog boxes, however JavaFX does not. So, let's make one to use! We can then use this dialog, or message, box elsewhere.

Create a new Java package with the following name:

edu.govschool.modals

The code will look like this:

package edu.govschool.modals;

import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.stage.*;
import javafx.geometry.*;

/**
 * Displays a modal dialog box for messages.
 * @author Bryce Davis
 */
public class MessageBox {
    /**
     * Show a <code>MessageBox</code>.
     * Display a modal dialog box with a message and a title.
     * @param msg The message to display
     * @param title The title of the message box
     */
    public static void show(String msg, String title)
    {
        // Create a new stage, or window, to display. Since we want a modal
        // dialog, we set the modality as APPLICATION_MODAL. We also set the 
        // minimum width the box can be.
        Stage stage = new Stage();
        stage.initModality(Modality.APPLICATION_MODAL);
        stage.setTitle(title);
        stage.setMinWidth(250);
        
        // This label will display the message
        Label lbl = new Label();
        lbl.setText(msg);
        
        // The button to close the dialog
        Button btn = new Button();
        btn.setText("OK");
        btn.setOnAction(e -> stage.close());
        
        // Position all of the elements vertically
        VBox pane = new VBox(20);
        pane.getChildren().addAll(lbl, btn);
        pane.setAlignment(Pos.CENTER);
        
        // Add everything to the scene and display the box
        Scene scene = new Scene(pane);
        stage.setScene(scene);
        stage.showAndWait();
    }
}

You can then use it in other Netbeans projects! Right-click the Libraries option under the project in the projects view, and then Add Project. You can choose other Netbeans projects you've created to use as a library.

Let's also create a confirmation box, so the user can make a boolean choice in our applications:

package edu.govschool.modals;

import javafx.application.*;
import javafx.stage.*;
import javafx.scene.*;
import javafx.scene.layout.*;
import javafx.scene.control.*;
import javafx.scene.text.*;
import javafx.event.*;
import javafx.geometry.*;

/**
 * Displays an OK/Cancel confirmation box.
 * Used if the user needs to confirm or deny an action.
 * @author Bryce Davis
 */
public class ConfirmationBox {
    // These are static because these dialog boxes are considered "singleton"
    // objects, or objects that only have one instance. Normally this instance
    // is created via a static class method, and is the case here.
    static Stage stage;
    static boolean confirm;
    
    /**
     * Display a confirmation box.
     * @param msg The message to display
     * @param title The title of the box
     * @param yesText The text for the confirm button
     * @param noText The text for the deny button
     * @return <code>true</code> if the user confirms, <code>false</code> otherwise
     */
    public static boolean show(String msg, String title, 
                               String yesText, String noText) {
        // We will use this boolean to return the user's choice
        confirm = false;
        
        // Again, we are initializing a modal stage
        stage = new Stage();
        stage.initModality(Modality.APPLICATION_MODAL);
        stage.setTitle(title);
        stage.setMinWidth(250);
        
        // Create our various GUI elements
        Label lbl = new Label();
        lbl.setText(msg);
        
        Button confirmBtn = new Button();
        confirmBtn.setText(yesText);
        confirmBtn.setOnAction(e -> confirmBtn_Click());
        
        Button denyBtn = new Button();
        denyBtn.setText(noText);
        denyBtn.setOnAction(e -> denyBtn_Click());
        
        // Here is an example of using multiple layout panes to organize our
        // elements. The buttons are arranged horizontally in an HBox, but the
        // Label and this HBox are arranged vertically in a VBox.
        HBox buttonsPane = new HBox(20);
        buttonsPane.getChildren().addAll(confirmBtn, denyBtn);
        
        VBox pane = new VBox(20);
        pane.getChildren().addAll(lbl, buttonsPane);
        pane.setAlignment(Pos.CENTER);
        
        // Let's set the scene!
        Scene scene = new Scene(pane);
        stage.setScene(scene);
        stage.showAndWait();
        
        return confirm;
    }
    
    /**
     * Event handler for the confirmation button.
     * Closes the stage and sets that the user confirmed.
     */
    private static void confirmBtn_Click()
    {
        stage.close();
        confirm = true;
    }
    
    /**
     * Event handler for the deny button.
     * Closes the stage and sets that the user denied.
     */
    private static void denyBtn_Click()
    {
        stage.close();
        confirm = false;
    }
}

Finally, we can have a modal with a custom Scene:

package edu.govschool.modals;

import javafx.scene.*;
import javafx.stage.*;

/**
 * Modal to display a custom <code>Scene</code>.
 * @author Mr. Davis
 */
public class CustomBox 
{
    private static Stage stage;
    
    /**
     * Show a modal dialog with a custom <code>Scene</code>.
     * @param scene the <code>Scene</code> to display
     * @param title the title of the modal
     */
    public static void show(Scene scene, String title)
    {
        stage = new Stage();
        stage.setScene(scene);
        stage.setTitle(title);
        stage.showAndWait();
    }
    
    /**
     * Show a modal dialog with a <code>Parent</code> node as
     * the root node.
     * @param node the root GUI element
     * @param title the title of the modal
     */
    public static void show(Parent node, String title)
    {
        show(new Scene(node), title);
    }
}

Closing a Stage

So far, we have not been using the best practices for handling the window closing. Even if we tried to be prudent and code an 'Exit/Close' button that takes care of any last-minute operations that need to be performed before closing, if the user simply clicks the Close button in the window's toolbar (generally, this is a red button) then that code won't be run! Instead, we need to listen for and handle CloseRequest events. As it turns out, Stage has a method called setOnCloseRequest(EventHandler), which accepts an EventHandler argument. As such, let's use a lambda expression! We'll write an updated version of ClickCounter using our new modals.

Note: Java will automatically close the stage unless the event is 'consumed', see the lambda below

import javafx.application.*;
import javafx.stage.*;
import javafx.scene.*;
import javafx.scene.layout.*;
import javafx.scene.control.*;
import javafx.geometry.*;

public class ClickCounter extends Application
{
    private static Stage stage;
    private static int count = 0;

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

    @Override
    public void start(Stage primaryStage)
    {
        stage = primaryStage;

        // Create the buttons
        Button clickBtn = new Button();
        clickBtn.setText("Click me please!");
        clickBtn.setOnAction(e -> clickBtn_Click());
        Button closeBtn = new Button();
        closeBtn.setText("Close");
        closeBtn.setOnAction(e -> closeBtn_Click());

        // Organize the elements
        VBox pane = new VBox(10);
        pane.getChildren().addAll(clickBtn, closeBtn);
        pane.setAlignment(Pos.CENTER);

        // Add the pane to the scene
        Scene scene = new Scene(pane, 250, 150);

        // Finish and show the stage.
        // Not the lambda expression. The event 'e' must be consumed, else
        // Java will close the window regardless of the user's choice.
        stage.setScene(scene);
        stage.setTitle("Click Counter");
        stage.setOnCloseRequest(
            e -> {
                e.consume();
                closeBtn_Click();
            }
        );
        stage.show();
    }

    public void clickBtn_Click()
    {
        count++;
        if (count == 1) {
            MessageBox.show("You have clicked once.", "Click!");
        } else {
            MessageBox.show("You have clicked " +
                            count + " times", "Click!");
        }
    }

    public void closeBtn_Click()
    {
        boolean confirm = ConfirmationBox.show(
            "Are you sure you want to quit?", "Confirmation",
            "Yes", "No"
        );

        if (confirm) {
            // If we needed to do anything right at the end, like closing a 
            // file, we do it here.
            stage.close();
        }
    }
}