JavaFX - Overview with Hands-on

JavaFX
Knoldus Blog Audio
Reading Time: 8 minutes

1. Overview

JavaFX is a Java library used to build Rich Internet Applications (RIA). It provides a set of graphics and media packages that enables developers to design, create, test, debug, and deploy rich client applications that operate consistently across diverse platforms.

JavaFX provides a rich graphical user interface. JavaFX has the structure and APIs specifically for animation, 2D and 3D geometry, charts, special effects, color gradients, graphical controls, and easy manipulation of media, including audio, video, and images.

2. JavaFX Key Features

  • Cross-platform compatibility: JavaFX is available on the leading desktop operating systems (Windows, Linux, and Mac OS X).
  • Java library: JavaFX is a Java library that consists of classes and interfaces that are written in native Java code.
  • FXML: FXML is an XML-based declarative markup language to define the structure of the user interface of the JavaFX application.
  • WebView: WebView is a web component that uses WebKitHTMLtechnology for embedding HTML content within a JavaFX application. JavaScript running in WebView can call Java APIs, and Java APIs can call JavaScript running in WebView.
  • Swing interoperability: Existing Swing applications can be updated with JavaFX features like embedded web content and rich graphics media. We can create rich content a whole lot easier using JavaFX than with Swing.
  • Built-in UI controls: JavaFX provides all the major UI controls required to develop a full-featured application.
  • CSS-like Styling: JavaFX can be skinned with standard Web technologies such as CSS. It provides a CSS-like styling to improve the design of our application.
  • Canvas API: JavaFX Canvas API enables drawing directly within an area of the JavaFX scene that consists of one graphical element.
  • Multitouch Support: JavaFX provides support for multitouch operations, based on the capabilities of the underlying platform.
  • Integrated Graphics library: JavaFX provides classes for 2D and 3D graphics.
  • Graphics pipeline: JavaFX supports graphics based on the hardware-accelerated graphics rendering pipeline known as Prism. When used with a supported Graphic Card or GPU it offers smooth graphics. In case the system does not support a graphic card then prism defaults to the software rendering stack.
  • Self-contained application deployment model: Self-contained application packages have all of the application resources and a private copy of the Java and JavaFX runtimes. They are distributed as native installable packages and provide the same installation and launch experience as native applications for that operating system.

3. JavaFX Application Structure

JavaFX uses the metaphor of a theater to model the graphics application.

Stage

  • A stage represents the top-level container or window. It contains all the objects of a JavaFX application.
  • It is defined by the javafx.stage.Stage class.
  • The size of the stage can be specified by passing its dimensions (height and width).
  • The stage is divided into content-area and decorations (title bar and borders).

Scene

  • A scene represents the physical contents of a JavaFX application. It contains all the individual controls or components.
  • It is defined by the javafx.scene.Scene class.
  • An application can have more than one scene, but only one of the scenes can be displayed on the stage at any given time.
  • The size of the scene can be specified by passing its dimensions (height and width) along with the root node to its constructor.

Scene Graph

  • A scene graph is a tree-like data structure (hierarchical) representing the contents of a scene. All visual components (controls, layouts, etc.) are part of the scene graph.
  • Scene graph components must be attached to a scene to be displayed, and that scene must be attached to a stage for the whole scene to be visible.

Nodes

  • A node is a visual/graphical object of a scene graph.
  • Nodes of scene graph are defined by javafx.scene.Node class.
  • A node may include:
    Geometrical or graphical objects: 2D, 3D
    UI controls: Button, CheckBox, ChoiceBox, TextArea, etc.
    Containers or layout panes: BorderPane, GridPane, FlowPane, etc.
    Media elements: Audio, Video, and Image objects.
  • Nodes are of the following types:
    Root node: First node of the scene graph.
    Branch/Parent node: Node with child nodes like — Group, Region, StackPane, etc.
    Leaf Node − Node without child nodes like — Rectangle, Ellipse, Box, ImageView, MediaView, etc.

4. JavaFX Components

JavaFX comes with a large set of built-in GUI components, like buttons, text fields, tables, trees, menus, charts, and much more.

Some of the major components in JavaFX are:

