Java Tutorials Made Easy banner

  

JavaFX can be used to write applications with Graphical User Interfaces (GUIs). GUIs are easier to use than command-line interfaces.

 

Section 1 - A Simple JavaFX program

 

All JavaFX applications extend the Application class in the javafx.application package. The application must override the start method which accepts a stage as its argument (this is the primary stage, a JavaFX program may have several stages). 

 

When running a JavaFX program, the JVM creates a primary stage, then invokes the application’s start method with the stage as a parameter. Older IDEs don’t support this implicitly, and need to provide a main method that calls launch with the command-line arguments.

 

JavaFX uses a movie cinema analogy, where graphical elements are players in a scene, and scenes are layed out on stages. The main graphical layout components are called panes. The Hello.java program in Listing 1 creates a single Pane object that is added to the scene. The scene object also specifies a width of 200 pixels and a height of 50 pixels. The scene is added to the primary stage. The primaryStage object sets the title of the window to “Hello”, and displays the window by invoking its show method.

 

Listing 1 - Hello.java (version 1)

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

public class Hello extends Application 
{
    @Override
    public void start(Stage primaryStage) 
    {
        Pane pane = new Pane();

        Scene scene = new Scene(pane, 200, 50);

        primaryStage.setTitle("Hello"); 
        primaryStage.setScene(scene); 

        primaryStage.show(); 
    }

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

The window displays as follows:                             

 

Section 2 - Nodes

 

Nodes are graphical elements that are displayed in panes. This section introduces the text node which displays text. Later sections will demonstrate buttons, checkboxes, radio buttons, labels, and text fields, text areas, and shapes. 

 

The following statement creates a text node containing the text “Hello” aligned 80 pixels from the left border of its pane and 25 pixels from the top of its pane.

    Text myText = new Text(80, 25, "Hello"); 

Panes maintain a list of child nodes displayed within them. We can add this text node to the pane of the Hello.java program by adding it to its child nodes as follows:

    pane.getChildren().add(myText);

The new version of Hello.java is shown in Listing 2. Observe the text “Hello” displayed in the center of the pane object.

 

Listing 2 - Hello.java (version 2)

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

public class Hello extends Application 
{
    @Override
    public void start(Stage primaryStage) 
    {
        Text myText = new Text(80, 25, "Hello"); 

        Pane pane = new Pane();
        pane.getChildren().add(myText);

        Scene scene = new Scene(pane, 200, 50);

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

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

The window displays as follows:

 

Section 3 - The StackPane

 

The pane in Listing 2 is an object of the Pane class. It is a simple pane used for displaying a single node. If multiple nodes need to be placed in the pane one Pane’s subclasses, which are able to lay out multiple nodes, must be used. One such subclass is the StackPane class. A stack pane lays out its child nodes one on top of each other in the center of the pane. If the nodes are transparent, they will all be visible. If they are opaque, only the last node added to the pane will be visible. 

 

We can place a rectangle and a text one on top of another in the stack pane. Since the nodes are centered, x and y coordinates are not provided in the Text object constructor. The Rectangle object is created with a width of 100 pixels and a height of 30 pixels.

        Text myText = new Text("Hello"); 

        Rectangle rectangle = new Rectangle(100,30);

In order for the nodes to be transparent, the setFill method must be called with a transparent color. The edge of the rectangle is set to black with the setStroke method.

        rectangle.setFill(Color.TRANSPARENT);

        rectangle.setStroke(Color.BLACK);

The new version of Hello.java is shown in Listing 3. The addAll method adds multiple nodes to the stack pane’s child node list.

 

Listing 3 - Hello.java (version 3)

import javafx.application.*;
import javafx.scene.*;
import javafx.scene.layout.*;
import javafx.scene.text.*;
import javafx.scene.shape.*;
import javafx.scene.paint.*;
import javafx.stage.*;
 
public class Hello extends Application 
{
    @Override
    public void start(Stage primaryStage) 
    {
        Text myText = new Text("Hello"); 

        Rectangle rectangle = new Rectangle(100,30);

        rectangle.setFill(Color.TRANSPARENT);
        rectangle.setStroke(Color.BLACK);

        StackPane pane = new StackPane();
        pane.getChildren().addAll(myText, rectangle);

        Scene scene = new Scene(pane, 200, 50);

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

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

The window displays as follows:

 

Section 4 - The GridPane

 

GridPane is a subclass of Pane that places its child nodes in rows and columns. If more than one node is placed in a single cell, they are displayed one on top of another. 

 

Suppose we want to draw a tic tac toe board. We can use a grid pane with three rows and three columns containing X’s, O’s, and blank space. Calling the setGridLinesVisible method with argument true displays the lines between the cells. Calling the setAlignment method with Pos.CENTER aligns the cells at the center of the pane. The Insets object creates margins of top = 15 pixels, right = 0 pixels, bottom = 15 pixels, left = 0 pixels.

    GridPane pane = new GridPane();

    pane.setGridLinesVisible(true);
    pane.setAlignment(Pos.CENTER);
    pane.setPadding(new Insets(15, 0, 15, 0));

For the X’s, O’s, and blank space, we can use Text nodes, but we will increase the font size to 50.

    private Text getText(String s)
    {
        Text rowColumn = new Text(s);

        rowColumn.setFont(new Font(50));

        return rowColumn;
    }

The GridPane class provides an add method that places a node at the specified column and row. For example, the following statement places a Text node containing a blank space at column one and row 0:

    pane.add(getText(" "), 1 ,0);

The program in Listing 4 displays the tic tac toe board.

 

Listing 4 - TicTacToe.java

import javafx.application.*;
import javafx.geometry.*;
import javafx.scene.*;
import javafx.scene.layout.*;
import javafx.scene.text.*;
import javafx.stage.*;
public class TicTacToe extends Application 
{
    private Text getText(String s)
    {
        Text rowColumn = new Text(s);

        rowColumn.setFont(new Font(50));

        return rowColumn;
    }

    @Override
    public void start(Stage primaryStage) 
    {
        GridPane pane = new GridPane();

        pane.setGridLinesVisible(true);
        pane.setAlignment(Pos.CENTER);
        pane.setPadding(new Insets(15, 0, 15, 0));

        pane.add(getText("X"), 0 ,0);
        pane.add(getText(" "), 1 ,0);
        pane.add(getText(" "), 2 ,0);
        pane.add(getText(" "), 0 ,1);
        pane.add(getText("X"), 1 ,1);
        pane.add(getText(" "), 2 ,1);
        pane.add(getText(" "), 0 ,2);
        pane.add(getText(" "), 1 ,2);
        pane.add(getText("X"), 2 ,2);

        Scene scene = new Scene(pane, 230, 230);

        primaryStage.setTitle("TicTacToe"); 
        primaryStage.setScene(scene); 

        primaryStage.show(); 
    }

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

The window displays as follows:


 

Section 5 - The FlowPane

 

FlowPane is a subclass of Pane that flows its child nodes from left to right in rows that fill up the pane. When one row is full, the subsequent child nodes are placed in the next row, and so on until all the nodes are displayed. Nodes are displayed in the order in which they added to the pane.

 

Suppose you want to write a program that allows a user to enter his or her address. For this program we will introduce label and text field nodes. The Label class displays text. The following statements create labels for the user’s city, state, and zip code :

    Label cityLabel    = new Label("City:"); 

    Label stateLabel   = new Label("State:"); 

    Label zipCodeLabel = new Label("ZipCode:"); 

The TextField class allows the user to enter a single line of text. The following statements create text fields for the user’s city, state, and zip code. The text fields are sized to allow the user to enter 10 characters in each field.

    TextField city    = new TextField();
    TextField state   = new TextField();
    TextField zipCode = new TextField();

    city.setPrefColumnCount(10);
    state.setPrefColumnCount(10);
    zipCode.setPrefColumnCount(10);

Labels and text fields are frequently used together, where the labels tell the user what to enter in the text fields.

 

A flow pane is then created with suitable margins. The setHgap method is called to put a space of 10 pixels between each node horizontally. The setVgap method is called to put a space of 5 pixels between each row vertically. 

        FlowPane pane = new FlowPane();

        pane.setPadding(new Insets(10, 20, 10, 20));
        pane.setHgap(10);
        pane.setVgap(5);

The Address.java program in Listing 5 contains the complete application. Each label and its corresponding text field is added to the flow pane in pairs.

 

Listing 5 - Address.java (version 1)

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

public class Address extends Application 
{
    @Override
    public void start(Stage primaryStage) 
    {
        Label cityLabel    = new Label("City:"); 
        Label stateLabel   = new Label("State:"); 
        Label zipCodeLabel = new Label("ZipCode:"); 

        TextField city    = new TextField();
        TextField state   = new TextField();
        TextField zipCode = new TextField();

        city.setPrefColumnCount(10);
        state.setPrefColumnCount(10);
        zipCode.setPrefColumnCount(10);

        FlowPane pane = new FlowPane();
        pane.setPadding(new Insets(10, 20, 10, 20));

        pane.setHgap(10);
        pane.setVgap(5);

        pane.getChildren().addAll(cityLabel, city, stateLabel, state,
                                  zipCodeLabel, zipCode);

        Scene scene = new Scene(pane, 230, 100);

        primaryStage.setTitle("Address"); 
        primaryStage.setScene(scene); 

        primaryStage.show(); 
    }

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

The window displays as follows:

 

 

Section 6 - The BorderPane

 

BorderPane is a subclass of Pane that places its child nodes into five regions: top, bottom, left, center, and right.

 

Suppose you want to write a program that draws ellipses.  For this program we will introduce the ellipse and button nodes. The ellipse occupies the center region of the border pane, while the buttons occupy the top, bottom, left, and right regions.

 

The Ellipse class allows you to specify the X and Y coordinates of the ellipse’s center, and the X and Y radii of the shape. Initially, the ellipse is a circle with a radius of 10 pixels, and the x and y coordinates are chosen to place the circle in the center of the window. The ellipse is placed into a Pane object, in order to not change its layout.

    Ellipse ellipse  = new Ellipse(75,106, 10, 10);

    Pane ellipsePane = new Pane();

    ellipsePane.getChildren().add(ellipse);

The Button class is used to define an action which occurs when the button is pressed. Buttons provide a descriptive text which tells the user what will happen when the button is pressed. In this program, we will provide buttons that will make the ellipse shorter, taller, more narrow, and wider, respectively.

    Button up    = new Button("Short");
    Button down  = new Button("Tall");
    Button left  = new Button("Narrow");
    Button right = new Button("Wide");

The buttons are placed in a stack pane in order to center then in their region. A margin of 10 pixels is specified for each button.

    StackPane createButtonPane(Button button)
    {
        StackPane pane = new StackPane();

        pane.getChildren().add(button);

        pane.setPadding(new Insets(10, 10, 10, 10));

        return pane;
    }

The complete program is shown in Listing 6. We will hook up the button in section 9 when we discuss event handlers. For now, we are only concerned with the program’s layout.

 

Listing 6 - EllipseShape.java (version 1)

import javafx.geometry.*;
import javafx.application.*;
import javafx.scene.*;
import javafx.scene.layout.*;
import javafx.scene.shape.*;
import javafx.scene.control.*;
import javafx.stage.*;
 
public class EllipseShape extends Application 
{
    StackPane createButtonPane(Button button)
    {
        StackPane pane = new StackPane();

        pane.getChildren().add(button);

        pane.setPadding(new Insets(10, 10, 10, 10));

        return pane;
    }

    @Override
    public void start(Stage primaryStage) 
    {
        Ellipse ellipse  = new Ellipse(75,106, 10, 10);

        Pane ellipsePane = new Pane();
        ellipsePane.getChildren().add(ellipse);
        
        Button up    = new Button("Short");
        Button down  = new Button("Tall");
        Button left  = new Button("Narrow");
        Button right = new Button("Wide");
 
        BorderPane pane = new BorderPane();        

        pane.setTop(createButtonPane(up));
        pane.setLeft(createButtonPane(left));
        pane.setRight(createButtonPane(right));
        pane.setBottom(createButtonPane(down));
        pane.setCenter(ellipsePane);
        
        Scene scene = new Scene(pane, 300, 300);

        primaryStage.setTitle("Ellipse Shape"); 
        primaryStage.setScene(scene); 
        primaryStage.show(); 
    }

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

The window displays as follows:

 

Section 7 - HBoxes and VBoxes

 

An HBox is a pane that places its child nodes in a single row. A VBox is a plane that places its child nodes in a single column.

 

Suppose you want to write a program that changes a circle’s attributes. The attributes we will change are the X and Y coordinates of the circle’s center, its radius, and its color. For this program we will introduce the circle and radio button nodes.

 

The Circle class allows you to specify the X and Y coordinates of the circle’s center and the circle’s radius. Initially, the circle is black (the default color) with a radius of 10 pixels, and the x and y coordinates are chosen to place the circle in the center of the border pane’s center region. The circle is placed into a Pane object, in order to not change its layout.

    Circle circle  = new Circle(75, 55, 10);

    Pane circlePane = new Pane();

    circlePane.getChildren().add(circle);

 Text fields are provided to allow the user to enter values for the circle’s X and Y coordinates, and the circle’s radius.

    TextField centerX = new TextField("75");
    TextField centerY = new TextField("55");
    TextField radius  = new TextField("10");

    centerX.setPrefColumnCount(5);
    centerY.setPrefColumnCount(5);
    radius.setPrefColumnCount(5);

The text fields are placed in an HBox, so that they will occupy a single row with a space of 5 pixels between each node. The HBox occupies the top region of the border pane.

     HBox text = new HBox(5); 

     text.getChildren().addAll(centerX, centerY, radius);

     text.setPadding(new Insets(10, 10, 0, 10));

The RadioButton class allows you to specify a button whose action is usually a member of a group of mutually exclusive actions. In other words, only one of the radio buttons in a group can be selected at a time. In this program, we will allow the user to color the circle red, green, or blue:

    RadioButton red   = new RadioButton("red");
    RadioButton green = new RadioButton("green");
    RadioButton blue  = new RadioButton("blue");

It is often helpful to provide a heading for radio buttons. The static setMargin method of the VBox class allows you  to provide insets for each node individually. Since the heading has not left margin, and the radio buttons have a left margin of 15 pixels, the buttons are effectively indented from the heading.

    Text heading = new Text("Select a color:");

    VBox.setMargin(red,   new Insets(0, 0, 0, 15));
    VBox.setMargin(green, new Insets(0, 0, 0, 15));
    VBox.setMargin(blue,  new Insets(0, 0, 0, 15));

The heading and radio buttons are placed in a VBox, so that they will occupy a single column with a space of 10 pixels between each node. The VBox occupies the left region of the border pane.

    VBox colors = new VBox(10);

    colors.getChildren().addAll(heading, red, green, blue);

    colors.setPadding(new Insets(10, 0, 10, 10));

The complete program is shown in Listing 7. We will hook up the text fields and buttons in section 9 when we discuss event handlers. For now, we are only concerned with the program’s layout.

 

Listing 7 - CircleAttributes.java (version 1)

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

public class CircleAttributes extends Application 
{
    @Override
    public void start(Stage primaryStage) 
    {
        Circle circle  = new Circle(75, 55, 10);

        Pane circlePane = new Pane();

        circlePane.getChildren().add(circle);

        TextField centerX = new TextField("75");
        TextField centerY = new TextField("55");
        TextField radius  = new TextField("10");

        centerX.setPrefColumnCount(5);
        centerY.setPrefColumnCount(5);
        radius.setPrefColumnCount(5);

        HBox text = new HBox(5); 

        text.getChildren().addAll(centerX, centerY, radius);
        text.setPadding(new Insets(10, 10, 0, 10));

        VBox colors = new VBox(10);
        Text heading = new Text("Select a color:");

        RadioButton red   = new RadioButton("red");
        RadioButton green = new RadioButton("green");
        RadioButton blue  = new RadioButton("blue");
        
        VBox.setMargin(red,   new Insets(0, 0, 0, 15));
        VBox.setMargin(green, new Insets(0, 0, 0, 15));
        VBox.setMargin(blue,  new Insets(0, 0, 0, 15));

        colors.getChildren().addAll(heading, red, green, blue);

        colors.setPadding(new Insets(10, 0, 10, 10));

        BorderPane pane = new BorderPane();        

        pane.setTop(text);
        pane.setLeft(colors);
        pane.setCenter(circlePane);

        Scene scene = new Scene(pane);

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

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

The window displays as follows:

 

 

 

Section 8 - Checkboxes and Text Areas

 

The Checkbox class allows you to specify a button whose action is usually a member of a group of actions that are not mutually exclusive. In other words, more than one of the checkboxes in a group may be selected at a time.

 

The TextArea class allows the user to enter multiple lines of text.a single line of text. 

 

Suppose you want to change the address program from Section 5 to allow the user to change how his or her address is displayed. The address will be displayed in a text area. The user will be allowed to non-exclusively select if the city state and zip are displayed as a single line, if the user’s phone number is included, and if the user’s email is included. Since the content of these nodes need to be accessible after their creation, they must be declared as class fields.

    CheckBox singleLine, displayPN, displayEmail;

    TextArea area;

The text area is added to a stack pane that occupies the center region of the border pane:

    Pane createTextArea()
    {
        area = new TextArea();

        area.setPrefColumnCount(5);
        area.setPrefRowCount(5);

        StackPane areaPane = new StackPane();

        areaPane.getChildren().add(area);
        areaPane.setPadding(new Insets(0, 20, 0, 10));

        return areaPane;
    }

The checkboxes are added to a VBox that is displayed in the left region of the border pane:

    Pane createCheckboxes()
    {

        singleLine   = new CheckBox("Single Line");
        displayPN    = new CheckBox("Display PN");
        displayEmail = new CheckBox("Display Email");

        VBox checkboxes = new VBox(5);

        checkboxes.getChildren().addAll(singleLine, displayPN,
                                        displayEmail);

        checkboxes.setPadding(new Insets(0, 0, 0, 10));

        return checkboxes;
    }

The complete program is shown in Listing 8. The labels and text fields of the address components are placed in a grid pane that occupies the top region of the border pane.

A display button is placed in a stack pane that occupies the bottom region of the border pane. We will hook up the text fields and buttons in section 9 when we discuss event handlers. For now, we are only concerned with the program’s layout.

 

Listing 8 - Address.java (version 2)

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

public class Address extends Application 
{
    TextField city, state, zipCode, phone, email;

    CheckBox singleLine, displayPN, displayEmail;

    TextArea area;
    
    Pane createInputPane()
    {
        Label cityLabel    = new Label("City:"); 
        Label stateLabel   = new Label("State:"); 
        Label zipCodeLabel = new Label("ZipCode:"); 
        Label phoneLabel   = new Label("Phone:"); 
        Label emailLabel   = new Label("Email:"); 

        city    = new TextField();
        state   = new TextField();
        zipCode = new TextField();
        phone   = new TextField();
        email   = new TextField();

        city.setPrefColumnCount(10);
        state.setPrefColumnCount(10);
        zipCode.setPrefColumnCount(10);
        phone.setPrefColumnCount(10);
        email.setPrefColumnCount(10);
        
        GridPane inputPane = new GridPane();

        inputPane.setPadding(new Insets(10, 0, 10, 10));
        inputPane.setHgap(10);
        inputPane.setVgap(5);

        inputPane.add(cityLabel   , 0, 0);
        inputPane.add(city        , 1, 0);
        inputPane.add(stateLabel  , 0, 1); 
        inputPane.add(state       , 1, 1);
        inputPane.add(zipCodeLabel, 0, 2); 
        inputPane.add(zipCode     , 1, 2);
        inputPane.add(phoneLabel  , 0, 3); 
        inputPane.add(phone       , 1, 3);
        inputPane.add(emailLabel  , 0, 4);  
        inputPane.add(email       , 1, 4);

        return inputPane;
    }
    
    Pane createCheckboxes()
    {
        singleLine   = new CheckBox("Single Line");
        displayPN    = new CheckBox("Display PN");
        displayEmail = new CheckBox("Display Email");

        VBox checkboxes = new VBox(5);

        checkboxes.getChildren().addAll(singleLine, displayPN,
                                        displayEmail);

        checkboxes.setPadding(new Insets(0, 0, 0, 10));

        return checkboxes;
    }

    Pane createDisplayButton()
    {
        Button button = new Button("Display");

        StackPane buttonPane = new StackPane();

        buttonPane.getChildren().add(button);
        buttonPane.setPadding(new Insets(10, 0, 10, 0));

        return buttonPane;
    }

    Pane createTextArea()
    {
        area = new TextArea();

        area.setPrefColumnCount(5);
        area.setPrefRowCount(5);

        StackPane areaPane = new StackPane();
        areaPane.getChildren().add(area);

        areaPane.setPadding(new Insets(0, 20, 0, 10));

        return areaPane;
    }

    @Override
    public void start(Stage primaryStage) 
    {
        BorderPane pane = new BorderPane();

        pane.setTop(createInputPane());
        pane.setLeft(createCheckboxes());
        pane.setBottom(createDisplayButton());
        
        area = new TextArea();

        area.setPrefColumnCount(5);
        area.setPrefRowCount(5);
        pane.setCenter(createTextArea());
 
        Scene scene = new Scene(pane,300,300);

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

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

The window displays as follows:

 

 

Section 9 - Event Handlers

 

Nodes that are defined in the javafx.scene.control package can provide the setOnAction method which specifies what happens when the button is pressed or the data is entered into the text field. 

 

The setOnAction method takes an EventHandler<ActionEvent> object as its argument. EventHandler is a functional interface that is generic for <T extends Event> with functional method handle that takes an Event object as its argument.

@FunctionalInterface
public interface EventHandler<T extends Event> 
{
     void handle(T event);
}

Event is a class that defines the properties of an event in Java. The ActionEvent class defines the properties of a button press.

 

Therefore, the statement:

    myNode.setOnAction(e -> statement_returning_void);

implements functional interface EventHandler<ActionEvent> by defining a handle method which operates on node myNode. Since handle is a void-returning method, the statement or statements in the lambda expression must also return void. Methods of the ActionEvent parameter are also accessible through lambda parameter e

 

We can now hook up the buttons defined in the previous programs. The up and down buttons of the EllipseShape program make the ellipse shorter and taller by subtracting and adding and 5 pixels to the current value of its Y radius, respectively:

    up.setOnAction   (e -> 
        ellipse.setRadiusY(ellipse.getRadiusY() - 5));

    down.setOnAction (e -> 
        ellipse.setRadiusY(ellipse.getRadiusY() + 5));

The left and right buttons of the EllipseShape program make the ellipse narrower and wider by subtracting and adding and 5 pixels to the current value of its X radius, respectively:

    left.setOnAction (e -> 
        ellipse.setRadiusX(ellipse.getRadiusX() - 5));

    right.setOnAction(e -> 
        ellipse.setRadiusX(ellipse.getRadiusX() + 5));

 

Listing 9 - EllipseShape.java (version 2)

import javafx.geometry.*;
import javafx.application.*;
import javafx.scene.*;
import javafx.scene.layout.*;
import javafx.scene.shape.*;
import javafx.scene.control.*;
import javafx.stage.*;
 
public class EllipseShape extends Application 
{
    StackPane createButtonPane(Button button)
    {
        StackPane pane = new StackPane();

        pane.getChildren().add(button);
        pane.setPadding(new Insets(10, 10, 10, 10));

        return pane;
    }

    @Override
    public void start(Stage primaryStage) 
    {

        Ellipse ellipse  = new Ellipse(75,106, 10, 10);

        Pane ellipsePane = new Pane();
        ellipsePane.getChildren().add(ellipse);
       
        Button up    = new Button("Short");
        Button down  = new Button("Tall");
        Button left  = new Button("Narrow");
        Button right = new Button("Wide");

        up.setOnAction   (e -> 
            ellipse.setRadiusY(ellipse.getRadiusY() - 5));

        down.setOnAction (e -> 
            ellipse.setRadiusY(ellipse.getRadiusY() + 5));

        left.setOnAction (e -> 
            ellipse.setRadiusX(ellipse.getRadiusX() - 5));

        right.setOnAction(e -> 
            ellipse.setRadiusX(ellipse.getRadiusX() + 5));

        BorderPane pane = new BorderPane();        

        pane.setTop(createButtonPane(up));
        pane.setLeft(createButtonPane(left));
        pane.setRight(createButtonPane(right));
        pane.setBottom(createButtonPane(down));

        pane.setCenter(ellipsePane);

        Scene scene = new Scene(pane, 300, 300);

        primaryStage.setTitle("Ellipse Shape"); 
        primaryStage.setScene(scene); 
        primaryStage.show(); 
    }

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

The window looks as follows after pressing the wide button three times and the short button once:

 

 

The centerX and centerY text fields of the CircleAttributes program change the X and Y coordinates of the center of the circle to the value entered in the text field, respectively. The radius text field sets the circle’s radius to the value entered in the text field. Note that the action only occurs after the <Enter> key is pressed.

    centerX.setOnAction(e -> 
        circle.setCenterX(
            Integer.parseInt(centerX.getText())));

    centerY.setOnAction(e ->
        circle.setCenterY(
            Integer.parseInt(centerY.getText())));

    radius.setOnAction (e ->  
        circle.setRadius(
            Integer.parseInt(radius.getText())));

 

Listing 10 - CircleAttributes.java (version 2)

import javafx.geometry.*;
import javafx.application.*;
import javafx.scene.paint.*;
import javafx.scene.*;
import javafx.scene.layout.*;
import javafx.scene.text.*;
import javafx.scene.shape.*;
import javafx.scene.control.*;
import javafx.stage.*;
 
public class CircleAttributes extends Application 
{
    @Override
    public void start(Stage primaryStage) 
    {
        Circle circle  = new Circle(75, 55, 10);

        Pane circlePane = new Pane();
        circlePane.getChildren().add(circle);

        TextField centerX = new TextField("75");
        TextField centerY = new TextField("55");
        TextField radius  = new TextField("10");

        centerX.setPrefColumnCount(5);
        centerY.setPrefColumnCount(5);
        radius.setPrefColumnCount(5);

        centerX.setOnAction(e -> 
            circle.setCenterX(
                Integer.parseInt(centerX.getText())));

        centerY.setOnAction(e ->
            circle.setCenterY(
                Integer.parseInt(centerY.getText())));

        radius.setOnAction (e ->  
            circle.setRadius(
                Integer.parseInt(radius.getText())));
        
        HBox text = new HBox(5);
 
        text.getChildren().addAll(centerX, centerY, radius);
        text.setPadding(new Insets(10, 10, 0, 10));
        
        VBox colors = new VBox(10);

        Text heading = new Text("Select a color:");

        RadioButton red   = new RadioButton("red");
        RadioButton green = new RadioButton("green");
        RadioButton blue  = new RadioButton("blue");
        
        red.setOnAction(e -> circle.setFill(Color.RED));
        green.setOnAction(e -> circle.setFill(Color.GREEN));
        blue.setOnAction(e -> circle.setFill(Color.BLUE));
        
        ToggleGroup colorGroup = new ToggleGroup();

        red.setToggleGroup(colorGroup);
        green.setToggleGroup(colorGroup);
        blue.setToggleGroup(colorGroup);
 
        VBox.setMargin(red,   new Insets(0, 0, 0, 15));
        VBox.setMargin(green, new Insets(0, 0, 0, 15));
        VBox.setMargin(blue,  new Insets(0, 0, 0, 15));

        colors.getChildren().addAll(heading, red, green, blue);

        colors.setPadding(new Insets(10, 0, 10, 10)); 

        BorderPane pane = new BorderPane();        

        pane.setTop(text);
        pane.setLeft(colors);
        pane.setCenter(circlePane);
        
        Scene scene = new Scene(pane);

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

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

The window looks as follows after entering the 100, 80, and 20 in the text fields and pressing the blue button: 

 

 

The display button of the Address program uses the current state of the checkboxes in order to format an address string that is displayed in the text area. The state of a checkbox is obtained by calling its isSelected method.

    button.setOnAction(e -> 
    {
        String s = "";

        if (singleLine.isSelected())
        {
            s += city.getText() + ", " + state.getText() + 
                 " " + zipCode.getText();
        }
        else
        {
            s += city.getText() + "\n" + state.getText() + 
                 "\n" + zipCode.getText();
        }

        if (displayPN.isSelected())
        {
                s += "\n" + phone.getText();
        }

        if (displayEmail.isSelected())
        {
            s += "\n" + email.getText();
        }

        area.setText(s);
    });

 

Listing 11 - Address.java (version 3)

import javafx.application.*;
import javafx.geometry.*;
import javafx.scene.*;
import javafx.scene.layout.*;
import javafx.scene.control.*;
import javafx.stage.*;
 
public class Address extends Application 
{
    TextField city, state, zipCode, phone, email;

    CheckBox singleLine, displayPN, displayEmail;

    TextArea area;
    
    Pane createInputPane()
    {
        Label cityLabel    = new Label("City:"); 
        Label stateLabel   = new Label("State:"); 
        Label zipCodeLabel = new Label("ZipCode:"); 
        Label phoneLabel   = new Label("Phone:"); 
        Label emailLabel   = new Label("Email:"); 
        
        city    = new TextField();
        state   = new TextField();
        zipCode = new TextField();
        phone   = new TextField();
        email   = new TextField();

        city.setPrefColumnCount(10);
        state.setPrefColumnCount(10);
        zipCode.setPrefColumnCount(10);
        phone.setPrefColumnCount(10);
        email.setPrefColumnCount(10);

        GridPane inputPane = new GridPane();
        inputPane.setPadding(new Insets(10, 0, 10, 10));

        inputPane.setHgap(10);
        inputPane.setVgap(5);

        inputPane.add(cityLabel   , 0, 0);
        inputPane.add(city        , 1, 0);
        inputPane.add(stateLabel  , 0, 1); 
        inputPane.add(state       , 1, 1);
        inputPane.add(zipCodeLabel, 0, 2); 
        inputPane.add(zipCode     , 1, 2);
        inputPane.add(phoneLabel  , 0, 3); 
        inputPane.add(phone       , 1, 3);
        inputPane.add(emailLabel  , 0, 4);  
        inputPane.add(email       , 1, 4);

        return inputPane;
    }
    
    Pane createCheckboxes()
    {
        singleLine   = new CheckBox("Single Line");
        displayPN    = new CheckBox("Display PN");
        displayEmail = new CheckBox("Display Email");

        VBox checkboxes = new VBox(5);

        checkboxes.getChildren().addAll(singleLine, displayPN, 
                                        displayEmail);

        checkboxes.setPadding(new Insets(0, 0, 0, 10));

        return checkboxes;
    }

    Pane createDisplayButton()
    {
        Button button = new Button("Display");

        button.setOnAction(e -> 
        {
            String s = "";

            if (singleLine.isSelected())
            {
                s += city.getText() + ", " + state.getText() + 
                     " " + zipCode.getText();
            }
            else
            {
                s += city.getText() + "\n" + state.getText() + 
                     "\n" + zipCode.getText();
            }

            if (displayPN.isSelected())
            {
                s += "\n" + phone.getText();
            }

            if (displayEmail.isSelected())
            {
                s += "\n" + email.getText();
            }

            area.setText(s);
        });

        StackPane buttonPane = new StackPane();

        buttonPane.getChildren().add(button);

        buttonPane.setPadding(new Insets(10, 0, 10, 0));

        return buttonPane;
    }
 
    Pane createTextArea()
    {
        area = new TextArea();

        area.setPrefColumnCount(5);
        area.setPrefRowCount(5);

        StackPane areaPane = new StackPane();

        areaPane.getChildren().add(area);
        areaPane.setPadding(new Insets(0, 20, 0, 10));

        return areaPane;
    }

    @Override
    public void start(Stage primaryStage) 
    {
        BorderPane pane = new BorderPane();

        pane.setTop(createInputPane());
        pane.setLeft(createCheckboxes());
        pane.setBottom(createDisplayButton())

        area = new TextArea();

        area.setPrefColumnCount(5);
        area.setPrefRowCount(5);

        pane.setCenter(createTextArea());

        Scene scene = new Scene(pane,300,300);

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

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

The window looks as follows after populating the text fields and selecting all the checkboxes

and pressing the display button:

 

 

The window looks as follows after selecting only the “display Email” checkbox and pressing the display button:

 

 

Section 10 - Project: A Dictionary

 

JavaFX programs frequently need one or more data structures to provide the data displayed in their nodes. Suppose you wanted to write a JavaFX program that maintains a dictionary. The dictionary can be displayed in a text area. Since a dictionary is merely a collection of words paired with their definitions, an implementation of the Map interface in the Java Collections Framework would be ideal to back the text area by providing the data. The words and definitions can be input using two text fields. Since we want our dictionary to preserve the input order, we will used a LinkedHashMap:

    public class Dictionary extends Application 
    {
        private TextField word, definition;

        private TextArea  area;

        private LinkedHashMap<String, String> dictionary;

        ...
    }

The updateArea method is called when buttons are pressed. It selects between the dictionary field and a local tree map (which is sorted by its keys) based on the state of the sorted button.

    private void updateArea()
    {
        Map<String, String> map = dictionary;

        if (sorted.isSelected())
            map = new TreeMap(dictionary);

        updateAreaWithMap(map);
    }

The updateAreaWithMap method uses the selected map to create a string containing each word and its definition on separate lines. The string is then used to update the text area.

    private void updateAreaWithMap(Map<String, String> map)
    {
        text = "";

        map.forEach( (w, d) -> text += w + ":" + d + "\n");

        area.setText(text);
    }

We will provide three operations for our dictionary: insert, find, and delete. The update operation is unnecessary, since it is identical to the insert operation for a map (if the word already exists, it is updated with the new definition).  

 

The insert button reads the strings in the word and definition text fields to add or modify an entry in the dictionary. It then calls the updateArea method. In order to support multiple iterations of the same operation, the insert, find, and delete buttons deselect themselves.

 

      insert.setOnAction(e -> {

         dictionary.put(word.getText(), definition.getText());

         updateArea();

         insert.setSelected(false);
      });

 The find button reads the string in the word text field and looks up the corresponding definition, which it displays in the definition text field. If the word is not found, “NOT FOUND: is displayed in the definition text field.

      find.setOnAction(e -> {

         String s = dictionary.get(word.getText());

         if (s == null)
             s = "NOT FOUND";

         definition.setText(s);

         find.setSelected(false);
      });

 The delete button reads the strings in the word text field and removes the corresponding entry in the dictionary. It then calls the updateArea method and clears the text fields. 

      delete.setOnAction(e -> {

         dictionary.remove(word.getText());

         updateArea();

         word.setText("");
         definition.setText("");

         delete.setSelected(false);
      });

 The insertion order and sorted buttons simply call the updateArea method to display the dictionary.

 

The text fields are layed out in an HBox that occupies the top region of the border pane.

The text area occupies the left region of the border pane. The insertfind, and delete buttons are layout out in an HBox. The insertion order and sorted buttons are layout out in another HBox. Both Hboxes are placed in a VBox that occupies the bottom region of the border pane. The windows is displayed as follows:

 


 

Listing 12 - Dictionary.java

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

public class Dictionary extends Application 
{
  private TextField word, definition;

  private TextArea  area;

  private LinkedHashMap<String, String> dictionary;

  RadioButton sorted;

  String text;

  @Override public void start(Stage primaryStage) 
  {
    dictionary = new LinkedHashMap<>();
    
    BorderPane pane = new BorderPane();

    pane.setTop(drawTextFields());
    pane.setLeft(drawTextArea());
    pane.setBottom(drawButtons());
    
    Scene scene = new Scene(pane);

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

  private Pane drawTextFields()
  {
      word = new TextField();

      word.setPrefColumnCount(10);

      definition = new TextField();
      definition.setPrefColumnCount(20);

      Pane pane = new HBox();
      pane.getChildren().addAll(word, definition);

      return pane;
  }

  private TextArea drawTextArea()
  {
      area = new TextArea();

      area.setPrefColumnCount(30);
      area.setPrefRowCount(5);

      return area;
  }

  private Pane drawButtons()
  {
      RadioButton insert         = new RadioButton("insert");
      RadioButton find           = new RadioButton("find");
      RadioButton delete         = new RadioButton("delete");
      RadioButton insertionOrder = new RadioButton("insertion order");
                  sorted         = new RadioButton("sorted");

      HBox hboxTop = new HBox(5);
      hboxTop.getChildren().addAll(insert, find, delete);

      hboxTop.setAlignment(Pos.CENTER);

      HBox hboxBottom = new HBox(5);
      hboxBottom.getChildren().addAll(insertionOrder, sorted);

      hboxBottom.setAlignment(Pos.CENTER);

      VBox buttons = new VBox(5);
      buttons.getChildren().addAll(hboxTop, hboxBottom);
      
      ToggleGroup groupTop = new ToggleGroup();

      insert.setToggleGroup(groupTop);
      find.setToggleGroup(groupTop);
      delete.setToggleGroup(groupTop);

      ToggleGroup groupBottom = new ToggleGroup();

      insertionOrder.setToggleGroup(groupBottom);
      sorted.setToggleGroup(groupBottom);

      insert.setOnAction(e -> {

         dictionary.put(word.getText(), definition.getText());

         updateArea();

         insert.setSelected(false);
      });

      find.setOnAction(e -> {

         String s = dictionary.get(word.getText());

         if (s == null)
             s = "NOT FOUND";

         definition.setText(s);

         find.setSelected(false);
      });

      delete.setOnAction(e -> {

         dictionary.remove(word.getText());

         updateArea();

         word.setText("");
         definition.setText("");

         delete.setSelected(false);
      });

      insertionOrder.setOnAction( e -> updateArea() );

      sorted.setOnAction( e -> updateArea() );

      return buttons;
  }

  private void updateArea()
  {
      Map<String, String> map = dictionary;

      if (sorted.isSelected())
          map = new TreeMap(dictionary);

      updateAreaWithMap(map);
  }

  private void updateAreaWithMap(Map<String, String> map)
  {
      text = "";

      map.forEach( (w, d) -> text += w + ":" + d + "\n");

      area.setText(text);
  }

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

 

Table 1 shows each operation and the corresponding change in the window.



Operations

Results

word field: guinea pig
definition field: not from Guinea

<insert>

 

word field: cat
definition field: furry feline

<insert>

 

word field: dog
definition field: man's best friend

<insert>

 

<sorted>

 

word field: canary

<find>

 

word field: guinea pig

<find>

 

definition field: not a pig

<insert>

 

word field: dog

<delete>

 

 

Table 1 - Dictionary operations and results