Jump into JavaFX, Part 1: JavaFX Preview SDK

Experience JavaFX with NetBeans 6.1 and Project Nile

JavaFX seems like a viable alternative for RIA development, but you need to write, build, and run some scripts to know whether it's for you. In this article (the first in a short series), Jeff Friesen introduces you to the JavaFX Preview SDK. Get installation instructions for JavaFX with NetBeans 6.1, create a Hello World script, and explore scalable vector graphics conversion and rendering using Project Nile. Level: Beginner.

It is remarkable to realize that one year ago we could only explore the JavaFX Script language and various JavaFX APIs via an interpreter and an interactive application called JavaFX Pad. Since that time, Sun Microsystems has evolved the language, has created a compiler to improve runtime performance, and has released the JavaFX Preview SDK for JavaFX programming.

The JavaFX Preview SDK (which will be replaced by the upcoming JavaFX SDK 1.0, expected to be released in November) consists of two components that provide the tools, technologies, and resources for creating RIAs (rich Internet applications) that are based on the JavaFX Script language and its associated APIs:

  • NetBeans IDE 6.1 with JavaFX
  • Project Nile

Learning JavaFX, especially at this early stage in its development, is a big undertaking; but it can also be fun. So we'll take our time getting to know the JavaFX SDK, starting this month with using JavaFX and Project Nile from within the NetBeans 6.1 IDE. We'll install the components and then I'll walk you through some easy RIA development using your new tools. Later articles in the series will explore the JavaFX Script language and its APIs. Tutorials will be based on the JavaFX Preview SDK, which is available now, and on JavaFX SDK 1.0, once it is released.

Getting JavaFX Preview SDK

NetBeans IDE 6.1 with JavaFX provides an environment for developing JavaFX-based RIAs. It includes JavaFX tools (such as a compiler) and plugins for these tools, and is available for Windows and Mac OS X (Intel only). Visit Sun's JavaFX Technology Downloads page to learn about the NetBeans IDE 6.1 with JavaFX system requirements and download the component.

What about Linux users?

Although NetBeans IDE 6.1 with JavaFX officially supports only Windows and Mac OS X (Intel only), developer Weiqi Gao shows you how to get most of the Mac OS X version of the SDK to work on Linux in his blog post "Watch JavaFX SDK Run -- On Linux" (see Resources). If you're holding out hope that Linux will be officially supported at some point, there's a good chance that you will get your wish with JavaFX SDK 1.0, which is due out later this year.

Whether you download the NetBeans IDE 6.1 with JavaFX or just the JavaFX tools (if you have NetBeans IDE 6.1 installed and just need the tools), you should also make sure that you have the Java SE 6 update 7 (or later) SDK installed on your platform. Sun recommends that you install this Java SDK prior to installing NetBeans IDE 6.1 with JavaFX.

I installed Java SE 6u7 on my Windows XP platform, and then installed the NetBeans/JavaFX combo. After downloading the netbeans-6.1-javafx-windows.exe installer file, I proceeded to run this installer. If you've never installed the NetBeans IDE on your platform, you'll find that installation proceeds very smoothly.

The installer first presents a welcome screen, shown in Figure 1.

The welcome screen reveals the size of the NetBeans installation.
Figure 1. The welcome screen reveals the size of the NetBeans installation. (Click to enlarge.)

