Newsletter sign-up
View all newsletters

Enterprise Java Newsletter
Stay up to date on the latest tutorials and Java community news posted on JavaWorld

JavaWorld Daily Brew

Java Tutor

Java Tutor is my platform for teaching about Java 7+ and JavaFX 2.0+, mainly via programming projects.


Rebooting JavaFX, Part 1

 

JavaFX is a platform for delivering Rich Internet Applications (RIAs). Sun Microsystems introduced this platform at its JavaOne Worldwide Java Developer Conference in May 2007. In late July 2008, Sun released the JavaFX Preview SDK for creating JavaFX-based RIAs.

Note: Check out Wikipedia's Rich Internet application entry for an introduction to RIAs.

The JavaFX Preview SDK and subsequent SDKs (through JavaFX 1.3.1) include a compiler for JavaFX Script, a declarative language for specifying a JavaFX application's source code. JavaFX Script supports binding, function types, and other powerful language features.

Note: JavaFX Script evolved from Charles Oliver's F3 (Form Follows Function) language -- see Oliver's original F3 blog post.

Oracle took over Sun in early 2010, and announced a new roadmap for JavaFX at the September JavaOne Conference. One of Oracle's plans for this technology was to discontinue JavaFX Script in favor of accessing Java APIs from Java and other languages.

Note: JavaFX Script lives on as Visage, a language for user interfaces -- see Stephen Chin's Visage announcement.

On May 26, 2011, Oracle released a beta version of JavaFX 2.0. It made JavaFX 2.0 generally available on October 10, followed up with a security-bugfix 2.0.1 release several days later, and released 2.0.2 with bug fixes and minor new features on December 12.

Note: Oracle's JavaFX 2.0 Roadmap presents Oracle's timeline for future JavaFX releases.

This tutorial begins a two-part series on JavaFX 2.0. Part 1 focuses on installing the JavaFX 2.0.2 SDK, exploring JavaFX's architecture, and developing a rich "Hello, World"-style application, where you learn about application architecture and some basic JavaFX APIs.

