Chapter 3

Setting the Stage and Scene Layout

IN THIS CHAPTER

check Looking at some useful methods of the Stage and Scene classes

check Alternating scenes within a single stage

check Displaying alert boxes

check Discovering the proper way to exit a JavaFX program

O for a Muse of fire, that would ascend
The brightest heaven of Invention,

A kingdom for a stage, princes to act,
And monarchs to behold the swelling scene!

So begins William Shakespeare’s play Henry V, and so also begins this chapter, in which I explore the various ways to manipulate the appearance of a JavaFX application by manipulating its stage and its swelling scenes.

Specifically, this chapter introduces you to important details about the Stage class and the Scene class so that you can control such things as whether the window is resizable and if so, whether it has a maximum or a minimum size. You also learn how to coerce your programs into displaying additional stages beyond the primary stage, such as an alert or confirmation dialog box. And finally, you learn the proper way to end a JavaFX program by handling the events generated when the user closes the stage.

Examining the Stage Class

A stage, which is represented by the Stage class, is the topmost container in which a JavaFX user interface appears. In Windows, on a Mac, or in Linux, a stage is usually a window. On other types of devices, such as a smartphone or tablet, the stage may be the full screen or a tiled region of the screen.

When a JavaFX application is launched, a stage known as the primary stage is automatically created. A reference to this stage is passed to the application’s start method via the primaryStage parameter:

@Override public void start(Stage primaryStage)
{
// primaryStage refers to the
// application's primary stage.
}

You can then use the primary stage to create the application’s user interface by adding a scene, which contains one or more controls or other user-interface nodes.

tip In many cases, you will need to access the primary stage outside of the scope of the start method. You can easily make this possible by defining a class field and using it to reference the primary stage. You see an example of how to do that later in this chapter, in the section “Switching Scenes .”

The primary stage initially takes on the default characteristics of a normal windowed application, which depends on the operating system within which the program will run. You can, if you choose, change these defaults to suit the needs of your application. At the minimum, you should always set the window title. You may also want to change such details as whether the stage is resizable and various aspects of the stage’s appearance.

The Stage class comes equipped with many methods that let you manipulate the appearance and behavior of a stage. Table 3-1 lists the ones you’re most likely to use.

TABLE 3-1 Commonly Used Methods of the Stage Class

Method

Description

void close()

Closes the stage.

void initModality(Modality modality)

Sets the modality of the stage. This method must be called before the show method is called. The modality can be one of the following:

Modality.NONE

Modality.APPLICATION_MODAL

Modality.WINDOW_MODAL

void initStyle(StageStyle style)

Sets the style for the stage. This method must be called before the show method is called. The style can be one of the following:

StageStyle.DECORATED

StageStyle.UNDECORATED

StageStyle.TRANSPARENT

StageStyle.UNIFIED

StageStyle.UTILITY

void getMaxHeight(double maxheight)

Gets the maximum height for the stage.

void getMaxWidth(double maxwidth)

Gets the maximum width for the stage.

void getMinHeight(double maxheight)

Gets the minimum height for the stage.

void getMinWidth(double maxwidth)

Gets the minimum width for the stage.

void setFullScreen(boolean fullscreen)

Sets the fullscreen status of the stage.

void setIconified(boolean iconified)

Sets the iconified status of the stage.

void setMaximized(boolean maximized)

Sets the maximized status of the stage.

void setMaxHeight(double maxheight)

Sets the maximum height for the stage.

void setMaxWidth(double maxwidth)

Sets the maximum width for the stage.

void setMinHeight(double maxheight)

Sets the minimum height for the stage.

void setMinWidth(double maxwidth)

Sets the minimum width for the stage.

void setResizable(boolean resizable)

Sets the fullscreen status of the stage.

void setScene(Scene scene)

Sets the scene to be displayed on the stage.

void setTitle(String title)

Sets the title to be displayed in the stage’s title bar, if a title bar is visible.

void show()

Makes the stage visible.

void showAndWait()

Makes the stage visible and then waits until the stage is closed before continuing.

void toFront()

Forces the stage to the foreground.

void toBack()

Forces the stage to the background.

The following paragraphs point out some of the ins and outs of using the Stage class methods listed in Table 3-1 :

Examining the Scene Class

