Chapter 3
IN THIS CHAPTER
Looking at some useful methods of the Stage and Scene classes
Alternating scenes within a single stage
Displaying alert boxes
Discovering the proper way to exit a JavaFX program
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.
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 mobile devices, 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.
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 |
|
Closes the stage. |
|
Sets the modality of the stage. This method must be called before the
|
|
Sets the style for the stage. This method must be called before the
|
|
Gets the maximum height for the stage. |
|
Gets the maximum width for the stage. |
|
Gets the minimum height for the stage. |
|
Gets the minimum width for the stage. |
|
Sets the fullscreen status of the stage. |
|
Sets the iconified status of the stage. |
|
Sets the maximized status of the stage. |
|
Sets the maximum height for the stage. |
|
Sets the maximum width for the stage. |
|
Sets the minimum height for the stage. |
|
Sets the minimum width for the stage. |
|
Controls whether the user can resize the stage. |
|
Sets the scene to be displayed on the stage. |
|
Sets the title to be displayed in the stage's title bar, if a title bar is visible. |
|
Makes the stage visible. |
|
Makes the stage visible and then waits until the stage is closed before continuing. |
|
Forces the stage to the foreground. |
|
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:
For many (if not most) applications, the only three methods from Table 3-1 you need to use are setScene
, setTitle
, and show
.
The other methods in the table let you change the appearance or behavior of the stage, but the defaults are acceptable in most cases.
setResizable
method like this:
primaryStage.setResizable(false);
Then, the user can’t change the size of the window. (By default, the stage is resizable. Thus, you don’t need to call the setResizable
method unless you want to make the stage non-resizable.)
primaryStage.setResizable(true);
primaryStage.setMinWidth(200);
primaryStage.setMinHeight(200);
primaryStage.setMaxWidth(600);
primaryStage.setMaxHeight(600);
In this example, the user can resize the window, but the smallest allowable size is 200-x-200 pixels and the largest allowable size is 600-x-600 pixels.
setMaximized
:
primaryStage.setMaximized(true);
A maximized window still has the usual decorations (a title bar, window borders, and Minimize, Restore, and Close buttons). If you want the stage to completely take over the screen with no such decorations, use the setFullScreen
method instead:
primaryStage.setFullScreen(true);
When your stage enters fullscreen mode, JavaFX displays a message advising the user on how to exit fullscreen mode.
setIconified
method:
primaryStage.setIconified(true);
close
method, see the section “Exit, Stage Right” later in this chapter.initModality
and initStyle
methods are interesting because they can be called only before you call the show
method. The initModality
method allows you to create a modal dialog box — that is, a window that must be closed before the user can continue using other functions within the program. And the initStyle
method lets you create windows that do not have the usual decorations such as a title bar or Minimize, Restore, and Close buttons. You typically use these methods when you need to create additional stages for your application beyond the primary stage. You can read more about how that works later in this chapter, in the section “Creating a Dialog Box.”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 |
|
Creates a new scene with the specified root node |
|
Creates a new scene with the specified root node, width, and height |
Method |
Description |
|
Gets the height of the scene |
|
Gets the width of the scene |
|
Gets the horizontal position of the scene |
|
Gets the vertical position of the screen |
|
Sets the root node |
The following paragraphs explain some of the more interesting details of the constructors and methods of the Scene
class:
All the Scene
class constructors require that you specify the root node.
You can change the root node later by calling the setRoot
method, but it's not possible to create a scene without a root node.
Parent
class rather than an instance of the Node
class. The Parent
class is actually a subclass of the Node
class, which represents a node that can have child nodes. There are several other subclasses of Node
, which represent nodes that can’t have children; those nodes can’t be used as the root node for a scene.You can set the scene’s initial size when you create it by specifying the Width
and Height
parameters.
If you don’t set the size, the scene will determine its own size based on its content.
You can retrieve the size of the scene via the getHeight
and getWidth
methods.
There are no corresponding set
methods that let you set the height or width.
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:
FIGURE 3-1: The SceneSwitcher program.
LISTING 3-1 The SceneSwitcher Program
package com.lowewriter.sceneswitcher;
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→17
int iClickCount = 0;
Label lblClicks;
Button btnClickMe;
Button btnSwitchToScene2;
Scene scene1;
// class fields for Add-Subtract scene→24
int iCounter = 0;
Label lblCounter;
Button btnAdd;
Button btnSubtract;
Button btnSwitchToScene1;
Scene scene2;
// class field for stage
Stage stage;→33
@Override public void start(Stage primaryStage)
{
stage = primaryStage;→37
// Build the Click-Counter scene→39
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→61
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→87
primaryStage.setScene(scene1);
primaryStage.setTitle("Scene Switcher");
primaryStage.show();
}
// Event handlers for scene 1→94
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→116
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:
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.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.stage
is used to hold a reference to the primary stage so that it can be accessed throughout the program.stage
class field to reference the primary stage.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.HBox
layout pane, and creating the scene using the HBox
pane as its root.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.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.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 Book 2, Chapter 2. Table 3-3 shows the commonly used constructors and methods for this class.
TABLE 3-3 Commonly Used Constructors and Methods of the Alert class
Constructor |
Description |
|
Creates a new alert of the specified type |
|
Creates a new alert and optionally sets one or more buttons to be displayed |
Method |
Description |
|
Sets the title |
|
Shows the alert and waits for the user's response, which is returned as a |
The AlertType
parameter lets you specify one of several types of Alert dialogs:
AlertType.CONFIRMATION
, which prompts the user to confirm an action.AlertType.ERROR
, which display an error message.AlertType.INFORMATION
, which displays an information dialog box.AlertType.WARNING
, which displays a warning message.AlertType.NONE
, which display a generic alert dialog.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.
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:
ButtonType.APPLY
ButtonType.CANCEL
ButtonType.CLOSE
ButtonType.FINISH
ButtonType.NEXT
ButtonType.NO
ButtonType.OK
ButtonType.PREVIOUS
ButtonType.YES
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 of this minibook. 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
package com.lowewriter.clickcounteralert;
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 of this minibook (in Listing 2-2). In fact, here are the only two differences:
buttonClick
method of the Chapter 2 version, the label’s setText
method was called to display the number of times the button has been clicked. In this version, an Alert box is used instead.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.
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:
Adding a button, menu command, or other way for the user to signal that she wishes to close the program.
Many programs include a button labeled Exit or Close, and programs that use a menu usually have an Exit command.
Optionally displaying a confirmation box that verifies whether the user really wants to close the program. You can do that by using the ConfirmationBox
class shown in the preceding section or by using a similar class.
Depending on the program, you might want to display this dialog box only if the user has made changes to a document, database, or other file that have not yet been saved.
close
method.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.
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.
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.
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
package com.lowewriter.clickcounterexit;
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();
}
}
}