Controls

  • JavaFX controls are JavaFX components that provide some kind of control functionality inside a JavaFX application.
  • For a control to be visible it must be attached to the scene graph of some scene object.
  • Controls are usually nested inside some JavaFX layout component that manages the layout of controls relative to each other.
  • Some of the examples of JavaFX controls are — Button, CheckBox, Label, Menu, RadioButton, TableView, TextField, TreeView, etc.

Layouts

  • JavaFX layouts are components that contain other components inside them. The layout component manages the layout of the components nested inside it.
  • JavaFX layout components are also sometimes called parent components because they contain child components and because layout components are subclasses of the JavaFX class javafx.scene.Parent.
  • A layout component must be attached to the scene graph of some scene object to be visible.
  • It is possible to nest layout components inside other layout components. This can be useful to achieve a specific layout.
  • Some of the examples of JavaFX layouts are — Group, Pane, HBox, VBox, BorderPane, StackPane, GridPane, etc.

Charts

JavaFX comes with a set of built-in ready-to-use chart components to avoid coding charts from scratch every time you need a basic chart.

2D/3D Graphics

JavaFX contains features that make it easy to draw 2D/3D graphics on the screen.

Audio

JavaFX provides features that make it easy to play audio in JavaFX applications. This is typically useful in games or educational applications.

Video

JavaFX contains features that make it easy to play video in JavaFX applications. This is typically useful in streaming applications, games, or educational applications.

WebView

JavaFX contains a WebView component that is capable of showing web pages using HTML and CSS. The JavaFX WebView component is based on WebKitHTML technology.

5. Build GUI Application Using JavaFX

Here, we are creating a simple JavaFX application as shown in the video. This application consists of a loading GIF, a loading text, a cross button control, and a background.

Let’s have a look at the source code of this JavaFX application:

package com.knoldus.javafx;

import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
import javafx.scene.text.TextAlignment;
import javafx.stage.Stage;
import javafx.stage.StageStyle;

public class JavaFXSeeder extends Application {
    public final static String ID = "seeder";
    public static final String SEEDER_CONTAINER_ID = "seeder-container-id";
    public final static String SEEDER_BACKGROUND_ID = "seeder-background-id";
    public final static String SEEDER_CONTENT_ID = "seeder-content-id";
    public final static String SEEDER_CONTROLS_ID = "seeder-controls-id";
    public final static String SEEDER_LOGO_ID = "seeder-logo-id";
    public final static String SEEDER_STATUS_ID = "seeder-status-id";
    public final static String SEEDER_CLOSE_BUTTON_ID = "seeder-close-button-id";

    public final static String IMAGES = "file:src/main/resources/img";
    public final static String SEEDER_BACKGROUND = IMAGES + "/seeder-background.jpg";
    public final static String SEEDER_LOGO = IMAGES + "/seeder-logo.gif";
    public final static String SEEDER_ICON = IMAGES + "/seeder-icon.jpg";
    public final static String SEEDER_CLOSE_BUTTON = IMAGES + "/seeder-close-button.png";
    public final static String SEEDER_CLOSE_BUTTON_HOVERED = IMAGES + "/seeder-close-button-hovered.png";

    public final static String SEEDER_STAGE_TITLE = "Loader";
    public final static String SEEDER_STATUS = "Loading";

    public final static double SEEDER_CLOSE_BUTTON_SIZE = 24;
    private static final double SEEDER_WIDTH = 768;
    private static final double SEEDER_HEIGHT = 464;
    private static final double SEEDER_LOGO_SIZE = 200;

    private static double xOffset = 0;
    private static double yOffset = 0;

    private final double SEEDER_STATUS_FONT_SIZE = 24;
    private final String SEEDER_STATUS_FONT_FAMILY = "Roboto Medium";
    private final Color SEEDER_STATUS_COLOR = Color.rgb(25, 25, 25, 1);
    private final Font SEEDER_STATUS_FONT = Font.font(SEEDER_STATUS_FONT_FAMILY, FontWeight.BOLD, SEEDER_STATUS_FONT_SIZE);