Like the Stage class, the Scene class is fundamental to JavaFX programs. In every JavaFX program, you use at least one instance of the Scene class to hold the user-interface controls that your users will interact with as they use your program.

Table 3-2 lists the more commonly used constructors and methods of the Scene class.

TABLE 3-2 Commonly Used Constructors and Methods of the Scene class

Constructor

Description

Scene(Parent root)

Creates a new scene with the specified root node

Scene(Parent root, double width, double height)

Creates a new scene with the specified root node, width, and height

Method

Description

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 screen

void setRoot(Parent root)

Sets the root node

TABLE 3-3 Commonly Used Constructors and Methods of the Alert class

Constructor

Description

Alert(Alert.AlertType)

Creates a new alert of the specified type

Alert(Alert.AlertType, String text)

Creates a new alert of the specified type and sets the message text

Alert(Alert.AlertType, String text, ButtonType type…)

Creates a new alert and sets the buttons to be displayed

Method

Description

void setTitle(String text)

Sets the title

Optional<ButtonType> showAndWait()

Shows the alert and waits for the user’s response, which is returned as a ButtonType object

The following paragraphs explain some of the more interesting details of the constructors and methods of the Scene class:

Switching Scenes

The primary stage of a JavaFX program (or any other stage, for that matter) can have only one scene displayed within it at any given time. However, that doesn’t mean that your program can’t create several scenes and then swap them as needed. For example, suppose you’re developing a word-processing program and you want to let the user switch between an editing view and a page preview view. You could do that by creating two distinct scenes, one for each view. Then, to switch the user between views, you simply call the stage’s setScene method to switch the scene.

In Chapter 1 of this minibook, you read about a ClickCounter program whose scene displays a label and a button and then updates the label to indicate how many times the user has clicked the button. Then, in Chapter 2 of this minibook, you saw several variations of an AddSubtract program whose scene displayed a label and two buttons: One button added one to a counter when clicked, the other subtracted one from the counter.

Listing 3-1 shows a program named SceneSwitcher that combines the scenes from the ClickCounter and AddSubtract programs into a single program. Figure 3-1 shows this program in action:

image

FIGURE 3-1: The SceneSwitcher program.

LISTING 3-1 The SceneSwitcher Program

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

public class SceneSwitcher extends Application
{
public static void main(String[] args)
{
launch(args);
}

// class fields for Click-Counter scene →15
int iClickCount = 0;
Label lblClicks;
Button btnClickMe;
Button btnSwitchToScene2;
Scene scene1;

// class fields for Add-Subtract scene →22
int iCounter = 0;
Label lblCounter;
Button btnAdd;
Button btnSubtract;
Button btnSwitchToScene1;
Scene scene2;

// class field for stage
Stage stage; →31

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

// Build the Click-Counter scene →37

lblClicks = new Label();
lblClicks.setText("You have not clicked the button.");

btnClickMe = new Button();
btnClickMe.setText("Click me please!");
btnClickMe.setOnAction(
e -> btnClickMe_Click() );

btnSwitchToScene2 = new Button();
btnSwitchToScene2.setText("Switch!");
btnSwitchToScene2.setOnAction(
e -> btnSwitchToScene2_Click() );

VBox pane1 = new VBox(10);
pane1.getChildren().addAll(lblClicks, btnClickMe,
btnSwitchToScene2);

scene1 = new Scene(pane1, 250, 150);


// Build the Add-Subtract scene →59

lblCounter = new Label();
lblCounter.setText(Integer.toString(iCounter));

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

btnSubtract = new Button();
btnSubtract.setText("Subtract");
btnSubtract.setOnAction(
e -> btnSubtract_Click() );

btnSwitchToScene2 = new Button();
btnSwitchToScene2.setText("Switch!");
btnSwitchToScene2.setOnAction(
e -> btnSwitchToScene1_Click() );

HBox pane2 = new HBox(10);
pane2.getChildren().addAll(lblCounter, btnAdd,
btnSubtract, btnSwitchToScene2);

scene2 = new Scene(pane2, 300, 75);

// Set the stage with scene 1 and show the stage →84
primaryStage.setScene(scene1);
primaryStage.setTitle("Scene Switcher");
primaryStage.show();
}


// Event handlers for scene 1 →91