The installer next presents the license agreement screen, as shown in Figure 2 (it's always a good idea to review a license agreement).

You must accept the license agreement to continue with the install.
Figure 2. You must accept the license agreement to continue with the install. (Click to enlarge.)

Figure 3's screen lets you override the default install location and the JDK used by NetBeans -- remember that it must be at least Java SE 6u7!

Select a JDK by specifying its home directory.
Figure 3. Select a JDK by specifying its home directory. (Click to enlarge.)

The summary screen shown in Figure 4 gives you a chance to change your mind regarding the install location before committing to the install.

Begin the install from the summary screen.
Figure 4. Begin the install from the summary screen. (Click to enlarge.)

When installation finishes, the installer gives you a chance to register your copy of NetBeans, as shown in Figure 5.

Uncheck the checkbox if you choose not to register your copy of NetBeans
Figure 5. Uncheck the checkbox if you choose not to register your copy of NetBeans.

Getting Project Nile

Project Nile provides plugin-based tools for converting Adobe Illustrator and Photoshop graphics to a format that JavaFX-based RIAs can access. Just like NetBeans IDE 6.1 with JavaFX, you can download Project Nile from Sun's JavaFX Technology Downloads page. You can also learn about Project Nile's system requirements and study its release notes there.

I installed the Windows version of Project Nile (it's also available for Mac OS X, Intel only) by downloading and running the project_nile-1_0-pre1-windows-i586.exe installer. This program presents screens similar to those shown in Figures 1 through 5, and selects C:\Program Files\Sun\Project Nile as the default install location. We'll explore this location's contents later in this article.

Using NetBeans IDE 6.1 with JavaFX

After installing NetBeans, start up the IDE (double-click its desktop shortcut on a Windows platform) and wait for its workspace to appear. Figure 6 reveals the NetBeans workspace divided into several windows, which this IDE's help system describes -- select Help Contents from the Help menu to access the help system.

The workspace makes it easy to develop JavaFX software.
Figure 6. The workspace makes it easy to develop JavaFX software. (Click to enlarge.)

At this point, there are no JavaFX projects to access, so we'll have to create one. We can either create a skeletal project, or create a project based on one of the JavaFX samples bundled with NetBeans. In either case, select New Project from the File menu (or click the second button from the left on the toolbar). This activates the New Project wizard (see Figure 7).

NetBeans defaults to the JavaFX project category, which lets you only create JavaFX Script applications.
Figure 7. NetBeans defaults to the JavaFX project category, which lets you only create JavaFX Script applications. (Click to enlarge.)

Hello World! A JavaFX script

Because we'll create a skeletal project, make sure that the project category is set to JavaFX before clicking the Next button. In response, the wizard requests the JavaFX project's name and location on its next screen (see Figure 8). When lowercased, this name also serves as the project's default package name (each project is given its own package and directory).

On a Windows platform, NetBeans defaults to storing its projects in separate directories under the 'C:\Documents and Settings\user name\My Documents\NetBeansProjects' folder.
Figure 8. On a Windows platform, NetBeans defaults to storing its projects in separate directories under the C:\Documents and Settings\ user name \My Documents\NetBeansProjects folder. (Click to enlarge.)

Figure 8 reveals HelloJavaFX as the project's name -- it's traditional to introduce a new technology via some sort of "hello world" program (or script in JavaFX-speak). It also reveals Main as the default name (.fx is the extension) of the script's file, and other default settings. After clicking the Finish button, you'll discover the skeletal source code shown in Figure 9.

The workspace assigns a separate tab to each source file.
Figure 9. The workspace assigns a separate tab to each source file. (Click to enlarge.)

Although the "hello world" script could output its Hello, JavaFX! message to the standard output (which is the workspace's Output window), this option is boring and doesn't reveal what JavaFX is all about. So, instead we'll have this script rotate the message while changing its opacity over a gradient-rendered background. Before we look at the script's output, examine Listing 1.

Listing 1. Main.fx

/*
 * 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"

If you're new to JavaFX Script, much of Listing 1 will probably look alien to you. This is true even though you should already be familiar with import statements, the single-line comment style, class declaration via the class keyword, and brace character ({}) delimiters -- all being borrowed from Java. What you see in Listing 1 will make more sense after you've explored the JavaFX Script language and associated APIs in the second and third parts of this series.

Rather than trying to figure out how the script works, let's see what it's like to build and run it in NetBeans 6.1 with JavaFX. After replacing Main.fx's skeletal contents (see Figure 9) with Listing 1, compile the source code and run the resulting Java class file by clicking the toolbar's Run Main Project button (the green triangle button). If all goes well, you should see a window that presents identical content to that shown in Figure 10.

The script continuously rotates the message around the center of the window's content area, while also transitioning the message's opacity from invisible to fully visible and vice-versa.
Figure 10. The script continuously rotates the message around the center of the window's content area, while also transitioning the message's opacity from invisible to fully visible and vice-versa.

More sample applications

NetBeans 6.1 includes a variety of samples that primarily demonstrate JavaFX's support for animation, basic geometry, color, images, and mouse interaction. You can even have fun with the somewhat buggy Bounce sample, which presents a simple version of the popular Arkanoid game. If you haven't yet tried out these samples, Figure 11 gives you a glimpse of what's available.

Bounce, Color Wheel, Linear Gradient, Smoke Particle System, and Transparency sample GUIs.
Figure 11. Bounce, Color Wheel, Linear Gradient, Smoke Particle System, and Transparency sample GUIs. (Click to enlarge.)

Before you can play with a sample, you'll have to create a new project. Complete the following steps to accomplish this task:

  1. Activate the New Project wizard.
  2. From the Choose Project section of the wizard's starting screen, expand the Samples category node, followed by the JavaFX and Best Practices nodes in the Categories tree.
  3. Select a directory under Best Practices and highlight the desired project in the Projects list.
  4. Click Next and then Finish.

After you click Finish, the sample becomes the new main project. Click the green triangle button to compile and run its code.

To view the sample project's source code, expand its Source Packages hierarchy on the Projects tab of the workspace's Projects/Files/Services window, and double-click the appropriate .fx filename that's located at the bottom of this hierarchy. A new tab will be created that presents this file's source code.

More about the NetBeans IDE with JavaFX

To find out more about the NetBeans IDE, check out the NetBeans IDE Java Quick Start Tutorial page at NetBeans.org. Additionally, you might want to keep in mind that the File menu contains a Set Main Project menu item that lets you easily choose which of the various open projects is the main project -- clicking the green triangle button (or pressing F6) compiles and runs the main project. Also, you can close the main project by selecting File's Close "project name" menu item, and open any project not already open by selecting File's Open Project menu item. If you want to learn more about working with JavaFX in the NetBeans IDE (such as how to debug a malfunctioning script), first select Help Contents from the Help menu to access the IDE's help system, and then select one of the topics under JavaFX Applications (such as Debugging JavaFX Applications) on the help window's Contents tab.

Using Project Nile

Project Nile provides an Adobe Illustrator Creative Suite 3 (CS3) plugin that lets UI designers export Illustrator-based visual elements to FXD files (files based on the JavaFX Data, or FXD, format, and having the .fxd extension), or FX files (JavaFX Script source files, with the .fx extension). It also provides an Adobe Photoshop CS3 plugin for exporting Photoshop-based elements to the same kinds of files.

The Illustrator plugin (for Windows) consists of AI2JavaFX.aip and AI2JavaFX.jar files, which are located in the illustrator subdirectory of the Project Nile install location. Similarly, the Photoshop plugin (for Windows) consists of a PS2JavaFX.8be file that's located in the photoshop subdirectory.

If Illustrator and Photoshop are installed prior to installing Project Nile, the Project Nile installer will automatically copy these plugin files to the appropriate Illustrator and Photoshop plugin directories. If you install either or both of these programs after installing Project Nile, you'll need to manually copy the plugin files to the appropriate plugin directories (see the Resources section for instructions).

Once the plugins are installed, you'll discover an option to save the Illustrator-based or Photoshop-based layered graphics to FXD or FX files the next time you start Illustrator or Photoshop. Because I don't have Illustrator or Photoshop installed, and because I'm not very good at using either program, I won't say more about the Project Nile plugins.

SVG Converter and JavaFX Graphics Viewer

Project Nile provides GUI and command-line versions of its SVG Converter (in the svg subdirectory) and JavaFX Graphics Viewer (in the viewer subdirectory) tools. The former tool lets you convert Scalable Vector Graphics-based visual content to FXD and FX files; the latter tool lets you view the graphics described by these files.

Let's play with SVG Converter and JavaFX Graphics Viewer. For starters, I've used a program called Inkscape (see Resources) to create a simple vector graphics drawing of some basic shapes, and then save the result to an XML-based SVG file called shapes.svg. Listing 2 presents this file's content, which I've slightly modified for display purposes.

Listing 2. shapes.svg

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
   xmlns:dc="http://purl.org/dc/elements/1.1/"
   xmlns:cc="http://creativecommons.org/ns#"
   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
   xmlns:svg="http://www.w3.org/2000/svg"
   xmlns="http://www.w3.org/2000/svg"
   xmlns:xlink="http://www.w3.org/1999/xlink"
   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
   width="744.09448819"
   height="1052.3622047"
   id="svg2"
   sodipodi:version="0.32"
   inkscape:version="0.46"
   sodipodi:docname="shapes.svg"
   inkscape:output_extension="org.inkscape.output.svg.inkscape">
  <defs
     id="defs4">
    <inkscape:perspective
       sodipodi:type="inkscape:persp3d"
       inkscape:vp_x="0 : 526.18109 : 1"
       inkscape:vp_y="0 : 1000 : 0"
       inkscape:vp_z="744.09448 : 526.18109 : 1"
       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
       id="perspective3421" />
    <linearGradient
       id="linearGradient3217">
      <stop
         id="stop3219"
         offset="0"
         style="stop-color:#803200;stop-opacity:1;" />
      <stop
         style="stop-color:#401900;stop-opacity:0.49803922;"
         offset="0.5"
         id="stop3221" />
      <stop
         id="stop3223"
         offset="1"
         style="stop-color:#000000;stop-opacity:0;" />
    </linearGradient>
    <linearGradient
       inkscape:collect="always"
       xlink:href="#linearGradient3217"
       id="linearGradient3213"
       x1="122.85714"
       y1="-22.745518"
       x2="541.95331"
       y2="-22.745518"
       gradientUnits="userSpaceOnUse"
       spreadMethod="reflect" />
    <filter
       inkscape:collect="always"
       id="filter3389"
       x="-0.24714218"
       width="1.4942844"
       y="-0.066440908"
       height="1.1328818">
      <feGaussianBlur
         inkscape:collect="always"
         stdDeviation="43.259785"
         id="feGaussianBlur3391" />
    </filter>
  </defs>
  <sodipodi:namedview
     id="base"
     pagecolor="#ffffff"
     bordercolor="#666666"
     borderopacity="1.0"
     gridtolerance="10000"
     guidetolerance="10"
     objecttolerance="10"
     inkscape:pageopacity="0.0"
     inkscape:pageshadow="2"
     inkscape:zoom="0.35"
     inkscape:cx="375"
     inkscape:cy="522.94575"
     inkscape:document-units="px"
     inkscape:current-layer="layer1"
     showgrid="false"
     inkscape:window-width="1024"
     inkscape:window-height="768"
     inkscape:window-x="0"
     inkscape:window-y="0" />
  <metadata
     id="metadata7">
    <rdf:RDF>
      <cc:Work
         rdf:about="">
        <dc:format>image/svg+xml</dc:format>
        <dc:type
           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
      </cc:Work>
    </rdf:RDF>
  </metadata>
  <g
     inkscape:label="Layer 1"
     inkscape:groupmode="layer"
     id="layer1">
    <path
       sodipodi:type="arc"
       style="opacity:0.77490779;fill:#ff0000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:4, 1;stroke-dashoffset:0"
       id="path3411"
       sodipodi:cx="338.57144"
       sodipodi:cy="316.64789"
       sodipodi:rx="167.14285"
       sodipodi:ry="167.14285"
       d="M 505.71429,316.64789 A 167.14285,167.14285 0 1 1 171.42859,316.64789 A 167.14285,167.14285 0 1 1 505.71429,316.64789 z" />
    <path
       sodipodi:type="arc"
       style="opacity:0.77490778999999999;fill:#0000ff;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:4, 1;stroke-dashoffset:0"
       id="path3413"
       sodipodi:cx="455.71429"
       sodipodi:cy="505.21933"
       sodipodi:rx="110"
       sodipodi:ry="110"
       d="M 565.71429,505.21933 A 110,110 0 1 1 345.71429,505.21933 A 110,110 0 1 1 565.71429,505.21933 z" />
    <rect
       style="fill:#ffff00;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
       id="rect3415"
       width="268.57144"
       height="268.57144"
       x="62.857143"
       y="680.93359" />
    <rect
       style="fill:#008000;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
       id="rect3417"
       width="242.85715"
       height="242.85715"
       x="262.85715"
       y="786.64789" />
    <path
       sodipodi:type="star"
       style="opacity:0.77490778999999999;fill:#ff00ff;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:4, 1;stroke-dashoffset:0;stroke:#ff00ff"
       id="path3419"
       sodipodi:sides="5"
       sodipodi:cx="65.714286"
       sodipodi:cy="66.647897"
       sodipodi:r1="69.575975"
       sodipodi:r2="34.787988"
       sodipodi:arg1="0.33473684"
       sodipodi:arg2="0.96305537"
       inkscape:flatsided="false"
       inkscape:rounded="0"
       inkscape:randomized="0"
       d="M 131.42857,89.505038 L 85.578727,95.206748 L 64.282683,136.20914 L 44.691654,94.365272 L -0.88477702,86.781966 L 32.857145,55.219325 L 25.985405,9.5301921 L 66.430089,31.867274 L 107.75955,11.213143 L 99.013819,56.580861 L 131.42857,89.505038 z" />
    <g
       sodipodi:type="inkscape:box3d"
       style="opacity:0.77490779;fill:#ff00ff;stroke:#ff00ff;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:4, 1;stroke-dashoffset:0"
       id="g3423"
       inkscape:perspectiveID="#perspective3421"
       inkscape:corner0="-0.34564555 : 0.40317749 : 0 : 1"
       inkscape:corner7="-0.35853925 : 0.39135817 : 1.5147279 : 1">
      <path
         sodipodi:type="inkscape:box3dside"
         id="path3435"
         style="fill:#e9e9ff;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
         inkscape:box3dsidetype="11"
         d="M 691.14384,421.1672 L 695.27678,420.53923 L 695.27678,426.02081 L 691.14384,426.61619 L 691.14384,421.1672 z" />
      <path
         sodipodi:type="inkscape:box3dside"
         id="path3425"
         style="fill:#353564;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
         inkscape:box3dsidetype="6"
         d="M 568.57142,178.0765 L 568.57142,196.13906 L 691.14384,426.61619 L 691.14384,421.1672 L 568.57142,178.0765 z" />
      <path
         sodipodi:type="inkscape:box3dside"
         id="path3427"
         style="fill:#4d4d9f;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
         inkscape:box3dsidetype="5"
         d="M 568.57142,178.0765 L 580.00001,171.07941 L 695.27678,420.53923 L 691.14384,421.1672 L 568.57142,178.0765 z" />
      <path
         sodipodi:type="inkscape:box3dside"
         id="path3433"
         style="fill:#afafde;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
         inkscape:box3dsidetype="13"
         d="M 568.57142,196.13906 L 580.00001,189.50505 L 695.27678,426.02081 L 691.14384,426.61619 L 568.57142,196.13906 z" />
      <path
         sodipodi:type="inkscape:box3dside"
         id="path3431"
         style="fill:#d7d7ff;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
         inkscape:box3dsidetype="14"
         d="M 580.00001,171.07941 L 580.00001,189.50505 L 695.27678,426.02081 L 695.27678,420.53923 L 580.00001,171.07941 z" />
      <path
         sodipodi:type="inkscape:box3dside"
         id="path3429"
         style="fill:#8686bf;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
         inkscape:box3dsidetype="3"
         d="M 568.57142,178.0765 L 580.00001,171.07941 L 580.00001,189.50505 L 568.57142,196.13906 L 568.57142,178.0765 z" />
    </g>
  </g>
</svg>

Suppose that we want to access this SVG content from a JavaFX script. Before we can do this, we need to convert the content to something that a script would understand. The SVG Converter tool comes to the rescue, allowing us to convert shapes.svg to an equivalent shapes.fx file. Figure 12 reveals this tool's GUI.

If you don't specify a package name, it defaults to result.
Figure 12. If you don't specify a package name, it defaults to result . (Click to enlarge.)

The resulting shapes.fx source file specifies a class that describes a custom node (a developer-defined component that can be plugged into a user interface). Later in this series, you'll learn about custom nodes and how to create them. For now, check out Listing 3 to see the JavaFX Script equivalent of the previous XML-based SVG content. (You'll also learn how to interact with this script later in this series.)

Listing 3. shapes.fx (slightly reformatted for display)

/*
 * Generated by JavaFX svg2fx tool.
 * Created on Wed Sep 10 20:56:16 CDT 2008.
 */
package result;

import javafx.scene.*;
import javafx.scene.geometry.*;
import javafx.scene.image.*;
import javafx.scene.paint.*;
import javafx.scene.text.*;
import javafx.scene.effect.*;
import javafx.scene.transform.*;

public class shapes extends CustomNode {
    public attribute _root: Group;
    public attribute g3423: Group;
    public attribute layer1: Group;
    public attribute linearGradient3213: LinearGradient;
    public attribute linearGradient3217: LinearGradient;
    public attribute path3411: SVGPath;
    public attribute path3413: SVGPath;
    public attribute path3419: SVGPath;
    public attribute path3425: SVGPath;
    public attribute path3427: SVGPath;
    public attribute path3429: SVGPath;
    public attribute path3431: SVGPath;
    public attribute path3433: SVGPath;
    public attribute path3435: SVGPath;
    public attribute rect3415: Rectangle;
    public attribute rect3417: Rectangle;
    public attribute stop3219: Stop;
    public attribute stop3221: Stop;
    public attribute stop3223: Stop;

    protected function initializeCustomNode() : Void {

        _root = Group {
                content: [
                    defs4=metadata7=layer1=Group {
                            content: [
                            path3411=SVGPath {
                                    content: "M 505.71429,316.64789
                                              A 167.14285,167.14285 0 1 1 171.42859,316.64789
                                              A 167.14285,167.14285 0 1 1 505.71429,316.64789 z"
                                    fill: Color.rgb(0xff, 0x00, 0x00, 1.0)
                                    id: "path3411"
                                    opacity: 0.77490779
                                    strokeWidth: 1.0
                                },
                            path3413=SVGPath {
                                    content: "M 565.71429,505.21933 A 110,110 0 1 1 345.71429,505.21933
                                              A 110,110 0 1
                                              1 565.71429,505.21933 z"
                                    fill: Color.rgb(0x00, 0x00, 0xff, 1.0)
                                    id: "path3413"
                                    opacity: 0.77490778999999999
                                    strokeWidth: 1.0
                                },
                            rect3415=Rectangle {
                                    fill: Color.rgb(0xff, 0xff, 0x00, 1.0)
                                    fillRule: FillRule.EVEN_ODD
                                    height: 268.57144
                                    id: "rect3415"
                                    stroke: Color.rgb(0x00, 0x00, 0x00, 1.0)
                                    strokeLineCap: StrokeLineCap.BUTT
                                    strokeLineJoin: StrokeLineJoin.MITER
                                    strokeWidth: 1.0
                                    width: 268.57144
                                    x: 62.857143
                                    y: 680.9336
                                },
                            rect3417=Rectangle {
                                    fill: Color.rgb(0x00, 0x80, 0x00, 1.0)
                                    fillRule: FillRule.EVEN_ODD
                                    height: 242.85716
                                    id: "rect3417"
                                    stroke: Color.rgb(0x00, 0x00, 0x00, 1.0)
                                    strokeLineCap: StrokeLineCap.BUTT
                                    strokeLineJoin: StrokeLineJoin.MITER
                                    strokeWidth: 1.0
                                    width: 242.85716
                                    x: 262.85715
                                    y: 786.64795
                                },
                            path3419=SVGPath {
                                    content: "M 131.42857,89.505038
                                              L 85.578727,95.206748
                                              L 64.282683,136.20914
                                              L 44.691654,94.365272
                                              L -0.88477702,86.781966
                                              L 32.857145,55.219325
                                              L 25.985405,9.5301921
                                              L 66.430089,31.867274
                                              L 107.75955,11.213143
                                              L 99.013819,56.580861
                                              L 131.42857,89.505038 z"
                                    fill: Color.rgb(0xff, 0x00, 0xff, 1.0)
                                    id: "path3419"
                                    opacity: 0.77490778999999999
                                    stroke: Color.rgb(0xff, 0x00, 0xff, 1.0)
                                    strokeWidth: 1.0
                                },
                            g3423=Group {
                                    content: [
                                    path3435=SVGPath {
                                            content: "M 691.14384,421.1672
                                                      L 695.27678,420.53923
                                                      L 695.27678,426.02081
                                                      L 691.14384,426.61619
                                                      L 691.14384,421.1672 z"
                                            fill: Color.rgb(0xe9, 0xe9, 0xff, 1.0)
                                            fillRule: FillRule.EVEN_ODD
                                            id: "path3435"
                                            opacity: 0.77490779
                                            strokeLineCap: StrokeLineCap.BUTT
                                            strokeLineJoin: StrokeLineJoin.ROUND
                                            strokeWidth: 1.0
                                        },
                                    path3425=SVGPath {
                                            content: "M 568.57142,178.0765
                                                      L 568.57142,196.13906
                                                      L 691.14384,426.61619
                                                      L 691.14384,421.1672
                                                      L 568.57142,178.0765 z"
                                            fill: Color.rgb(0x35, 0x35, 0x64, 1.0)
                                            fillRule: FillRule.EVEN_ODD
                                            id: "path3425"
                                            opacity: 0.77490779
                                            strokeLineCap: StrokeLineCap.BUTT
                                            strokeLineJoin: StrokeLineJoin.ROUND
                                            strokeWidth: 1.0
                                        },
                                    path3427=SVGPath {
                                            content: "M 568.57142,178.0765
                                                      L 580.00001,171.07941
                                                      L 695.27678,420.53923
                                                      L 691.14384,421.1672
                                                      L 568.57142,178.0765 z"
                                            fill: Color.rgb(0x4d, 0x4d, 0x9f, 1.0)
                                            fillRule: FillRule.EVEN_ODD
                                            id: "path3427"
                                            opacity: 0.77490779
                                            strokeLineCap: StrokeLineCap.BUTT
                                            strokeLineJoin: StrokeLineJoin.ROUND
                                            strokeWidth: 1.0
                                        },
                                    path3433=SVGPath {
                                            content: "M 568.57142,196.13906
                                                      L 580.00001,189.50505
                                                      L 695.27678,426.02081
                                                      L 691.14384,426.61619
                                                      L 568.57142,196.13906 z"
                                            fill: Color.rgb(0xaf, 0xaf, 0xde, 1.0)
                                            fillRule: FillRule.EVEN_ODD
                                            id: "path3433"
                                            opacity: 0.77490779
                                            strokeLineCap: StrokeLineCap.BUTT
                                            strokeLineJoin: StrokeLineJoin.ROUND
                                            strokeWidth: 1.0
                                        },
                                    path3431=SVGPath {
                                            content: "M 580.00001,171.07941
                                                      L 580.00001,189.50505
                                                      L 695.27678,426.02081
                                                      L 695.27678,420.53923
                                                      L 580.00001,171.07941 z"
                                            fill: Color.rgb(0xd7, 0xd7, 0xff, 1.0)
                                            fillRule: FillRule.EVEN_ODD
                                            id: "path3431"
                                            opacity: 0.77490779
                                            strokeLineCap: StrokeLineCap.BUTT
                                            strokeLineJoin: StrokeLineJoin.ROUND
                                            strokeWidth: 1.0
                                        },
                                    path3429=SVGPath {
                                            content: "M 568.57142,178.0765
                                                      L 580.00001,171.07941
                                                      L 580.00001,189.50505
                                                      L 568.57142,196.13906
                                                      L 568.57142,178.0765 z"
                                            fill: Color.rgb(0x86, 0x86, 0xbf, 1.0)
                                            fillRule: FillRule.EVEN_ODD
                                            id: "path3429"
                                            opacity: 0.77490779
                                            strokeLineCap: StrokeLineCap.BUTT
                                            strokeLineJoin: StrokeLineJoin.ROUND
                                            strokeWidth: 1.0
                                        }
                                    ]
                                }
                            ]
                        }
                ]
            }
    }

    protected function create() : Node {
        if (_root == null) {
            initializeCustomNode();
        }
        return _root;
    }
}
shapes{}

A rendering challenge, and a workaround

To view the original SVG graphic described by shapes.fx, we could introduce a script that places this custom node in a window's content area. Because you're just learning about JavaFX Script and JavaFX APIs, however, it seems more appropriate to use the JavaFX Graphics Viewer for this task. Unfortunately, the tool displays the following error information when asked to render shapes.fx:

file:/c:/prj/jijfx/part1/code/shapes/shapes.fx from StringInputBuffer:41: cannot find symbol
symbol  : variable defs4
location: class result.shapes
compiler.err.cant.resolve.location
file:/c:/prj/jijfx/part1/code/shapes/shapes.fx from StringInputBuffer:41: cannot find symbol
symbol  : variable metadata7
location: class result.shapes
compiler.err.cant.resolve.location
file:/c:/prj/jijfx/part1/code/shapes/shapes.fx from StringInputBuffer:59: cannot find symbol
symbol  : variable fillRule
location: class javafx.scene.geometry.Rectangle
compiler.err.cant.resolve.location
file:/c:/prj/jijfx/part1/code/shapes/shapes.fx from StringInputBuffer:72: cannot find symbol
symbol  : variable fillRule
location: class javafx.scene.geometry.Rectangle
compiler.err.cant.resolve.location
file:/c:/prj/jijfx/part1/code/shapes/shapes.fx from StringInputBuffer:40: incompatible types
found   : Integer
required: javafx.scene.Node
compiler.err.prob.found.req
java.lang.reflect.InvocationTargetException (null)

We're not beaten yet! The solution is to use Converter to convert shapes.svg to shapes.fxd and shapesUI.fx, instead of shapes.fx. The shapes.fxd file can then be identified to the JavaFX Graphics Viewer tool, which will display the graphic. To perform this conversion, specify the .fxd extension instead of .fx when entering the destination filename.

Listing 4 presents shapes.fxd, which provides the JavaFX Script graphic definition equivalent of shapes.svg.

Listing 4. shapes.fxd (slightly reformatted for display)

/*
 * Generated by JavaFX svg2fx tool.
 * Created on Wed Sep 10 20:56:16 CDT 2008.
 */
//@version 0.1

import javafx.scene.*;
import javafx.scene.geometry.*;
import javafx.scene.image.*;
import javafx.scene.paint.*;
import javafx.scene.text.*;
import javafx.scene.effect.*;
import javafx.scene.transform.*;

Group {
    content: [
            Group {
                content: [
                    SVGPath {
                        content: "M 505.71429,316.64789
                                  A 167.14285,167.14285 0 1 1 171.42859,316.64789
                                  A 167.14285,167.14285 0 1 1 505.71429,316.64789 z"
                        fill: Color.rgb(0xff, 0x00, 0x00, 1.0)
                        id: "path3411"
                        opacity: 0.77490779
                        strokeWidth: 1.0
                    },
                    SVGPath {
                        content: "M 565.71429,505.21933
                                  A 110,110 0 1 1 345.71429,505.21933
                                  A 110,110 0 1 1 565.71429,505.21933 z"
                        fill: Color.rgb(0x00, 0x00, 0xff, 1.0)
                        id: "path3413"
                        opacity: 0.77490778999999999
                        strokeWidth: 1.0
                    },
                    Rectangle {
                        fill: Color.rgb(0xff, 0xff, 0x00, 1.0)
                        fillRule: FillRule.EVEN_ODD
                        height: 268.57144
                        id: "rect3415"
                        stroke: Color.rgb(0x00, 0x00, 0x00, 1.0)
                        strokeLineCap: StrokeLineCap.BUTT
                        strokeLineJoin: StrokeLineJoin.MITER
                        strokeWidth: 1.0
                        width: 268.57144
                        x: 62.857143
                        y: 680.9336
                    },
                    Rectangle {
                        fill: Color.rgb(0x00, 0x80, 0x00, 1.0)
                        fillRule: FillRule.EVEN_ODD
                        height: 242.85716
                        id: "rect3417"
                        stroke: Color.rgb(0x00, 0x00, 0x00, 1.0)
                        strokeLineCap: StrokeLineCap.BUTT
                        strokeLineJoin: StrokeLineJoin.MITER
                        strokeWidth: 1.0
                        width: 242.85716
                        x: 262.85715
                        y: 786.64795
                    },
                    SVGPath {
                        content: "M 131.42857,89.505038
                                  L 85.578727,95.206748
                                  L 64.282683,136.20914
                                  L 44.691654,94.365272
                                  L -0.88477702,86.781966
                                  L 32.857145,55.219325
                                  L 25.985405,9.5301921
                                  L 66.430089,31.867274
                                  L 107.75955,11.213143
                                  L 99.013819,56.580861
                                  L 131.42857,89.505038 z"
                        fill: Color.rgb(0xff, 0x00, 0xff, 1.0)
                        id: "path3419"
                        opacity: 0.77490778999999999
                        stroke: Color.rgb(0xff, 0x00, 0xff, 1.0)
                        strokeWidth: 1.0
                    },
                    Group {
                        content: [
                            SVGPath {
                                content: "M 691.14384,421.1672
                                          L 695.27678,420.53923
                                          L 695.27678,426.02081
                                          L 691.14384,426.61619
                                          L 691.14384,421.1672 z"
                                fill: Color.rgb(0xe9, 0xe9, 0xff, 1.0)
                                fillRule: FillRule.EVEN_ODD
                                id: "path3435"
                                opacity: 0.77490779
                                strokeLineCap: StrokeLineCap.BUTT
                                strokeLineJoin: StrokeLineJoin.ROUND
                                strokeWidth: 1.0
                            },
                            SVGPath {
                                content: "M 568.57142,178.0765
                                          L 568.57142,196.13906
                                          L 691.14384,426.61619
                                          L 691.14384,421.1672
                                          L 568.57142,178.0765 z"
                                fill: Color.rgb(0x35, 0x35, 0x64, 1.0)
                                fillRule: FillRule.EVEN_ODD
                                id: "path3425"
                                opacity: 0.77490779
                                strokeLineCap: StrokeLineCap.BUTT
                                strokeLineJoin: StrokeLineJoin.ROUND
                                strokeWidth: 1.0
                            },
                            SVGPath {
                                content: "M 568.57142,178.0765
                                          L 580.00001,171.07941
                                          L 695.27678,420.53923
                                          L 691.14384,421.1672
                                          L 568.57142,178.0765 z"
                                fill: Color.rgb(0x4d, 0x4d, 0x9f, 1.0)
                                fillRule: FillRule.EVEN_ODD
                                id: "path3427"
                                opacity: 0.77490779
                                strokeLineCap: StrokeLineCap.BUTT
                                strokeLineJoin: StrokeLineJoin.ROUND
                                strokeWidth: 1.0
                            },
                            SVGPath {
                                content: "M 568.57142,196.13906
                                          L 580.00001,189.50505
                                          L 695.27678,426.02081
                                          L 691.14384,426.61619
                                          L 568.57142,196.13906 z"
                                fill: Color.rgb(0xaf, 0xaf, 0xde, 1.0)
                                fillRule: FillRule.EVEN_ODD
                                id: "path3433"
                                opacity: 0.77490779
                                strokeLineCap: StrokeLineCap.BUTT
                                strokeLineJoin: StrokeLineJoin.ROUND
                                strokeWidth: 1.0
                            },
                            SVGPath {
                                content: "M 580.00001,171.07941
                                          L 580.00001,189.50505
                                          L 695.27678,426.02081
                                          L 695.27678,420.53923
                                          L 580.00001,171.07941 z"
                                fill: Color.rgb(0xd7, 0xd7, 0xff, 1.0)
                                fillRule: FillRule.EVEN_ODD
                                id: "path3431"
                                opacity: 0.77490779
                                strokeLineCap: StrokeLineCap.BUTT
                                strokeLineJoin: StrokeLineJoin.ROUND
                                strokeWidth: 1.0
                            },
                            SVGPath {
                                content: "M 568.57142,178.0765
                                          L 580.00001,171.07941
                                          L 580.00001,189.50505
                                          L 568.57142,196.13906
                                          L 568.57142,178.0765 z"
                                fill: Color.rgb(0x86, 0x86, 0xbf, 1.0)
                                fillRule: FillRule.EVEN_ODD
                                id: "path3429"
                                opacity: 0.77490779
                                strokeLineCap: StrokeLineCap.BUTT
                                strokeLineJoin: StrokeLineJoin.ROUND
                                strokeWidth: 1.0
                            }
                        ]
                    }
                ]
            }
    ]
}

Listing 5 presents the companion shapesUI.fx, which provides script access to the FXD file's graphic.

Listing 5. shapesUI.fx (slightly reformatted for display)

/*
 * Generated by JavaFX svg2fx tool.
 * UIStub for the file shapes.fxd.
 * Created on Wed Sep 10 20:56:16 CDT 2008.
 */
package result;

import java.lang.Object;
import java.lang.System;
import java.lang.RuntimeException;

import javafx.scene.*;
import javafx.scene.geometry.*;
import javafx.scene.image.*;
import javafx.scene.paint.*;
import javafx.scene.text.*;
import javafx.scene.effect.*;
import javafx.scene.transform.*;

import javafx.tools.fxd.loader.*;
import com.sun.javafx.tools.fxd.*;

public class shapesUI extends UiStub {
    public attribute g3423: Group;
    public attribute layer1: Group;
    public attribute linearGradient3213: LinearGradient;
    public attribute linearGradient3217: LinearGradient;
    public attribute path3411: SVGPath;
    public attribute path3413: SVGPath;
    public attribute path3419: SVGPath;
    public attribute path3425: SVGPath;
    public attribute path3427: SVGPath;
    public attribute path3429: SVGPath;
    public attribute path3431: SVGPath;
    public attribute path3433: SVGPath;
    public attribute path3435: SVGPath;
    public attribute rect3415: Rectangle;
    public attribute rect3417: Rectangle;
    public attribute stop3219: Stop;
    public attribute stop3221: Stop;
    public attribute stop3223: Stop;


    init {
        url = getURL();
    }

    protected function update() {
                g3423=getGroup("g3423");
        layer1=getGroup("layer1");
        linearGradient3213=getNode("linearGradient3213") as LinearGradient;
        linearGradient3217=getNode("linearGradient3217") as LinearGradient;
        path3411=getNode("path3411") as SVGPath;
        path3413=getNode("path3413") as SVGPath;
        path3419=getNode("path3419") as SVGPath;
        path3425=getNode("path3425") as SVGPath;
        path3427=getNode("path3427") as SVGPath;
        path3429=getNode("path3429") as SVGPath;
        path3431=getNode("path3431") as SVGPath;
        path3433=getNode("path3433") as SVGPath;
        path3435=getNode("path3435") as SVGPath;
        rect3415=getNode("rect3415") as Rectangle;
        rect3417=getNode("rect3417") as Rectangle;
        stop3219=getNode("stop3219") as Stop;
        stop3221=getNode("stop3221") as Stop;
        stop3223=getNode("stop3223") as Stop;

    }

    public static function getURL() : String {
        return "{__DIR__}shapes.fxd";
    }
}

Listing 5 refers to javafx.tools.fxd.loader and com.sun.javafx.tools.fxd packages, whose classes are stored in a JAR file named javafx-fxd-1.0-pre1.jar (located in the Project Nile install location's libraries subdirectory). This JAR file must be included in the classpath for scripts that access FXD files. This JAR file isn't required for scripts that access custom node-based FX files.

In addition to being viewable via the JavaFX Graphics Viewer tool, an FXD file contains three advantages over the custom node-based FX file:

  • Unlike an FX file, which must be compiled to Java bytecodes, an FXD file is never compiled, but is loaded as a resource file (it only contains graphics definitions). This gives FXD files a speed advantage.
  • Java Virtual Machine limitations can prevent FX files with large graphics from being compiled. This isn't an issue for FXD files.
  • Separating an FXD file from JavaFX Script code allows the designer and developer to maintain separate ownership. The designer can keep ownership of the FXD file, whereas the developer can keep ownership of the FX file that's also generated with the FXD file.

To view the shapes.fxd graphic, start the Java Graphics Viewer tool, specifying the location and name of this file. This graphic is shown in Figure 13.

A small part of the original graphic (at the bottom and on the right) is missing, possibly due to SVG Converter or Java Graphics Viewer.
Figure 13. A small part of the original graphic (at the bottom and on the right) is missing, possibly due to SVG Converter or Java Graphics Viewer. (Click to enlarge.)

In conclusion

Sun's JavaFX Preview SDK offers a good starting point for simplifying the development and design of RIAs based on JavaFX. As a developer, you primarily benefit from NetBeans IDE 6.1 with JavaFX. If you also dabble in design (or have a stronger interest), you'll appreciate Project Nile's Adobe Illustrator and Photoshop plugins. In this article you've had a look at what JavaFX can do, and also become familiar with using NetBeans 6.1 with JavaFX. That knowledge will come in handy when we explore the JavaFX Script language in Part 2.

Jeff Friesen is a freelance software developer and educator who specializes in Java technology. Jeff's book, Beginning Java SE 6 Platform: From Novice to Professional was released in October 2007 by Apress. Discover all of his published Java articles and more at javajeff.mb.ca.

Learn more about this topic

More from JavaWorld

Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more