Note: JavaFX has been maligned over the years; perhaps you think that learning about JavaFX 2.0 is a waste of time. However, Osvaldo Pinali Doederlein points out an important reason to not overload JavaFX 2.0 in his JavaFX 2.0 Beta: First impressions blog post: JavaSE as we know it is deprecated. I wonder how many people realize this; if you don't, check again Cindy Castillo's great overview of the JavaFX Architecture. It's not just a new library of components, animation and rich media. It's something that completely replaces AWT [Abstract Window Toolkit], Java2D, Swing, Java Sound, Applets, ImageIO, Accessibility -- in short, the entire Client layer of the JavaSE platform. (No, a JavaFX "applet" doesn't use the java.applet API anymore.) Oracle got rid of the massive legacy of the AWT and everything that was built on top of the AWT; that's the major reason why the new browser plugin is much better. It seems that AWT, Java 2D, Swing, Java Sound, Applets, ImageIO, and Accessibility are heading for obsolescence.

Installing JavaFX 2.0.2

If you're running Windows XP/Vista/7, point your browser to the JavaFX Downloads page, accept the license agreement, and click the Windows 32-bit or Windows 64-bit link. Clicking the former link gives you the opportunity to download the javafx_sdk-2_0_2-windows-i586.exe installer

Assuming that you've downloaded javafx_sdk-2_0_2-windows-i586.exe, run this installer to install the JavaFX SDK and runtime onto your system. This allows you to develop Java-based JavaFX applications and deploy these applications to run as standalone applications, over the Web using Java Web Start technology, and as applets embedded into Web pages.

Note: On my Windows XP SP3 platform, the installer creates a C:\Program Files\Oracle\JavaFX 2.0 Runtime directory and a C:\Program Files\Oracle\JavaFX 2.0 SDK directory. The former directory is analogous to the Java Runtime Environment, whereas the latter directory is analogous to the Java SE Development Kit.

JavaFX 2.0 Architecture

JavaFX 2.0's architecture consists of public APIs, the scene graph, the Quantum Toolkit, Prism, the Glass Windowing Toolkit, the Media and Web engines, Java 2D/OpenGL/D3D, and the JVM. Figure 1 reveals how JavaFX 2.0 layers these items.

Figure 1: JavaFX 2.0's layered architecture.

JavaFX 2.0 public APIs and the scene graph sit at the top of the hierarchy. The scene graph is a tree of nodes that represent all of the visual elements (e.g., controls or shapes) in the application's user interface.

Note: A node is a single scene graph element. It has an ID, a style class, and a bounding volume. Also, it can be associated with effects (e.g., reflections and drop shadows), opacity, event handlers (to respond to mouse events, for example), transforms (e.g., a rotation), and more.

The Quantum Toolkit sits at the next level in the hierarchy. Quantum connects Prism with the Glass Windowing Toolkit, making them available to the APIs and scene graph situated on the layer above. Quantum also manages threading rules related to rendering versus event-handling.

Look underneath the Quantum Toolkit layer and you'll find a layer consisting of Prism, the Glass Windowing Toolkit, the Media Engine, and the Web Engine:

  • Prism is responsible for the rasterization and rendering of JavaFX scenes, and supports hardware and software renderers (including 3D renderers). Several rendering pipelines are supported: DirectX 9 on Windows XP and Windows Vista, DirectX 11 on Windows 7, OpenGL on Mac and Linux, and Java 2D when hardware acceleration is not available.
  • Glass Windowing Toolkit (the lowest level framework for JavaFX's graphics stack) is a platform-dependent layer that connects the JavaFX platform to the native operating system (via the Java Native Interface). It provides operating system services such as window, timer, and surface management. It also manages the event queue. Unlike the AWT, Glass uses the native operating system's event queue functionality to schedule thread usage.
  • Media Engine supports the playback of MP3, AIFF, and WAV audio files; and the playback of FLV video files. It has been redesigned for JavaFX 2.0 to increase stability, improve performance, and provide consistent behavior across platforms.
  • Web Engine is an open source WebKit-based Web browser engine that supports HTML5, CSS, JavaScript, DOM, and SVG.

Threads and Pulses

To achieve high performance, the JavaFX architecture dedicates three special threads for performing various tasks: JavaFX application thread, Prism render thread, and Media thread. Two or more of these threads, which are described below, may be running at any given time:

  • JavaFX application thread: The primary thread used by JavaFX application developers. Any scene that is part of a window (a live scene) must be accessed from this thread. However, you can create scenes on background threads, allowing you to create complex scenes while keeping animations on live scenes running smoothly and quickly. This thread is different from the Swing and AWT Event-Dispatch Thread (EDT), so be careful when embedding JavaFX code into Swing applications.
  • Prism render thread: The thread that handles rendering separately from the event dispatcher. It supports concurrent processing in that frame N can be rendered while frame N+1 is being processed, which is a big advantage when multiple processors or cores are being used. The Prism render thread may also have multiple rasterization threads that help off-load work needing to be done during rendering.
  • Media thread: A background thread that synchronizes the latest frames through the scene graph by using the JavaFX application thread.

The Glass Windowing Toolkit uses high-resolution native operating system timers to execute pulse events, which are events that synchronize the state of a scene graph's elements with Prism. A pulse is throttled back to a maximum of 60 frames per second.

A pulse is fired whenever an animation is running on the scene graph. Even when there is no running animation, a pulse is scheduled when something changes in the scene graph. For example, a pulse is scheduled when a control (e.g., a button) is transformed in some manner.

When a pulse occurs, the state of the scene graph's elements is synchronized down to the rendering layer. A pulse lets developers handle events asynchronously, and allows the system to batch and execute events on the pulse.

Layout and CSS are connected to pulse events. Because numerous scene graph changes leading to multiple layout or CSS updates could seriously degrade performance, JavaFX automatically performs a CSS and layout pass once per pulse to avoid performance degradation.

Note: Layout or CSS passes can be triggered when needed in order to take measurements before a pulse event occurs.

Hello, JavaFX!

About 2-1/2 years ago, I wrote a four-part "Jump Into JavaFX" series for JavaWorld. My series focused on using the JavaFX Preview SDK to develop JavaFX Script-based applications. Unsurprisingly, the first application presented in this series was a rich variant of "Hello, World!"

For convenience, Listing 1 presents the application's Main.fx source code.

/*
 * Main.fx
 *
 */

package hellojavafx;

/**
 * @author Jeff Friesen
 */

import java.lang.System;

import javafx.animation.Interpolator;
import javafx.animation.Timeline;

import javafx.application.Frame;
import javafx.application.Stage;

import javafx.scene.Font;

import javafx.scene.paint.Color;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Stop;

import javafx.scene.text.Text;
import javafx.scene.text.TextOrigin;

class Model
{
    attribute text: String;
    attribute opacity: Number;
    attribute rotAngle: Number                          
}

var model = Model
{
    text: "Hello, JavaFX!"
}

Frame
{
    title: bind model.text
    
    width: 300
    height: 300
    
    var stageRef: Stage
    stage: stageRef = Stage
    {
        fill: LinearGradient
        {
            startX: 0.0
            startY: 0.0
            endX: 0.0
            endY: 1.0
            stops: 
            [
                Stop { offset: 0.0 color: Color.BLACK },
                Stop { offset: 1.0 color: Color.BLUEVIOLET }
            ]
        }
               
        var textRef: Text
        content: 
        [    
            textRef = Text
            {
                content: bind model.text
                x: bind (stageRef.width-textRef.getWidth ())/2
                y: bind (stageRef.height-textRef.getHeight ())/2
                textOrigin: TextOrigin.TOP

                rotate: bind model.rotAngle
                anchorX: bind textRef.x+textRef.getWidth ()/2
                anchorY: bind textRef.y+textRef.getHeight ()/2
                                
                font: Font
                {
                    name: "Arial"
                    size: 30
                }
                fill: Color.YELLOW
                stroke: Color.ORANGE

                opacity: bind model.opacity
            }
        ]
    }
           
    visible: true
    
    // If a function isn't assigned to closeAction, the script automatically 
    // terminates. If a function is assigned to this attribute, it must include 
    // System.exit() to terminate the script.

    closeAction: function () 
    { 
        System.exit (0)
    }
}

var timeline1 = Timeline 
{
     autoReverse: true
     repeatCount: Timeline.INDEFINITE
     
     var begin = at (0s)
     {
         model.opacity => 0.0
     }
                 
     var end = at (4s)
     {
         model.opacity => 1.0 tween Interpolator.LINEAR
     }
               
     keyFrames: [begin, end]
}
timeline1.start ();

var timeline2 = Timeline 
{
     repeatCount: Timeline.INDEFINITE
     
     var begin = at (0s)
     {
         model.rotAngle => 0.0
     }
                 
     var end = at (5s)
     {
         model.rotAngle => 360.0 tween Interpolator.LINEAR
     }
               
     keyFrames: [begin, end]
}
timeline2.start ();

//model.text = "I"

Listing 1: Main.fx

This source code describes an application that rotates Hello, JavaFX! around the center of its window (which has a gradient background) while animating the text's opacity. Figure 2 reveals the application's output.

Figure 2: Rotating text while animating its opacity.

What does this application look like in a JavaFX 2.0 context? Listing 2 presents HelloJavaFX.java.

// HelloJavaFX.java

import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;

import javafx.application.Application;

import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;

import javafx.geometry.VPos;

import javafx.scene.Group;
import javafx.scene.Scene;

import javafx.scene.paint.Color;
import javafx.scene.paint.CycleMethod;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Stop;

import javafx.scene.text.Font;
import javafx.scene.text.Text;

import javafx.stage.Stage;

import javafx.util.Duration;

public class HelloJavaFX extends Application
{
   DoubleProperty opacity = new SimpleDoubleProperty();
   DoubleProperty rotAngle = new SimpleDoubleProperty();

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

   @Override
   public void start(Stage primaryStage)
   {
      System.out.println(Thread.currentThread());

      primaryStage.setTitle("Hello, JavaFX!");
      primaryStage.setWidth(300);
      primaryStage.setHeight(300);

      Group root = new Group();
      LinearGradient lg;
      lg = new LinearGradient(0f, 0f, 0f, 1f, true, CycleMethod.NO_CYCLE,
                              new Stop[]
                              {
                                 new Stop(0, Color.BLACK),
                                 new Stop(1, Color.BLUEVIOLET)
                              });
      final Scene scene = new Scene(root, lg);

      final Text text = new Text();
      text.setText("Hello, JavaFX!");
      text.setTextOrigin(VPos.TOP);
      text.rotateProperty().bind(rotAngle);
      text.setFont(new Font("Arial", 30.0));
      text.setFill(Color.YELLOW);
      text.setStroke(Color.ORANGE);
      text.opacityProperty().bind(opacity);
      text.xProperty().bind(scene.widthProperty()
          .subtract(text.boundsInLocalProperty().getValue().getWidth())
          .divide(2));
      text.yProperty().bind(scene.heightProperty()
          .subtract(text.boundsInLocalProperty().getValue().getHeight())
          .divide(2));

      root.getChildren().add(text);

      primaryStage.setScene(scene);
      primaryStage.show();

      Timeline timeline1 = new Timeline();
      timeline1.setAutoReverse(true);
      timeline1.setCycleCount(Timeline.INDEFINITE);
      KeyFrame[] kf = new KeyFrame[2];
      kf[0] = new KeyFrame(Duration.ZERO,
                           new KeyValue(opacity, 0.0));
      kf[1] = new KeyFrame(new Duration(4000),
                           new KeyValue(opacity, 1.0));
      timeline1.getKeyFrames().addAll(kf);
      timeline1.play();

      Timeline timeline2 = new Timeline();
      timeline2.setCycleCount(Timeline.INDEFINITE);
      kf[0] = new KeyFrame(Duration.ZERO,
                           new KeyValue(rotAngle, 0.0));
      kf[1] = new KeyFrame(new Duration(5000),
                           new KeyValue(rotAngle, 360.0));
      timeline2.getKeyFrames().addAll(kf);
      timeline2.play();
   }
}

Listing 2: HelloJavaFX.java

I've kept the Java code as close to the JavaFX Script code as possible, to make it easier to explore differences. I'll ignore the import statements in the following discussion because they're nearly identical to their JavaFX Script counterparts.

The application is based on a class that extends the abstract javafx.application.Application class. The subclass's static void main(String[] args) method invokes Application's static void launch(String[] args) method to launch the application.

Note: Oracle's Getting Started with JavaFX tutorial states that the launch() method should be the only method called in main(), and that doing so is considered to be a JavaFX best practice.

launch() saves its command-line arguments array argument, creates the primary stage, and invokes the application's overriding void start(Stage primaryStage) method on the JavaFX application thread.

The overriding start() method outputs the current thread to prove that it is the JavaFX application thread. The primary stage passed to this method corresponds to the application's main window. This is where the application's scene is displayed.

The primary stage is an instance of the javafx.stage.Stage class. Stage's void setTitle(String value) method is called to set the stage's title, which is displayed on its corresponding window's titlebar.

Stage subclasses the javafx.stage.Window class, and Window's void setWidth(double value) and void setHeight(double value) methods are called to set the stage's dimensions.

Next, the javafx.scene.Group class is instantiated to serve as the scene's root node. Additional nodes are stored in this container node, either directly or indirectly via other container nodes. Anchoring a scene in a Group node is considered to be a JavaFX best practice.

A linear gradient is now created via javafx.scene.paint.LinearGradient and its LinearGradient(double startX, double startY, double endX, double endY, boolean proportional, CycleMethod cycleMethod, Stop[] stops) constructor.

Arguments passed to startX, startY, endX, and endY identify the gradient's start and end points. Passing true to proportional states that these points are relative to a unit square, and the gradient is stretched across the shape.

The CycleMethod.NO_CYCLE argument states that the gradient's terminal colors would fill the remaining area if there was a remaining area (and there isn't). The javafx.scene.paint.CycleMethod enum also provides REFLECT and REPEAT constants.

The array of javafx.scene.paint.Stop instances identifies the gradient's colors. The first argument passed to the Stop(double offset, Color color) constructor identifies an offset (from 0.0 to 1.0) where the corresponding javafx.scene.paint.Color value appears.

The javafx.scene.Scene class is instantiated as a container for the scene graph's content. Its Scene(Parent root, Paint fill) constructor is called to anchor the scene graph with the group node and fill its background with the linear gradient.

Note: Specifying a Group instance as the root node results in the contents of the scene graph being clipped by the scene's width and height, and changes to the scene's size (if the user resizes the stage) not altering the layout of the scene graph.

Another JavaFX best practice is to specify the width and height of the scene by calling a constructor such as Scene(Parent root, double width, double height, Paint fill) -- the stage's width and height would not also be specified. I chose to specify the stage's width and height instead of specifying the scene's width and height because I also specified the stage's width and height in the JavaFX Script application whose source code appears in Listing 1, and I wanted to be compatible with that application.

The difference between the stage's and scene's width and height is as follows: The stage's width and height include space for the window border and decorations (e.g., the titlebar with its minimize, maximize, and close buttons). In contrast, the scene's width and height specify the size of the window's client area only.

Moving forward, a Text node is created. Various setter methods (e.g., void setText(String value)) are called to establish various properties (e.g., the text string that's to be displayed).

JavaFX supports enhanced JavaBeans-style properties to facilitate binding. HelloJavaFX takes advantage of this support by declaring rotAngle and opacity field variables of type javafx.beans.property.DoubleProperty in the HelloJavaFX class.

rotAngle and opacity are subsequently accessed in the text.rotateProperty().bind(rotAngle) and text.opacityProperty().bind(opacity) expressions that bind the Text node's rotate and opacity properties to these variables.

Note: rotateProperty() and opacityProperty(), which are inherited from the Node class, demonstrate a pattern where a property name is suffixed with Property.

The concrete javafx.beans.property.SimpleDoubleProperty implementation of the abstract DoubleProperty class inherits the void bind(ObservableValue<? extends Number> rawObservable) method from its javafx.beans.property.DoublePropertyBase parent class. This method creates a unidirectional binding for DoubleProperty subclass instances.

To ensure that the rotating text appears in the center of the scene regardless of changes to the scene's size, Text's xProperty() and yProperty() methods are called to bind these properties to expressions that center the text.

The expressions take advantage of the Fluent API to facilitate binding construction. For example, DoubleProperty inherits methods such as DoubleBinding subtract(double other) and DoubleBinding divide(double other), which are part of the Fluent API.

It's time to add the Text node to the group. This task is accomplished by calling root.getChildren().add(text), which returns an observable list (for letting listeners track changes to the group, for example, node additions) and adds the Text node to this list.

The scene is finished and its Scene object is made accessible to the primary stage by calling Stage's void setScene(Scene value) method. The scene and stage window are then displayed by calling Stage's void show() method.

If there was no more code and you were to run this application, all you would see is a window with a gradient background. You would not see any rotating and opacity-changing text because there would be no animation code to make this happen.

The javafx.animation package's Timeline, KeyFrame, and KeyValue classes are used to animate the opacity and rotAngle property variables. Thanks to binding, changes to these variables result in changes to the Text node's rotate and opacity properties.

Timeline describes a timeline on which animations play out, KeyFrame associates a target value with a specific point in time, and KeyValue identifies the target value. Target values are interpolated between key frames so that a value is associated with each intermediate frame.

The opacity animation code calls Timeline's inherited void setAutoReverse(boolean value) method with a true value to ensure that an animation reverses direction for the next cycle. (Because the rotation animation code is similar, I'll only discuss the opacity animation code.)

Following the call to setAutoReverse(), Timeline's inherited void setCycleCount(int value) method is called to define the number of cycles for the animation. The Timeline.INDEFINITE argument specifies unending cycles.

A two-element KeyFrame array is created for the first animation (and reused for the second). Element 0 associates opacity 0.0 (invisible) with time 0, and element 1 associates opacity 1.0 (fully visible) with time 4 seconds. Opacity linearly interpolates from 0.0 to 1.0 over 4 seconds.

Time instances are described by instances of the javafx.util.Duration class. This class provides a ZERO constant to conveniently represent time instance 0. For other time instances, instantiate this class and pass, to its constructor, the time instance's milliseconds value.

Note: As an alternative to directly instantiating Duration, you can call this class's static Duration valueOf(String time) factory method.

That's it for the code. Execute the following command lines to compile HelloJavaFX.java and (assuming that there are no compilation errors) run the resulting HelloJavaFX classfile -- Figure 2 reveals one frame of the animation:

javac -cp "c:\progra~1\oracle\javafx 2.0 runtime\lib\jfxrt.jar" HelloJavaFX.java
java -cp "c:\progra~1\oracle\javafx 2.0 runtime\lib\jfxrt.jar";. HelloJavaFX

jfxrt.jar is a JAR file containing JavaFX runtime classes. The SDK's version of this file (in the javafx 2.0 sdk\lib directory) contains debug code, whereas the runtime version of this file (in the javafx 2.0 runtime\lib directory) does not.

Exercises

  1. What is JavaFX?
  2. What are the components of JavaFX 2.0's architecture?
  3. Identify the three special threads.
  4. What is a pulse event?
  5. Describe the architecture of a JavaFX application.
  6. Why does JavaFX 2.0 support enhanced JavaBeans-style properties?
  7. What is the Fluent API?
  8. JavaFX 2.0 provides javafx.animation.RotateTransition as an alternative to creating a Timeline for animating a rotation. Modify HelloJavaFX to take advantage of RotateTransition. (Hint: You won't need rotAngle.)

Code

You can download this post's code and answers here. Code was developed and tested with JDK 7u2 and JavaFX SDK 2.0.2 on a Windows XP SP3 platform.

* * *

I welcome your input to this blog, and will write about relevant topics that you suggest. While waiting for the next blog post, check out my TutorTutor website to learn more about Java and other computer technologies (and that's just the beginning).