public void btnClickMe_Click()
{
iClickCount++;
if (iClickCount == 1)
{
lblClicks.setText("You have clicked once.");
}
else
{
lblClicks.setText("You have clicked "
+ iClickCount + " times." );
}
}

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


// Event handlers for scene 2 →112

private void btnAdd_Click()
{
iCounter++;
lblCounter.setText(Integer.toString(iCounter));
}

private void btnSubtract_Click()
{
iCounter--;
lblCounter.setText(Integer.toString(iCounter));
}

private void btnSwitchToScene1_Click()
{
stage.setScene(scene1);

}

}

The following paragraphs point out some key sections of the program:

  1. →15: The section of the programs defines class fields that will be used by the scene for the Click-Counter portion of the program. These fields include iClickCount , used to count the number of times the user has clicked the Click Me! Button; the label used to display the count of how many times the Click Me! button has been clicked; the Click Me! button itself; and the button used to switch to the Add-Subtract scene. Also included is a Scene field named scene1 that will be used to reference the Click Counter scene.
  2. →22: These lines define class variables used by the Add-Subtract portion of the program, including the counter (iCounter ), the label used to display the counter, the two buttons used to increment and decrement the counter, the button used to switch back to the Click-Counter scene, and a Scene field named scene2 that will be used to reference the Add-Subtract scene.
  3. →31: A class field named stage is used to hold a reference to the primary stage so that it can be accessed throughout the program.
  4. →35: This line sets stage class field to reference the primary stage.
  5. →37: This section of the program builds the Click-Counter scene. First, it creates the label and buttons displayed by the scene. Then it creates a VBox layout pane (which lays out its controls in a vertical stack) and adds the label and buttons to the pane. Finally, it creates the scene using the VBox pane as its root.
  6. →59: This section of the program builds the Add-Subtract scene by creating the label and the buttons displayed by the scene, arranging them in an HBox layout pane, and creating the scene using the HBox pane as its root.
  7. →84: These lines set the Click-Counter scene as the root scene for the primary stage, sets the stage title, and then shows the stage.
  8. →91: This section of the program provides the event handlers for the buttons in the Click-Counter scene. The event handler for the Click Me! button increments the click counter, then sets the label to display an appropriate message. The handler for btnSwitchToScene2 simply switches the scene of the primary stage to scene2 , which instantly switches the display to the Add-Subtract scene as shown in the right side of Figure 3-1 .
  9. →112: This section of the program provides the event handlers for the buttons in the Add-Subtract scene. The event handler for the Add and Subtract buttons increment or decrement the counter and update the text displayed by the label. The handler for btnSwitchToScene1 switches the scene back to scene1 , which switches the display back to the Click-Counter scene shown in the left right side of Figure 3-1 .

Creating an Alert Box

JavaFX provides a simple means of displaying a basic message box by using the Alert class, which is similar to the JOptionPane class you learned back in Chapter 2 of Book 2. Table 3-3 shows the commonly used constructors and methods for this class.

warning The Alert class was introduced with Java version 1.8.0_40, so you must be using a version of Java more recent than that to use this feature. If you get compiler errors when you try to use the Alert class, upgrade to the most current version of Java.

The AlertType parameter lets you specify one of several types of Alert dialogs:

Here’s a snippet of code that displays a simple informational message using the Alert class:

Alert a = new Alert(Alert.AlertType.INFORMATION, "You have clicked once.");
a.showAndWait();

Figure 3-2 shows the resulting alert box.

image

FIGURE 3-2: An Alert dialog box.

You can control what buttons appear on the Alert box by using the optional ButtonType parameter. You can choose from among the following types of buttons:

You can include more than one button on an Alert box by simply specifying more than one ButtonType parameters in the constructor. For example:

Alert a = new Alert(Alert.AlertType.INFORMATION, "Are you certain?",
ButtonType.YES, ButtonType.NO);

In this example, the Alert box will include both a YES and a NO button.

To determine which button the user clicked, you must test the value returned by the showAndWait method. This value is an object of type Optional , since the user can close the dialog box without clicking any of the displayed buttons. You should first test whether the user clicked a button by calling the isPresent method. Then, you can call the get method to retrieve the actual result.

Here’s an example that determines whether the user clicked the YES button:

Alert a = new Alert(Alert.AlertType.INFORMATION, "Are you certain?",
ButtonType.YES, ButtonType.NO);
Optional<ButtonType> r = a.showAndWait();
if (r.isPresent() && r.get() == ButtonType.YES)
{
// The user clicked OK!
}

To demonstrate how you might use the ALERT class, the program shown in Listing 3-2 is a variation of the ClickCounter program that was originally discussed in Chapter 2 . The original version of this program displayed a label and a button, using the label to display a count of how many times the user has clicked the button. This version of the program dispenses with the label and instead uses the Alert class to display a message indicating how many times the user has clicked the button.

LISTING 3-2 The ClickCounterAlert Program

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

public class ClickCounterAlert extends Application
{
public static void main(String[] args)
{
launch(args);
}

Button btn;
Label lbl;
int iClickCount = 0;

@Override public void start(Stage primaryStage)
{
// Create the button
btn = new Button();
btn.setText("Click me please!");
btn.setOnAction(e -> buttonClick());

// Add the button to a layout pane
BorderPane pane = new BorderPane();
pane.setCenter(btn);

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

// Add the scene to the stage, set the title
// and show the stage
primaryStage.setScene(scene);
primaryStage.setTitle("Click Counter");
primaryStage.show();
}

public void buttonClick()
{
iClickCount++;
if (iClickCount == 1)
{
Alert a = new Alert(Alert.AlertType.INFORMATION, "You have clicked once." );
a.showAndWait();
}
else
{
Alert a = new Alert(Alert.AlertType.INFORMATION, "You have clicked "
+ iClickCount + " times.");
a.showAndWait();
}
}
}

This program is nearly identical to the version that was presented in Chapter 2 (in Listing 2-2 ). In fact, here are the only two differences:

Figure 3-2 shows the new version of the ClickCounter program in action. Here, you can see the alert box displayed when the user clicks the button the first time. (In this example, I moved the message box to the side a little so that you can see the primary stage beneath it. When you actually run the program, the message box is centered over the primary stage.)

Exit, Stage Right

Because I started this chapter by quoting Shakespeare, I thought it’d be nice to end it by quoting Snagglepuss, the famous pink mountain lion from the old Hanna-Barbera cartoons. He’d often leave the scene by saying, “Exit, stage left” or “Exit, stage right.”

Heavens to Mergatroyd!

There’s a right way and a wrong way to exit the stage, even. And so far, none of the programs presented in this book have done it the right way. The only mechanism the programs you’ve seen so far have provided to quit the program is for the user to click the standard window close button, typically represented by an X in the upper-right corner of the window’s title bar. That is almost always the wrong way to exit a program.

In most cases, the correct way to exit a program involves the following details:

In the following sections, you read about how to add a Close button to your application, how to prevent the window close button from unceremoniously terminating your application, and how to put these two elements together in a complete program.

Creating a Close button

To add a button or other user-interface element that allows the user to close the button, all you have to do is provide an action event handler that calls the stage’s close method.

For example, suppose you create a Close button using the following code:

Button btnClose = new Button();
btnClose.setText("Close");
btnClose.setOnAction( e -> primaryStage.close() );

In this case, the action event handler simply calls primaryStage.close() to close the application.

If you want to do more than simply call the close method in the action event handler, you may want to isolate the event handler in a separate method, as in this example:

btnClose.setOnAction( e -> btnClose_Clicked());

Because the btnClose_Clicked method will need to access the primary stage to close it, you need to define a class field of type Stage and use it to reference the primary stage. Then, your btnClose_Clicked method can easily perform additional tasks. For example:

private void btnClose_Click()
{
boolean reallyQuit = false;
reallyQuit = ConfirmationBox.show(
"Are you sure you want to quit?",
"Confirmation",
"Yes", "No");
if (reallyQuit)
{
// Perform cleanup tasks here
// such as saving files or freeing resources
stage.close();
}
}

In this example, a confirmation box is displayed to make sure the user really wants to exit the program.

Handling the CloseRequest event

Providing a Close button is an excellent way to allow your users to cleanly exit from your program. However, the user can bypass your exit processing by simply closing the window — that is, by clicking the window close button, usually represented as an X in the upper-right corner of the window border. Unless you provide otherwise, clicking this button unceremoniously terminates the application, bypassing all your nice code that confirms whether the user wants to save his work, closes any open resources, and otherwise provides for a graceful exit.

