Recommended: Sing it, brah! 5 fabulous songs for developers
JW's Top 5
Java Tutor is my platform for teaching about Java 7+ and JavaFX 2.0+, mainly via programming projects.
| 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.
|
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.
|
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:
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. |
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 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 |
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.
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.)
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).