Skip to content

Programming by Design

If you're not prepared to be wrong, you'll never come up with anything original. – Sir Ken Robinson

  • About
  • Java-PbD
  • C-PbD
  • ASM-PbD
  • Algorithms
  • Other

Chapter 7 – Graphical User Interface – JavaFX

Posted on June 2, 2019January 10, 2025 By William Jojo
Java Book

(Updated January 10, 2025)

Table of contents

    JavaFX Overview
    Dialogs
    JavaFX Application
    The Stage
    The Scene
    Common UI Controls
    Layouts
    Events
    Graphics
    Exercises
Important Note!
The code in this chapter is not supported in JDoodle! You will not find a button to launch these code examples. You must use the copy option and paste it into your IDE of choice.

JavaFX Overview

The JavaFX runtime environment is built upon the idea of a JavaFX application. Unlike Swing, a series of components your application can extend, JavaFX uses the Application class to create the running program.

Important Note!
JavaFX is not included in most JDKs. It would be best if you endeavored to run an LTS version of Java JDK for development that includes JavaFX. Currently, that is 11, 17, and 21, and Azul’s Zulu JDK/FX is a solid choice. You can use this document for the Java specifics.

The Application class implements a few key methods, as shown in Table 1. Specifically, these methods concern a JavaFX application’s structure and life cycle.

Some methods of the Application class.
void init()
This method initializes the application. The default init method does nothing. You can override this to perform initialization tasks. This method must never create any javafx.stage.Stage or javafx.scene.Scene objects. Overriding this method is not required.
void start(Stage primaryStage)
This method is where all the magic happens. You override this method to build and launch your application.
void stop()
This method can tear down anything that needs finalizing, closing files, etc. If you override the stop() method, you may perform such tasks. The default stop() method does nothing. Like the init() method, it is not necessary to override this method.

Table 1: Some methods available to the Application class.

The process of application startup and termination is as follows:

  1. The Application instance is created.
  2. The init() method is invoked on the JavaFX Launcher Thread.
  3. The start() method is invoked on the JavaFX Application Thread.
  4. The runtime waits for the application to finish. This can be when javafx.application.Platform.exit() is invoked or when the last window has been closed and Platform‘s implicitExit attribute is set to true.
  5. The stop() method is invoked on the JavaFX Application Thread.

There is no need for a main() method when creating a JavaFX application. However, if you will make a JAR file type executable, you will need to provide one.

The JavaFX program in Example 1 demonstrates the life cycle of a JavaFX application. This is for demonstration purposes only. This is not a typical framework.

JavaFXLifeCycle.java
import javafx.application.Application;
import javafx.application.Platform;
import javafx.stage.Stage;

public class JavaFXLifeCycle extends Application {


    /**
     * JavaFX Init routine. This is called before start().
     */
    @Override
    public void init() {
        System.out.printf("init() called on thread %s%n",
                Thread.currentThread());
    }

    /**
     * Start is where your code goes in a typical application.
     * @param primaryStage the main stage provided by JavaFX
     */
    @Override
    public void start(Stage primaryStage) {
        System.out.printf("start() called on thread %s%n",
                Thread.currentThread());
        Platform.exit(); // the stop() will be called.
    }

    /**
     * We're done. Do any necessary cleanup.
     */
    @Override
    public void stop() {
        System.out.printf("stop() called on thread %s%n",
                Thread.currentThread());
    }
}

Example 1: JavaFX life cycle demonstrated.

Sample output is provided below.