Fortunately, you can easily avoid such ungraceful exits. Whenever the user attempts to close the window within which a JavaFX stage is displayed, JavaFX generates a CloseRequest event, which is sent to the stage. You can provide an event handler for this event by calling the setOnCloseRequest method of the Stage class. Then, the event handler is called whenever the user tries to close the window.

You might be tempted to create a single method that can serve as the event handler for both the Action event of your Close button and the CloseRequest event, like this:

btnClose.setText("Close");
btnClose.setOnAction( e -> btnClose_Click () );
primaryStage.setOnCloseRequest( e -> btnClose_Click () );

Here, the intent is to handle the CloseRequest event exactly as if the user had clicked the btnClose button.

That’s a good idea, but it doesn’t work if the btnClose_Click event displays a confirmation box and closes the stage only if the user confirms that she really wants to quit the program. That’s because when the event handler for the CloseRequest event ends, JavaFX automatically closes the stage if the event handler doesn’t explicitly close the stage.

To prevent that from happening, you call the consume method of the CloseRequest event object. Consuming the event causes it to be stopped in its tracks within the event handler, thus preventing JavaFX from automatically closing the stage when the event handler ends.

In the Lambda expression passed to the setOnCloseRequest method, the CloseRequest event object is represented by the argument e . Thus, you can consume the CloseRequest event by calling e.consume() .

An easy way to provide a method that handles both the Action event for a Close button and the CloseRequest event for a stage is to craft the Lambda expression for the setOnCloseRequest method so that it consumes the event before calling the method that will handle the event:

btnClose.setText("Close");
btnClose.setOnAction( e -> btnClose_Click () );
primaryStage.setOnCloseRequest(
e -> {
e.consume();
btnClose_Click ();
} );

Here, the event handler for the CloseRequest event first consumes the event and then calls btnClose_Click . The btnClose_Click method, in turn, displays a confirmation box and closes the stage if the user confirms that this is indeed what he wishes to do.

Putting it all together

Now that you know how to add a Close button to a scene and how to handle the CloseRequest event, I look at a program that puts together these two elements to demonstrate the correct way to exit a JavaFX program.

This section presents a variation of the ClickCounter program that includes a Close button in addition to the Click Me! button. When the user clicks the Click Me! button, a message box displays to indicate how many times the button has been clicked. But when the user attempts to exit the program, whether by clicking the Close button or by simply closing the window, the ConfirmationBox class that was shown in Listing 3-3 is used to ask the user whether she really wants to exit the program. Then, the stage is closed only if the user clicks the Yes button in the confirmation box.

The source code for this program is shown in Listing 3-3 .

LISTING 3-3 The ClickCounterExit program

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

public class ClickCounterExit extends Application
{
public static void main(String[] args)
{
launch(args);
}

Stage stage;
int iClickCount = 0;


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

// Create the Click Me button
Button btnClickMe = new Button();
btnClickMe.setText("Click me please!");
btnClickMe.setOnAction(e -> btnClickMe_Click());

// Create the Close button
Button btnClose = new Button();
btnClose.setText("Close");
btnClose.setOnAction(e -> btnClose_Click());

// Add the buttons to a layout pane
VBox pane = new VBox(10);
pane.getChildren().addAll(btnClickMe, btnClose);
pane.setAlignment(Pos.CENTER);

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

// Finish and show the stage
primaryStage.setScene(scene);
primaryStage.setTitle("Click Counter");
primaryStage.setOnCloseRequest( e ->
{
e.consume();
btnClose_Click();
} );
primaryStage.show();
}

public void btnClickMe_Click()
{
iClickCount++;
if (iClickCount == 1)
{
Alert a = new Alert(Alert.AlertType.INFORMATION, "You have clicked once." );
a.showAndWait();
}
else
{
Alert a = new Alert(Alert.AlertType.INFORMATION, "You have clicked "
+ iClickCount + " times.");
a.showAndWait();
}
}

public void btnClose_Click()
{
Alert a = new Alert(Alert.AlertType.CONFIRMATION,
"Are you sure you want to quit?",
ButtonType.YES, ButtonType.NO);
Optional<ButtonType> confirm = a.showAndWait();
if (confirm.isPresent() && confirm.get() == ButtonType.YES)
{
stage.close();
}
}

}