Section 6.1
A Basic JavaFX Application
The command-line programs that you have learned how to write would seem very alien to most computer users. The style of interaction where the user and the computer take turns typing strings of text seems like something out of the early days of computing, although it was only in the mid 1980s that home computers with graphical user interfaces started to become available. Today, most people interact with their computers exclusively through a GUI. A GUI program offers a much richer type of user interface, where the user uses a mouse and keyboard (or other input devices) to interact with GUI components such as windows, menus, buttons, check boxes, text input boxes, scroll bars, and so on.
This section introduces some of the basic ideas of programming with JavaFX by looking at a very simple GUI application. ("Application" is the preferred term for "program" in this context.) The application simply displays a window containing a message and three buttons. Here's what the window looks like when it first opens:
Clicking "Say Hello" will get the computer to tell you, "Hello World!". Clicking "Say Goodbye" will change the text of the message to "Goodbye". Clicking the "Quit" button will end the application, which can also be ended by clicking the window's close box.
6.1.1 JavaFX Applications
A JavaFX program (or "application") is represented by an object of type Application, which is defined in the package javafx.application. Application is an abstract class, which defines, among other things, one abstract instance method, named start(). To create a JavaFX program, you need to create a class that extends Application and provides a definition for the start() method. (See Subsection 5.5.1 and Subsection 5.5.5.)
The class that you write to create a JavaFX application also typically includes a main() method that simply "launches" the application:
public static void main(String[] args) { launch(); }
(The call to launch() can be replace by launch(args) for an application that uses command-line arguments (Subsection 4.3.6), but the parameter is optional for all of the examples in this book.) When this main() routine is executed, the launch() method creates a new thread, called the JavaFX application thread. Recall from Section 1.2 that a thread can execute a sequence of instructions that can be run in parallel with other threads. It is important that anything that affects the GUI be done on the JavaFX application thread. That will be pretty much automatic for the things that we do in this chapter, but it's something that will become important when we cover threads in Chapter 12 and write some GUI programs that use several threads. The launch() method then creates the object that represents the application; that object is an instance of the class that contains the call to the launch() method. The start() method of that object is then called on the JavaFX application thread, and it is the responsibility of that start() method to set up the GUI and open a window on the screen.
Here, then is our first JavaFX application. We will spend the rest of this section discussing it:
import javafx.application.Application; import javafx.scene.Scene; import javafx.stage.Stage; import javafx.application.Platform; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; import javafx.geometry.Pos; import javafx.scene.control.Label; import javafx.scene.control.Button; import javafx.scene.text.Font; public class HelloWorldFX extends Application { public void start(Stage stage) { Label message = new Label("First FX Application!"); message.setFont( new Font(40) ); Button helloButton = new Button("Say Hello"); helloButton.setOnAction( evt -> message.setText("Hello World!") ); Button goodbyeButton = new Button("Say Goodbye"); goodbyeButton.setOnAction( evt -> message.setText("Goodbye!!") ); Button quitButton = new Button("Quit"); quitButton.setOnAction( evt -> Platform.exit() ); HBox buttonBar = new HBox( 20, helloButton, goodbyeButton, quitButton ); buttonBar.setAlignment(Pos.CENTER); BorderPane root = new BorderPane(); root.setCenter(message); root.setBottom(buttonBar); Scene scene = new Scene(root, 450, 200); stage.setScene(scene); stage.setTitle("JavaFX Test"); stage.show(); } // end start(); public static void main(String[] args) { launch(); // Run this Application. } } // end class HelloWorldFX
The first thing that you will notice is the large number of imports at the start of the program, all from subpackages of the javafx package. A typical JavaFX program uses many classes from such packages. When I discuss a JavaFX class for the first time, I will usually mention the package that it comes from. But in any case, you can look up the class in the JavaFX API documentation. As I write this, the documentation for JavaFX 17 can be be found at https://openjfx.io/javadoc/17/.
The HelloWorldFX program contains a main method to launch the application, and it contains the required start() method. Of course, we will often add other methods to our application classes, to be called by start(). There are also a couple other methods in Application that can be overridden. In particular, there is an init(), that will be called by the system before start(), and a stop() method that is called by the system when the application is shutting down. These two methods are defined in class Application to do nothing. A programmer can redefine init() to do some initialization and stop() to do cleanup. However, we will rarely if ever need them. Any initialization that we need can be done in start().
6.1.2 Stage, Scene, and SceneGraph
The start() method has a parameter of type Stage, from package javafx.stage. A Stage object represents a window on the computer's screen. The stage that is passed as a parameter to start() is constructed by the system. It represents the main window of a program, and is often referred to as the "primary stage." A program can create other windows by constructing new objects of type Stage.
A window is an area on the screen that can be filled with content. It can contain GUI components such as menus, buttons, and text input boxes, as well as drawing areas like those used in the graphical programs from Section 3.9. Although the primary stage is created before start() is called, the window does not have any content, and it is not yet visible on the screen. The start() method is responsible for adding content to the window and making it visible. The very last line of start() in the HelloWorldFX program, stage.show(), is what makes the window visible. The rest of the method creates content, adds the content to the window, and sets various configuration options for the content and for the window itself. For example, the line
stage.setTitle("JavaFX Test");
sets the text that will appear in the title bar at the top of the window.
A stage shows a scene, which fills its content area and serves as a container for the GUI components in the window. A scene is represented by an object of type Scene. In the sample program, the statement
stage.setScene(scene);
sets the scene that will be displayed in the content area of the stage.
A scene can be filled with things called GUI components, such as buttons and menu bars. Each component is represented by an object belonging to a JavaFX class. For example, a push button such as the "Say Hello" button in the sample program, is represented by an object belonging to the class Button, from the package javafx.scene.control. Some components, such as the object buttonBar of type HBox, are containers. A container represents a region in the window that can contain other components, including other containers. So, a window contains GUI components, inside containers, which can be inside bigger containers, each represented by an object. All of these objects make up something called the scene graph for the window. The scene graph shows the containment relationships among all the components in the scene. For the sample program, the scene graph looks like this:
Note that this is not a class hierarchy. It does not show the relationships among the classes of the objects in the program; rather, it is a containment hierarchy that shows how the components are contained within other components on the screen. In this scene graph, root and buttonBar are containers while message and the three buttons are simple components.
A scene contains a single "root" component, which is a container that contains all of the other components in the scene. In the sample program, the root component is named root (although of course that is not required), and the root of the scene is set when the Scene object is constructed:
Scene scene = new Scene(root, 450, 200);
The numbers in this constructor specify the width and the height of the scene, in pixels. The numbers can be omitted, in which case the size will be computed based on the contents of the scene.
6.1.3 Nodes and Layout
Objects that can be part of a scene graph are referred to as nodes. Scene graph nodes must belong to one of the subclasses of javafx.scene.Node. Scene graph objects that can act as containers must belong to one of the subclasses of javafx.scene.Parent, which is a subclass of Node. The nodes that are contained in a parent are called children of that node. The root node in a scene graph must be a Parent.
The buttons in HelloWorldFX are represented by objects of type Button. The constructor that is used to create the button objects specifies the text that is displayed on the button. Similarly, message is a node of type Label, from package javafx.scene.control, whose purpose is simply to passively display a String. One of the properties of a Label object is a font, which specifies the size and style of the characters in the displayed string. The font for the text is set by calling the label's setFont() method. The Font constructor that is used in the sample program, new Font(40), takes a parameter that specifies the size of the font.
Containers are Nodes which can have other nodes as children. The act of arranging a container's children on the screen is referred to as layout. Layout means setting the size and location of the components inside the container. While it is possible for a program to set the sizes and locations directly, it is more commonly done automatically by the container. Different containers implement different layout policies. For example, an HBox is a container that simply arranges the components that it contains in a horizontal row. In the constructor
HBox buttonBar = new HBox( 20, helloButton, goodbyeButton, quitButton );
the first parameter specifies the size of a gap that the HBox will place between its children, and the remaining parameters are nodes to be added as children of the container.
A BorderPane is a container that implements a completely different layout policy. A BorderPane can contain up to five components, one in the center of the pane and up to four more placed at the top, at the bottom, to the left, and to the right of the center. In the sample program, the root of the scene is a BorderPane and components are added in the pane's center and bottom positions with the statements
root.setCenter(message); root.setBottom(buttonBar);
Layout is configurable by a large number of options. The sample program has only one example of this,
buttonBar.setAlignment(Pos.CENTER);
This command centers the buttons within the HBox; without it, they would be shoved over to the left edge of the window. Pos, short for "position," is an enumerated type (see Subsection 2.3.5). JavaFX uses many enumerated types for specifying various options.
6.1.4 Events and Event Handlers
In addition to setting up the physical layout of the window, the start() method configures event handling. In HelloWorldFX, an event occurs when the user clicks one of the buttons. The application must be configured to respond to, or "handle," these events. Handling an event involves two objects. The event itself is represented by an object that holds information about the event. For a button click, the event is of type ActionEvent, and the information that it carries is the button that was clicked. The second object is of type EventHandler, a functional interface that defines a method handle(evt), where the parameter, evt, is the event object. To program a response to an event, you can create a class that implements the EventHandler interface and provides a definition for the handle() method. However, since EventHandler is a functional interface, the handler can alternatively be specified as a lambda expression (see Section 4.5). Lambda expressions are very commonly used in JavaFX for writing event handlers, among other uses. For example, the lambda expression
evt -> message.setText("Hello World!")
represents an event handler that responds to an event by changing the text of the message to read "Hello World!". The parameter, evt, is the ActionEvent object that represents the event. In this case, the parameter is not used in the response in any way, but it still has to be there to satisfy the syntax of the lambda expression: Since the function in the EventHandler interface has a parameter, the lambda expression must have a parameter to match the interface.
In addition to writing the event handler, you also have to register the handler with the object that will produce the event. In this case, the object is helloButton, and the handler is registered by calling the button's setOnAction() method:
helloButton.setOnAction( evt -> message.setText("Hello World!") );
Handlers for each of the other two buttons are set up in a similar way. Remember that in each case, there is an object that generates the event in response to a user action, an object that represents the event, and an event handler that contains the code that is executed in response to the event. This diagram summarizes how it all works:
About all that still remains to be explained in the sample program is the response to a click on the "Quit" button: Platform.exit(). The static exit() method in the Platform class is the preferred way to programmatically end a JavaFX program. It is preferred to System.exit() because it cleanly shuts down the application thread and gives it an opportunity to clean up by calling the application's stop() method before terminating.
This section has been only a brief overview of JavaFX applications, but it has introduced many of the fundamental concepts. We will cover all of this in much greater detail in the following sections.