    private final StackPane m_seederContainer = new StackPane();
    private final VBox m_seederContent = new VBox();
    private final HBox m_seederControls = new HBox();
    private final ImageView m_seederBackground = new ImageView();
    private final ImageView m_seederLogo = new ImageView();
    private final ImageView m_seederCloseButtonImage = new ImageView();
    private final Text m_seederStatus = new Text();
    private final Button m_seederCloseButton = new Button();
    private final Group m_root = new Group();
    private final Stage m_stage = new Stage();
    private Image m_image;

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

    @Override
    public void start(Stage stage) {
        initialize();
        showSeederWindow();
    }

    private void initialize() {
        Scene scene = new Scene(m_root, SEEDER_WIDTH, SEEDER_HEIGHT);
        m_stage.setScene(scene);
        m_stage.setTitle(JavaFXSeeder.SEEDER_STAGE_TITLE);
        m_stage.setHeight(SEEDER_HEIGHT);
        m_stage.setWidth(SEEDER_WIDTH);
        m_stage.initStyle(StageStyle.TRANSPARENT);
        m_stage.getIcons().addAll(new Image(SEEDER_ICON));
        m_stage.setOnCloseRequest(windowEvent ->
        {
            windowEvent.consume();
            handleCloseEvent();
        });

        m_root.setId(ID);

        StackPane.setAlignment(m_seederContainer, Pos.CENTER);

        m_seederContainer.setId(SEEDER_CONTAINER_ID);
        m_seederContainer.setMaxSize(SEEDER_WIDTH, SEEDER_HEIGHT);

        initializeSeederBackground();

        initializeSeederContent();

        initializeSeederControls();

        m_seederContainer.getChildren().addAll(m_seederBackground, m_seederContent, m_seederControls);
        m_root.getChildren().add(m_seederContainer);

        dragSeeder();
    }

    public void initializeSeederBackground() {
        m_image = new Image(SEEDER_BACKGROUND);

        m_seederBackground.setId(SEEDER_BACKGROUND_ID);
        m_seederBackground.setImage(m_image);
        m_seederBackground.setFitWidth(SEEDER_WIDTH);
        m_seederBackground.setFitHeight(SEEDER_HEIGHT);
    }

    public void initializeSeederContent() {
        initializeSeederLogo();
        initializeSeederStatus();

        m_seederContent.getChildren().addAll(m_seederLogo, m_seederStatus);

        m_seederContent.setId(SEEDER_CONTENT_ID);
        m_seederContent.setSpacing(10);
        m_seederContent.setAlignment(Pos.CENTER);
    }

    public void initializeSeederLogo() {
        m_image = new Image(SEEDER_LOGO);

        m_seederLogo.setId(SEEDER_LOGO_ID);
        m_seederLogo.setImage(m_image);
        m_seederLogo.setFitWidth(SEEDER_LOGO_SIZE);
        m_seederLogo.setFitHeight(SEEDER_LOGO_SIZE);
    }

    public void initializeSeederStatus() {
        m_seederStatus.setText(SEEDER_STATUS);
        m_seederStatus.setId(SEEDER_STATUS_ID);
        m_seederStatus.setFill(SEEDER_STATUS_COLOR);
        m_seederStatus.setFont(SEEDER_STATUS_FONT);
        m_seederStatus.setTextAlignment(TextAlignment.CENTER);
    }

    public void initializeSeederControls() {
        initializeSeederCloseButton();

        m_seederControls.getChildren().addAll(m_seederCloseButton);
        m_seederControls.setId(SEEDER_CONTROLS_ID);
        m_seederControls.setAlignment(Pos.TOP_RIGHT);
    }