init() called on thread Thread[#25,JavaFX-Launcher,5,main]
start() called on thread Thread[#23,JavaFX Application Thread,5,main]
stop() called on thread Thread[#23,JavaFX Application Thread,5,main]

It is important to recognize that it is the architecture of Application that drives the order of execution of the methods. Although Example 1 declares the methods in the order init(), start() then stop(), the order of the declarations has no impact on the order of execution.

Important Note!
Example 1 is intended only to show the flow of a JavaFX application. This code would not be used in your general programs unless you needed to step in to perform specific tasks.

Now, we will consider a basic template for a simple JavaFX program. One possible template for a JavaFX application could look like Example 2.

JavaFXTemplate.java
import javafx.application.Application;
import javafx.application.Platform;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.VBox;

public class JavaFXTemaplate extends Application {

    @Override
    public void start(Stage primaryStage) {
        // code goes here!
        VBox vbox = new VBox();
        Scene scene = new Scene(vbox, 300, 200);
        primaryStage.setTitle("JavaFX Template");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public static void main(String[] args) {
        // standard JavaFX Application object launch
        launch(args);
    }
}

Example 2: A JavaFX template.

Although we have not yet discussed layouts, one is necessary to create a Scene and put it on the Stage. Our window is 300 pixels wide by 200 pixels tall.

The code in Example 2 will display a decorated window as in Figure 1.

JavaFX Template window
Figure 1: Window displayed when the primaryStage is shown.

Technical Note
Example 2 has a main() method that is technically not required. It’s provided here and in other examples as a reminder of where things begin.

Recall, however, that init(), start(), and stop() are actually running the show here. The use of the main() method is only absolutely necessary if you are passing arguments from the command line.


Dialogs

The class Dialog and three subclasses Alert, ChoiceDialog and TextInputDialog were added starting in Java 8u40.

Several examples of dialogs are shown below. You will begin to notice a pattern with these examples. Specifically, certain methods are frequently called for each type of dialog. You will also note the absence of Platform.exit() as it is not necessary to demonstrate the different kinds of Dialog.

TestAlert.java
import javafx.application.Application;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.stage.Stage;

public class TestAlert extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception {

        Alert alert = new Alert(AlertType.ERROR);
        alert.setTitle("Error Dialog");
        alert.setHeaderText("Unrecoverable error!");
        alert.setContentText("The application needs to close.");

        alert.showAndWait();

    }
}

Example 3: Alert dialog.

Value of AlertType Example
AlertType.ERROR Alert type error
AlertType.INFORMATION Alert type information
AlertType.NONE Alert type none
AlertType.CONFIRMATION Alert type confirmation
AlertType.WARNING Alert type warning

Table 2: Enumerated list of AlertType values.

TestChoiceDialog.java
import javafx.application.Application;
import javafx.scene.control.ChoiceDialog;
import javafx.stage.Stage;
import java.util.Optional;

public class TestChoiceDialog extends Application {

    @Override
    public void start(Stage primaryStage) {

        // array of choices
        String[] days = { "Sunday", "Monday", "Tuesday", "Wednesday",
                "Thursday", "Friday", "Saturday" };

        // populate the dialog options with Monday as the default.
        ChoiceDialog<String> dialog = new ChoiceDialog<>(days[1], days);
        dialog.setTitle("Choice Dialog");
        dialog.setHeaderText("Which day would you prefer delivery?");
        dialog.setContentText("Choose the day:");

        // prompt and get choice.
        Optional<String> result = dialog.showAndWait();
        if (result.isPresent())
            System.out.println("Your choice: " + result.get());
    }
}

Example 4: Using a ChoiceDialog.

Choice dialog
Figure 2: ChoiceDialog showing preferred choice already selected.

TestTextInputDialog.java
import javafx.application.Application;
import javafx.scene.control.TextInputDialog;
import javafx.stage.Stage;
import java.util.Optional;

public class TestTextInputDialog extends Application {

    @Override
    public void start(Stage primaryStage) {
        // create dialog with default text
        TextInputDialog dialog = new TextInputDialog("default name");
        dialog.setTitle("Text Input Dialog");
        dialog.setHeaderText("We need a little more information.");
        dialog.setContentText("Please enter your name:");

        // prompt and get choice.
        Optional result = dialog.showAndWait();
        if (result.isPresent()){
            System.out.println("Your name: " + result.get());
        }
    }
}

Example 5: Using TextInputDialog.

Text input dialog
Figure 3: TextInputDialog with default text.


JavaFX Application

The JavaFX application is built on a hierarchy. Figure 4 shows the basic layering of a JavaFX application.

javafx hierarchy
Figure 4: JavaFX application hierarchy.

The stage is the window we are displaying. It contains all the objects of the JavaFX application. The stage is constructed by the Stage class available in the javafx.stage package.

The scene contains the scene graph which is displayed on the stage. The scene is constructed by the Scene class available in the javafx.scene package.

The scene graph is a hierarchical structure of nodes that define the scene as is plays out on the stage. Rather than create actual Node objects (the Node class is defined in javafx.scene), we create nodes as specific objects like buttons, check boxes, text areas, layout panes and media.


The Stage

We will begin the discussion of the stage by reiterating the code from Example 2 with a bit more detail. This is shown in Example 6.

BasicStage.java
import javafx.application.Application;
import javafx.application.Platform;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.VBox;

public class BasicStage extends Application {

    @Override
    public void start(Stage primaryStage) {
        // code goes here!
        VBox vbox = new VBox();
        Scene scene = new Scene(vbox, 300, 200);
        primaryStage.setTitle("JavaFX Template");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
}

Example 6: Just a basic stage.

The size of the stage is 300 pixels wide by 200 pixels high. Of course, a stage should have a scene. And, like a traditional scene performed on a stage, scenes are often made up of various characters.

The scene in this example is what drives the stage size. The code in Example 6 also shows how we can assign a title to the window before showing it. Remember that we must call show() before we will see the window. Upon closing the window, the program terminates.

You can have many stages. However, careful planning is necessary to ensure your stages behave as you desire. Multiple stages are beyond the scope of this introductory chapter.


The Scene

Having a stage is a beautiful thing! You can hold any performance on that stage. However, without a planned scene of what you wish to take place, the stage is an empty place. This is true both in the theater and in JavaFX. You must create a scene to be displayed on the stage. The scene contains a cast of characters who will carry out whatever needs to happen in the performance.

But it all starts with a scene. Recall the scene graph from Figure 4. It lists the nodes contained within the scene and shows their relationship to each other – root, branch and leaf. That scene graph could also be written as:

Stage
|-> Scene
    |-> Root Node
        |-> Leaf Node
        |-> Branch Node
            |-> Leaf Node
            |-> Leaf Node

The scene needs a root – a place to hang all the other components. We will use a simple VBox. A VBox is a kind of Layout, and these will be discussed later.

[NOTE: You may find some examples from other sources like to use Group for their basic examples. Frankly, the Group examples are misleading leaving you to think that working with Group is simple. Far from it. With Group, you are responsible for placing the nodes within the viewable scene. Therefore we will begin with an example that does the placement for us until we are ready to talk about arranging the scene.]

We will create a Label, TextField and a Button. Placing them in the VBox layout will automatically stack the objects vertically within the scene. This is demonstrated in Example 7a.

SetTheScene1.java
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.control.Button;
import javafx.scene.layout.VBox;

public class SetTheScene1 extends Application {

    @Override
    public void start(Stage primaryStage) {

        // all the things
        Label label;
        TextField tf;
        Button button;
        VBox vbox;
        Scene scene;

        // create some objects
        label = new Label("Label:");
        button = new Button("Button!");
        tf = new TextField("Text Field!");

        // creat the VBox and add them in order
        vbox = new VBox(label, tf, button);
        scene = new Scene(vbox, 300, 200);

        // and go!
        primaryStage.setTitle("A Simple Scene!");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
}

Example 7a: Setting the Scene with VBox.

Not let us describe what it going on. Lines 15-18 declare the components for managing the scene. The VBox is our root node while the Label, TextField and Button are leaf nodes. Lines 21-23 create the object and assign text values to each object. Line 25 creates the VBox object providing the label, tf and button variables to be placed in the VBox in that order.

Ultimately we are creating a scene graph like the following:

Stage
|-> Scene
    |-> VBox
        |-> Label
        |-> TextField
        |-> Button

The program creates a window like that in Figure 5a.

VBox with nodes
Figure 5a: The stacked components of the VBox.

We try to pretty things up a bit in the following example and make it more presentable.

SetTheScene2.java
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.control.Button;
import javafx.scene.layout.VBox;
import javafx.geometry.Pos;

public class SetTheScene2 extends Application {

    @Override
    public void start(Stage primaryStage) {

        // all the things
        Label label;
        TextField tf;
        Button button;
        VBox vbox;
        Scene scene;

        // create some objects
        label = new Label("Label:");
        button = new Button("Button!");
        tf = new TextField("Text Field!");
        tf.setMaxWidth(200);

        // creat the VBox and add them in order
        vbox = new VBox(label, tf, button);
        // but this time let's make them pretty
        vbox.setSpacing(20);
        vbox.setAlignment(Pos.CENTER);
        scene = new Scene(vbox, 300, 200);

        // and go!
        primaryStage.setTitle("A Simple Scene!");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
}

Example 7b: Setting the Scene with VBox and some minor additional formatting.

This introduces the Pos class (line 8) which allows us to specify how some things can be arranged vertically.

Table 4a shows how we can provide guidance on where items can be positioned. With the exception of CENTER, all positions are created by combining the prefixes BASELINE, BOTTOM, TOP and CENTER with the suffixes _CENTER, _LEFT and _RIGHT.

Constants Defining Poistion Using Pos.
Pos.CENTER
Positions horizontally and vertially center.
Pos.BASELINE_LEFT
Positions vertically baseline (top) and horizontally left.
Pos.TOP_CENTER
Positions vertically top and horizontally center.
Pos.BOTTOM_RIGHT
Positions vertically bottom and horizontally right.

Table 4a: Some of the constants defined by the enumeration Pos.

At line 25 we restrict how wide the TextField will be rather than the default of occupying the width of the VBox. We add additional spacing, also known as padding, at line 28. This avoids the objects being crowded together. Finally, line 29 centers the nodes vertically and horizontally.

VBox formatted
Figure 5b: The stacked components of the VBox with some additional formatting.

The following table defines some constructors of the Scene class. Our examples have used the second form.

Constructors of the Scene class.
Scene(Parent root)
Creates a Scene for root.
Scene(Parent root, double width, double height)
Creates a Scene for root with the given height and width.
Scene(Parent root, double width, double height, Paint fill)(
Creates a Scene for root with the given height and width and fill.
Scene(Parent root, Paint fill)(
Creates a Scene for root with the given fill.

Table 4b: Some constructors available in the Scene class.


Common UI Controls

Any GUI needs to have a variety of ways to offer the user choices in how information is managed. Whether you are designing a form for intake information in a doctor’s office or providing a mechanism to modify the settings for a game, you will need to have various tools to make the interface more intuitive.

Now, if you want to understand the science of interface design, one need look no further than the late Jef Raskin’s The Humane Interface. More software developers should read this book!

Below are listed a variety of user interface controls. This is not an exhaustive list. Nor is the list of constructors and methods anywhere near complete. This is intended to be introductory and two-fold in discovery. This section will use every UI control in an example program to show how they look. Later, after the layouts and events sections, we will revisit this code and lay it out a bit better, integrating button clicks to provide a better, well-rounded solution to wrap up the JavaFX discussion.

We will begin with a variarion of the code we used during the Scene discussion. This is what we are building:


Figure 6: A pile of UI Controls.

There is no default RadioButton selected at runtime. The user entered a password, selected Choice D, the Baguette and Nap until tomorrow. The code for this is shown in Example 8.

CommonUIControls.java
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.control.Button;
import javafx.scene.control.PasswordField;
import javafx.scene.control.RadioButton;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.VBox;
import javafx.geometry.Pos;

public class CommonUIControls extends Application {

    @Override
    public void start(Stage primaryStage) {

        Label label, pwl;
        TextField tf;
        Button button;
        PasswordField pwf;
        RadioButton rb1, rb2, rb3;
        CheckBox cb1, cb2;
        ToggleButton tb1, tb2;
        ToggleGroup tg1, tg2;
        VBox vbox;
        Scene scene;

        //Basic controls
        label = new Label("Label:");
        button = new Button("Button!");
        tf = new TextField("Text Field!");
        tf.setMaxWidth(200);

        // PasswordField
        pwl = new Label("Password:");
        pwf = new PasswordField();
        pwf.setMaxWidth(150);

        // RadioButton code
        rb1 = new RadioButton("Choice A");
        rb2 = new RadioButton("Choice 2");
        rb3 = new RadioButton("Choice D");
        tg1 = new ToggleGroup();
        rb1.setToggleGroup(tg1);
        rb2.setToggleGroup(tg1);
        rb3.setToggleGroup(tg1);

        // CheckBox code
        cb1 = new CheckBox("Fries");
        cb2 = new CheckBox("Baguette");

        // ToggleButton Code
        tb1 = new ToggleButton("Nap for 2 hours.");
        tb2 = new ToggleButton("Nap until tomorrow.");
        tg2 = new ToggleGroup();
        tb1.setToggleGroup(tg2);
        tb2.setToggleGroup(tg2);

        // load up the VBox!
        vbox = new VBox(label, tf, button, pwl, pwf, rb1, rb2, rb3, cb1, cb2, tb1, tb2);
        vbox.setSpacing(20);
        vbox.setAlignment(Pos.CENTER);
        scene = new Scene(vbox, 300, 500);

        // and go!
        primaryStage.setTitle("The Kitchen Sink!");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
}

Example 8: The kitchen sink of UI controls.

While most of the code is self-explanatory, a few things should be clarified with a little review of the tables (below). The radio buttons are connected together through a ToggleGroup. The ToggleGroup only allows one radio button to be selected within the group. This allows for simpler coding whereby the programmer does not need to select a radio button when another is selected.

The ToggleButtons are also tied together with a ToggleGroup. While a ToggleButton remains depressed after being selected, if another button option were mutually exclusive, the ToggleGroup “pops” the other ToggleButton when another in the group is selected.

The Button class.
Button()
Creates a Button with no text.
Button(String text)
Creates a Button setting the button text to text.
public final void setOnAction(EventHandler<ActionEvent> event)
Assigns an event handler for the button. [Inherited from javafx.scene.control.ButtonBase]

Table 5a: A few components of the Button class.

The Label class.
Label()
Creates a Label with no text.
Label(String text)
Creates a Label setting the label text to text.
public final void setMinWidth(double value)
Sets a specific minimum width if the default computed value does not meet the needs of the application. [Inherited from javafx.scene.layout.Region]
public final void setMaxWidth(double value)
Sets a specific maximum width if the default computed value does not meet the needs of the application. [Inherited from javafx.scene.layout.Region]

Table 5b: A few components of the Label class.

The TextField class.
TextField()
Creates a TextField with no text.
TextField(String text)
Creates a TextField setting the default text to text.
public final String getText()
Get the current String value within the TextField. [Inherited from javafx.scene.control.TextInputControl]
public final void setText(String text)
Set the value in the TextField to text. [Inherited from javafx.scene.control.TextInputControl]
public final void setMinWidth(double value)
Sets a specific minimum width if the default computed value does not meet the needs of the application. [Inherited from javafx.scene.layout.Region]
public final void setMaxWidth(double value)
Sets a specific maximum width if the default computed value does not meet the needs of the application. [Inherited from javafx.scene.layout.Region]

Table 5c: A few components of the TextField class.

The PasswordField class.
PasswordField()
Creates a PasswordField that does not display contents.
public final String getText()
Get the current String value within the PasswordField. [Inherited from javafx.scene.control.TextInputControl]
public final void setText(String text)
Set the value in the PasswordField to text. [Inherited from javafx.scene.control.TextInputControl]
public final void setMinWidth(double value)
Sets a specific minimum width if the default computed value does not meet the needs of the application. [Inherited from javafx.scene.layout.Region]
public final void setMaxWidth(double value)
Sets a specific maximum width if the default computed value does not meet the needs of the application. [Inherited from javafx.scene.layout.Region]

Table 5d: A few components of the PasswordField class.

The CheckBox class.
CheckBox()
Creates a CheckBox with no label value.
CheckBox(String text)
Creates a CheckBox setting the label to text.

Table 5e: A few components of the CheckBox class.

The RadioButton class.
RadioButton()
Creates a RadioButton with no label value.
RadioButton(String text)
Creates a RadioButton setting the label to text.
public final void setToggleGroup(ToggleGroup value)
Sets the ToggleGroup for the RadioButton. [Inherited from javafx.scene.layout.Region]

Table 5f: A few components of the RadioButton class.

The ToggleButton class.
ToggleButton()
Creates a RadioButton with no label value.
ToggleButton(String text)
Creates a ToggleButton setting the label to text.
public final void setToggleGroup(ToggleGroup value)
Sets the ToggleGroup for the ToggleButton. [Inherited from javafx.scene.layout.Region]

Table 5g: A few components of the ToggleButton class.

The ToggleGroup class.
ToggleGroup()
Creates a ToggleGroup for RadioButton or ToggleButton objects.
public final Toggle getSelectedToggle()
Returns the currect selected Toggle for the ToggleGroup.

Table 5h: A few components of the ToggleGroup class.


Layouts

We have already seen an introductory layout used in the Scene section. There we introduced the VBox layout.
Layouts help to determine how nodes will be placed within the scene graph. In this section we will introduce a few layout options. These include Group, HBox, VBox, GridPane, BorderPane and FlowPane.

The HBox Layout

HBoxDemo.java
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.HBox;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;

public class HBoxDemo extends Application {

    @Override
    public void start(Stage primaryStage) {
        // create HBox and add new objects
        HBox hb = new HBox(10, new Label("First Name:"), new TextField());
        Scene scene = new Scene(hb, 550, 70);

        // add additional objects to HBox
        hb.getChildren().addAll(new Label("Last Name:"), new TextField());

        // and go!
        primaryStage.setScene(scene);
        primaryStage.show();
    }
}

Example 9a: A simple HBox form.

HBox layout
Figure 7a:

The HBox layout.
HBox()
Creates a HBox layout with a spacing of 0.
HBox(double spacing)
Creates a HBox layout with a spacing of spacing.
HBox(Node... objects)
Creates a HBox layout with a spacing of 0 and adds all the objects to the pane.
HBox(double spacing, Node... objects)
Creates a HBox layout with a spacing of spacing and adds all the objects to the pane.
public final void setSpacing(double value)
Updates the HBox layout with a spacing of value.
public ObservableList getChildren()
Returns the ObservableList for the HBox. This is necessary to gain access to the add() method to add objects after the fact. [Inherited from javafx.scene.layout.Pane]

Table 6a: A few components of the HBox class.

The VBox Layout

VBoxDemo.java
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.VBox;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;

public class VBoxDemo extends Application {

    @Override
    public void start(Stage primaryStage) {
        // create VBox and add new objects
        VBox vb = new VBox(10, new Label("First Name:"), new TextField());
        Scene scene = new Scene(vb, 300, 150);

        // add additional objects to VBox
        vb.getChildren().addAll(new Label("Last Name:"), new TextField());

        // and go!
        primaryStage.setScene(scene);
        primaryStage.show();
    }
}

Example 9b: A simple VBox form.

VBox layout
Figure 7b:

The VBox layout.
VBox()
Creates a VBox layout with a spacing of 0.
VBox(double spacing)
Creates a VBox layout with a spacing of spacing.
VBox(Node... objects)
Creates a VBox layout with a spacing of 0 and adds all the objects to the pane.
VBox(double spacing, Node... objects)
Creates a VBox layout with a spacing of spacing and adds all the objects to the pane.
public final void setSpacing(double value)
Updates the VBox layout with a spacing of value.
public ObservableList getChildren()
Returns the ObservableList for the VBox. This is necessary to gain access to the add() method to add objects after the fact. [Inherited from javafx.scene.layout.Pane]

Table 6b: A few components of the VBox class.

The GridPane Layout

GridPaneDemo.java
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.geometry.Pos;

public class GridPaneDemo extends Application {

    @Override
    public void start(Stage primaryStage) {
        // all the things we need.
        Label l1, l2, l3;
        TextField tf1, tf2, tf3;
        GridPane pane = new GridPane();
        Scene scene = new Scene(pane, 300, 200);

        // create the labels and text fields
        l1 = new Label("Label 1");
        l2 = new Label("Label 2");
        l3 = new Label("Label 3");
        tf1 = new TextField("Text Field 1");
        tf2 = new TextField("Text Field 2");
        tf3 = new TextField("Text Field 3");

        // set pane object gaps and alignment
        pane.setHgap(10);
        pane.setVgap(10);
        pane.setAlignment(Pos.CENTER);

        // remember that column is first then row!
        pane.add(l1, 0, 0);
        pane.add(tf1, 1, 0);
        pane.add(l2, 0, 1);
        pane.add(tf2, 1, 1);
        pane.add(l3, 0, 2);
        pane.add(tf3, 1, 2);

        // and go!
        primaryStage.setTitle("GridPane");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
}

Example 9c: A simple GridPane form.

gridpane layout
Figure 7c:

The GridPane layout.
GridPane()
Creates a GridPane layout with a hgap and vgap of 0 and TOP_LEFT alignment.
public void add(Node child, int column, int row)
Add child to at row and column position.
public void add(Node child, int column, int row, int colspan, rowspan)
Add child to at row and column position spanning colspan columns and rowspan rows.

Table 6c: A few components of the GridPane class.


Events

Events are something that can happen at any given time. In a GUI application, we cannot expect the user to follow a given procedure like a traditionally written, text-based program. The whole idea of a GUI is to allow the user the opportunity to fill out portions of the form as they see fit. Depending on the application, they can submit some or all of the possible field information.

That being said, we need to approach how we handle the GUI a bit differently. Many events can occur, from filling in fields to mousing over an area to clicking a button to submit a form. All of these or even none of these can have actions associated with the event. Our job is to determine how our application should react to these events.

Three elementary examples will be provided to demonstrate how we can react to a button press. Since a button press does not involve the keyboard or the bevy of mouse-specific events, we will use the generic ActionEvent class to represent the event. In addition, we will use the EventHandler class to be able to assign an object to the Button.

A Button object can have the setOnAction() method invoked. When this is done, a reference to an object that implements EventHandleris passed. Specifically, the Button wants EventHandler. This then becomes the mechanism for handling the button press.

The use of the EventHandler interface (or any interface for that matter) means we agree to a specific contract as spelled out by the interface. In other words, we typically agree to supply a set of methods that can be accessed at runtime. The interface spells out the details – just like a contract spells out how two entities shall conduct business. The compiler enforces the rules of the agreement before it can become an issue at runtime. So the compiler essentially makes sure that all parties are keeping up their end of the deal.

For our sake, the only thing we need to do is provide a void method called handle() which handles the work to be done when the event occurs. Examples 10a and 10b show two different ways to implement this usign a class. Example 10c uses a Lambda expression.

We will be doing some additional formatting of controls within the pane so we will also introduce the HPos enumeration. Table 7a shows the constants available in HPos. This is similar to the Pos enumeration we used during our discussion of UI controls. HPos can only have an affect on horizontal positioning.

Constants Defining Poistion Using HPos.
HPos.CENTER
Positions horizontally center.
HPos.LEFT
Positions horizontally left.
HPos.RIGHT
Positions horizontally right.

Table 7a: The constants defined by the enumeration HPos.

Technical Note
Ok, so what do we mean by named-class, anonymous class, and lambda method? These are terms you’re about to see and mostly they have to do with how you will write your code, but there’s also an impact on file generation.

The example ButtonCopyWithClass.java will produce an additional file called ButtonCopyWithClass$ButtonHandler.class.

The example ButtonCopyWithAnonClass.java will produce an additional file called ButtonCopyWithClass$1.class.

Finally, the example ButtonCopyWithLambda.java will produce no additional file.

So, in addition to the structural code changes, you will also produce different files depending on your selection.

ButtonCopyWithClass.java
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.text.TextAlignment;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.control.Button;
import javafx.scene.layout.GridPane;
import javafx.event.EventHandler;
import javafx.event.ActionEvent;
import javafx.geometry.Pos;
import javafx.geometry.HPos;

public class ButtonCopyWithClass extends Application {

    TextField tfsrc, tfdst;

    @Override
    public void start(Stage primaryStage) {
        Label lsrc, ldst;
        Button btn;
        GridPane pane = new GridPane();
        Scene scene = new Scene(pane, 500, 200);

        lsrc = new Label("Source:");
        ldst = new Label("Destination:");
        btn = new Button("Perform the COPY");
        tfsrc = new TextField();
        tfsrc.setMinWidth(200);
        tfdst = new TextField();
        tfdst.setMinWidth(200);

        pane.setHgap(10);
        pane.setVgap(10);
        pane.setAlignment(Pos.CENTER);
        GridPane.setHalignment(btn, HPos.LEFT);
        GridPane.setHalignment(lsrc, HPos.CENTER);
        GridPane.setHalignment(ldst, HPos.CENTER);

        pane.add(lsrc, 0, 0);
        pane.add(tfsrc, 1, 0);
        pane.add(ldst, 0, 1);
        pane.add(tfdst, 1, 1);
        pane.add(btn, 1, 2);
        tfdst.setEditable(false);

        // this is where we assign the handler
        btn.setOnAction(new ButtonHandler());

        primaryStage.setTitle("Button Event Class");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    /**
     * Named class that implements the EventHandler
     */
    private class ButtonHandler implements EventHandler<ActionEvent> {
        @Override
        public void handle(ActionEvent event) {
            String s;
            // all we do is copy the text from src to dst.
            s = tfsrc.getText();
            tfdst.setText(s);
        }
    }
}

Example 10a: Using a class to support the EventHandler interface.

The first version of this uses a complete class called ButtonHandler. This model would be useful if you were developing a generic button handler for all buttons. You could then determine which button was pressed and react accordingly.

Note additional imports that are needed. Also note that the TextField reference variables are global within the class. This because the inner class, ButtonHandler, needs access to these objects later. This is considered an inner class because it is a class defined inside another class.

The following example uses an anonymous inner class.

ButtonCopyWithAnonClass.java
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.text.TextAlignment;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.control.Button;
import javafx.scene.layout.GridPane;
import javafx.event.EventHandler;
import javafx.event.ActionEvent;
import javafx.geometry.Pos;
import javafx.geometry.HPos;

public class ButtonCopyWithAnonClass extends Application {

    @Override
    public void start(Stage primaryStage) {
        Label lsrc, ldst;
        TextField tfsrc, tfdst;
        Button btn;
        GridPane pane = new GridPane();
        Scene scene = new Scene(pane, 500, 200);

        lsrc = new Label("Source:");
        ldst = new Label("Destination:");
        btn = new Button("Perform the COPY");
        tfsrc = new TextField();
        tfsrc.setMinWidth(200);
        tfdst = new TextField();
        tfdst.setMinWidth(200);

        pane.setHgap(10);
        pane.setVgap(10);
        pane.setAlignment(Pos.CENTER);
        GridPane.setHalignment(btn, HPos.LEFT);
        GridPane.setHalignment(lsrc, HPos.CENTER);
        GridPane.setHalignment(ldst, HPos.CENTER);

        pane.add(lsrc, 0, 0);
        pane.add(tfsrc, 1, 0);
        pane.add(ldst, 0, 1);
        pane.add(tfdst, 1, 1);
        pane.add(btn, 1, 2);
        tfdst.setEditable(false);

        // anonymous class with handle() method defined
        btn.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                String s;
                // all we do is copy the text from src to dst.
                s = tfsrc.getText();
                tfdst.setText(s);
            }
        });

        primaryStage.setTitle("Button Event Inner Class");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
}

Example 10b: Using an inner class to support the EventHandler interface.

The anonymous inner class is only beneficial when you know this class is a one-shot deal, will not be used again, and therefore needs no formal name like ButtonHandler from the previous example.

The upside is that the text fields no longer need to be global to the class. The downside to the anonymous inner class is that it is complicated to write and makes it look like the EventHandler is an actual class with a default constructor rather than an interface. This can make understanding and debugging the program somewhat tricky.

The following example uses a Lambda expression.

ButtonCopyWithLambda.java
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.text.TextAlignment;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.control.Button;
import javafx.scene.layout.GridPane;
import javafx.geometry.Pos;
import javafx.geometry.HPos;

public class ButtonCopyWithLambda extends Application {

    @Override
    public void start(Stage primaryStage) {
        Label lsrc, ldst;
        TextField tfsrc, tfdst;
        Button btn;
        GridPane pane = new GridPane();
        Scene scene = new Scene(pane, 500, 200);

        lsrc = new Label("Source:");
        ldst = new Label("Destination:");
        btn = new Button("Perform the COPY");
        tfsrc = new TextField();
        tfsrc.setMinWidth(200);
        tfdst = new TextField();
        tfdst.setMinWidth(200);

        pane.setHgap(10);
        pane.setVgap(10);
        pane.setAlignment(Pos.CENTER);
        GridPane.setHalignment(btn, HPos.LEFT);
        GridPane.setHalignment(lsrc, HPos.CENTER);
        GridPane.setHalignment(ldst, HPos.CENTER);

        pane.add(lsrc, 0, 0);
        pane.add(tfsrc, 1, 0);
        pane.add(ldst, 0, 1);
        pane.add(tfdst, 1, 1);
        pane.add(btn, 1, 2);
        tfdst.setEditable(false);

        // lambda expressoion where only the action is defined
        btn.setOnAction(event -> {
            String s;
            // all we do is copy the text from src to dst.
            s = tfsrc.getText();
            tfdst.setText(s);
        });

        primaryStage.setTitle("Button Event Lambda");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
}

Example 10c: Using a Lambda expression to support the EventHandler interface.

Since we have not yet spoken of Lambda expressions, we will first note that Lambda expressions are unique, were introduced in Java 8, and cannot be used everywhere. Only in particular instances can this be done.

The reason we can use it here is due to very specific knowledge regarding Button. Button expects that the setOnAction() method will have been passed an object that implements EventHandler. And, as previously mentioned, there is only one method implemented – handle().

In Java, Lambda expressions terms, EventHanlder is a functional interface. A functional interface is an interface that has only one abstract method. In this case it is handle(). As it is the only method defined, we do not even need to use its name as it is understood that we are overriding the only method available.

The name event is a parameter representing the ActionEvent being passed to the method. The compiler handles all the details to the point that in this example we are no longer importing EventHandler and ActionEvent!

So which should you use? The anonymous inner class (second version) will likely be the least frequently used. The named inner class (first version) is pretty traditional as it has consistent with how things are done in Swing, making it quite common. The Lambda expression model has limited use because of the specific criteria surrounding when it can be used. It is a pretty powerful tool and should be sought whenever possible.


Graphics

This section provides a rudimentary exposition of the JavaFX Graphics library. It is provided as part of a deeper understanding of graphical environments and ties into the CodeBreaker game provided in the interludes (q.v).

We have been talking about labels and buttons and text fields and all sorts of things you would see in a typical GUI form. Now we want to draw shapes and lines, which will require something different. We will begin by talking about Shape. The Shape class is just a base class from which all other shapes are derived.

We will not be instantiating Shape directly, but Table 6 lists some of the methods that we will employ.

Shape class methods
Paint getFill()
Method to return the current fill property.
Paint getStroke()
Method to return the current stroke property.
void setFill(Paint value)
Method to set the fill to value.
void setStroke(Paint value)
Method to set the stroke to value.

Table 6: Some methods of the Shape class.

As we said, the Shape class is the basis of many available shapes. These include Line, Circle, Rectangle and Polygon. All of which we will use in the next few examples. We will start with the basics.

Line constructors
Line()
Creates an Line object with no coordinate properties.
Line(double startX, double startY, double endX, double endY)
Creates a line from (startX,startX) to (endX,endY).

Table 7: Constructors of the Line class.

Creating a line is pretty simple. We can use the second constructor form to specify the start and end coordinate points. After that we can add it to the Group.

FXGraphicsEx1.java
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.shape.Line;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.scene.Group;
import javafx.scene.paint.Color;

public class FXGraphicsEx1 extends Application {

    @Override
    public void start(Stage primaryStage) {
        Group group;
        Scene scene;
        Line line;
        Rectangle rect;
        Circle cir;

        group = new Group();
        scene = new Scene(group, 400, 400, Color.LIGHTGRAY);

        line = new Line(10, 20, 100, 40);
        group.getChildren().add(line);

        line = new Line(390, 20, 290, 40);
        group.getChildren().add(line);

        rect = new Rectangle(100, 100, 50, 50);
        rect.setStroke(Color.RED);
        rect.setFill(Color.TRANSPARENT);
        group.getChildren().add(rect);

        cir = new Circle(275, 125, 25,Color.TRANSPARENT);
        cir.setStroke(Color.RED);
        group.getChildren().add(cir);

        cir = new Circle(125, 185, 25,Color.TRANSPARENT);
        cir.setStroke(Color.YELLOW);
        group.getChildren().add(cir);

        rect = new Rectangle(250, 160, 50, 50);
        rect.setStroke(Color.YELLOW);
        rect.setFill(Color.TRANSPARENT);
        rect.setArcWidth(10);
        rect.setArcHeight(10);
        group.getChildren().add(rect);

        rect = new Rectangle(100, 220, 50, 50);
        group.getChildren().add(rect);

        cir = new Circle(275, 245, 25);
        group.getChildren().add(cir);

        cir = new Circle(125, 305, 25, Color.BLUE);
        group.getChildren().add(cir);

        rect = new Rectangle(250, 280, 50, 50);
        rect.setFill(Color.BLUE);
        rect.setArcWidth(10);
        rect.setArcHeight(10);
        group.getChildren().add(rect);

        primaryStage.setTitle("FXGraphicsEx1");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
}

Example 11a: A program utilizing Graphics to draw basic shapes.

The code shown in Example 11a is designed to jump right in and start drawing. Remember coordinate geometry? The basic coordinate system is based on the origin (0,0) at the upper left-hand corner of the content pane. So, if you remember coordinate geometry, we are operating in Quadrant IV except all the x and y values are positive and move away from the origin.

The code in Example 11a produces the following graphic.

Example shapes

The next few tables describe some common Graphics methods and the color constants.

Drawing methods from the Graphics abstract class.
abstract void drawLine(int x1, int y1, int x2, int y2)
Draws a line in the current color from (x1,y1) to (x2,y2).
abstract void drawOval(int x, int y, int width, int height)
Draws an oval in the current color of width and height using (x,y) as the top left origin.
abstract void drawPolygon(int[] xPoints, int[] yPoints, int nPoints)
Draws a closed polygon in the current color using the count of nPoints specified in the xPoints and yPoints arrays.
void drawRect(int x, int y, int width, int height)
Draws a hollow rectangle of width and height in the current color using (x,y) as the top left origin.
abstract void drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight)
Draws a hollow rectangle of width and height in the current color using (x,y) as the top left origin. The arcWidth and arcHeight values are used to determine the rouding of the corners.
abstract void drawString(String str, int x, int y)
Draws text in str using the current color and font weight starting at (x,y).

Table 7: Some of the draw methods of the Graphics abstract class.

The following table defines some methods used for drawing filled graphic objects.

Fill methods from the Graphics abstract class.
abstract void fillArc(int x, int y, int width, int height, int startAngle, int arcAngle)
Fills an elliptical or circular arc using the current color and (x,y) as the top left origin.
abstract void fillOval(int x, int y, int width, int height)
Fills an oval in the current color of width and height using (x,y) as the top left origin.
abstract void fillPolygon(int[] xPoints, int[] yPoints, int nPoints)
Fills a closed polygon in the current color using the count of nPoints specified in the xPoints and yPoints arrays.
abstract void fillRect(int x, int y, int width, int height)
Fills a rectangle of width and height in the current color using (x,y) as the top left origin.
abstract void fillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight)
Fills a rectangle of width and height in the current color using (x,y) as the top left origin. The arcWidth and arcHeight values are used to determine the rouding of the corners.

Table 8: Some of the fill methods of the Graphics abstract class.

Color is a class defined in java.awt.Color. Some of the common colors are noted below.

Constructor and a few constants from Color.
Color(int r, int g, int b)
Constructor for a custom color specifying the levels of Red, Gree and Blue.
Color.BLACK and Color.black
Color.RED and Color.red
Color.LIGHT_GRAY and Color.lightGray
Color.YELLOW and Color.yellow

Table 9: Some of the constants defined by the Color class.

We used a drawing and a grid system to render some art for the following example. You can click on the image below to see the full-size picture.

AsteroidsGrid

The drawing is a few freehand shapes from an old game called Asteroids. You would fly around space to blow up the asteroids before they crushed your ship. As an omage to a once great game, we now have a piece of Java code that captures these images. Using the coordinate system on the drawing, the drawLine() method was used to create the ship while the drawPolygon() method was used to create the two asteroids.

FXGraphicsEx2.java
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.shape.Line;
import javafx.scene.shape.Polygon;
import javafx.scene.Group;
import javafx.scene.paint.Color;

public class FXGraphicsEx2 extends Application {

    @Override
    public void start(Stage primaryStage) {
        Group group;
        Scene scene;
        Line line;
        Polygon poly;

        group = new Group();
        scene = new Scene(group, 400, 300, Color.BLACK);

        // main ship body
        line = new Line(150,100,180,110);
        line.setStroke(Color.WHITE);
        group.getChildren().add(line);

        line = new Line(150,120,180,110);
        line.setStroke(Color.WHITE);
        group.getChildren().add(line);

        line = new Line(158,103,158,117);
        line.setStroke(Color.WHITE);
        group.getChildren().add(line);

        // jet boost
        line = new Line(153,107,145,110);
        line.setStroke(Color.WHITE);
        group.getChildren().add(line);

        line = new Line(153,113,145,110);
        line.setStroke(Color.WHITE);
        group.getChildren().add(line);

        // asteroid 1
        double[] poly1 = {280,90,280,130,300,130,290,150,320,160,350,160,350,140,370,120,360,100,360,90,330,70,300,70};
        poly = new Polygon(poly1);
        poly.setStroke(Color.WHITE);
        group.getChildren().add(poly);

        // asterpoid 2
        double[] poly2 = {90,160,90,173,100,186,120,177,123,180,130,176,120,167,130,163,132,156,120,150,103,150,110,160};
        poly = new Polygon(poly2);
        poly.setStroke(Color.WHITE);
        group.getChildren().add(poly);

        primaryStage.setTitle("FXGraphicsEx2");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
}

Example 11b: A program utilizing Graphics to draw a familiar video game.

The code in Example 11b creates the picture shown below.

Asteroids picture


Exercises

Rearrange the methods of Example 1 to prove the order of declaration has no impact on execution.

Post navigation

❮ Previous Post: Chapter 7a – Graphical User Interface – Swing (OLD)
Next Post: Chapter 8 – User Defined Classes and Abstract Data Types ❯

Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.

Copyright © 2018 – 2025 Programming by Design.