    public void initializeSeederCloseButton() {
        m_image = new Image(SEEDER_CLOSE_BUTTON);
        m_seederCloseButtonImage.setImage(m_image);
        m_seederCloseButtonImage.setFitWidth(SEEDER_CLOSE_BUTTON_SIZE);
        m_seederCloseButtonImage.setFitHeight(SEEDER_CLOSE_BUTTON_SIZE);

        m_seederCloseButton.setId(SEEDER_CLOSE_BUTTON_ID);
        m_seederCloseButton.setGraphic(m_seederCloseButtonImage);
        m_seederCloseButton.setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, CornerRadii.EMPTY, Insets.EMPTY)));
        m_seederCloseButton.setOnAction(handleCloseEvent());
        m_seederCloseButton.addEventHandler(MouseEvent.ANY, seederCloseButtonMouseEvent());
    }

    private EventHandler<? super MouseEvent> seederCloseButtonMouseEvent() {
        return (EventHandler<MouseEvent>) event ->
        {
            if (event.getEventType() == MouseEvent.MOUSE_ENTERED) {
                m_image = new Image(SEEDER_CLOSE_BUTTON_HOVERED);
                m_seederCloseButtonImage.setImage(m_image);
            } else if (event.getEventType() == MouseEvent.MOUSE_EXITED) {
                m_image = new Image(SEEDER_CLOSE_BUTTON);
                m_seederCloseButtonImage.setImage(m_image);
            }
        };
    }

    public void showSeederWindow() {
        m_stage.show();
        m_stage.toFront();
        m_stage.setAlwaysOnTop(true);
    }

    private EventHandler<ActionEvent> handleCloseEvent() {
        return event -> System.exit(0);
    }

    private void dragSeeder() {
        m_seederContainer.setOnMousePressed(event ->
        {
            xOffset = event.getSceneX();
            yOffset = event.getSceneY();
        });
        m_seederContainer.setOnMouseDragged(event ->
        {
            m_stage.setX(event.getScreenX() - xOffset);
            m_stage.setY(event.getScreenY() - yOffset);
        });
    }
}

Let’s understand the steps of building this JavaFX application:

Set entry-point:

  • A JavaFX GUI program extends from javafx.application.Application.
  • It is the entry point of the application in JavaFX.

Define start method:

  • JavaFXSeeder class inherits the Application class and implements its abstract method start().
  • In this method, we will write the entire code for the JavaFX graphics.

Set scene:

  • Allocate javafx.scene.Scene by specifying the root of the scene graph, where the root is of type javafx.scene.Group.
  • We can also pass two parameters of double type representing the height and width of the scene.

Set stage:

  • We then set the javafx.stage.Stage object. Set the title for the stage using the method setTitle(). Attach the scene to the stage using the setScene() method.
  • Display the contents of the scene using the show() method.
  • The primaryStageis a stage object which is passed to the start method of the scene class, as a parameter. The primary stage is created by the platform itself.
  • We can also set the width, height, taskbar icon, and StageStyle of the stage.
  • We can also set our application on top of other applications by invoking setAlwaysOnTop() on the stage object.

Set outer-layout:

  • Here we use javafx.scene.layout.StackPane as top-level layout node, which layouts its children in a back-to-front stack.
  • We can assign a unique ID and dimensions to this StackPane.
  • We can also add mouse-drag events on the outer layout using setOnMousePressed() and setOnMouseDragged() event handlers.
  • Set the position of the layout on the screen using setAlignment().

Set inner-layout:

  • The outer layout node has three children nodes, which is the Image, javafx.scene.layout.HBox and javafx.scene.layout.VBox .
  • HBox arranges its content nodes horizontally in a single row. VBox arranges its content nodes vertically in a single column.
  • We can add nodes to a layout control with getChildren().add() for a single node and getChildren().addAll() for multiple nodes.

Set controls:

  • JavaFX provides a huge set of controls (or components) in package javafx.scene.control.
  • Construct a Button control and place it in the top-right corner of the window. Attach a javafx.event.EventHandler<ActionEvent> to the Button, via method setOnAction().
  • We can embed javafx.scene.shape.Textinto a JavaFX scene by instantiating this class. We can set the font, color, position, etc. of the text.
  • We can load an image in JavaFX by instantiating the class javafx.scene.image.Image. After loading the image, we can set the view for the image by instantiating the ImageViewclass and passing the image to its constructor.

Launch application:

  • In the main()method, we have to launch the application using the launch() method.
  • This method internally calls the start() method of the Application class.

6. Summary

In this article, we talked about JavaFX and its tremendous capabilities. We learned about the major key features provided by JavaFX. We studied JavaFX application structure and how the scene, stage, scene graph, and nodes can be modeled into a graphics application. We read about a large set of built-in GUI components provided by JavaFX. Then we created a JavaFX application and studied its working.

From interpreted JavaFX to a full-blown GUI library, the growth of JavaFX is commendable.