« June 2008 | Main | August 2008 »

July 2008

July 30, 2008

Progress Indicator: Creating a JavaFX Custom Node and Binding to a Model

In the Rolling Your Own JavaFX "Custom Nodes": A Graphical Menu Example post, I began showing you how to create your own UI controls in JavaFX.  In that post we defined the MenuNode and ButtonNode custom nodes so that you can easily create menus that consist of buttons that fade-in and expand when the mouse rolls over them.

Then, in the Getting Decked: Another JavaFX Custom Node post, we defined a DeckNode  that stores a set of Node instances and displays one of these nodes at a time.  It is used, for example, to show the node that pertains to a given menu button.

The posts mentioned above are part of a series in the JFX Custom Nodes category in which a graphics designer (Mark Dingman of Malden Labs) and I are collaborating on an imaginary "Sound Beans" application.  The objectives of building this application are to demonstrate how to create custom nodes, and to provide a case study in how a graphics designer and an application developer can work together effectively in developing JavaFX applications.

In today's post, we're going to do two things:

  1. Define a ProgressNode control that may be use to show the progress of an operation.
  2. Introduce a model class into the Sound Beans application.  As I've said before, the "way of JavaFX" is to bind the UI to a model, and this Sound Beans application has gone long enough without one.

Here's the mock-up that Mark gave me for the Burn CD page:

Burning_2

Based upon this image, I decided to create a "progress bar" control that consists of JavaFX graphical nodes (e.g. Rectangle, Text).  For this page, there are no image assets needed from Mark.

I'll show you the code in a bit, but first take a look at a screenshot of the Sound Beans application after the Burn button has been clicked:

Progressnodeexample_3

As you can see, I added a slider control for the purpose of simulating the progress of the burn.  Give it a whirl by clicking on this Java Web Start link, keeping in mind that you'll need at least JRE 6.  Also, installing Java SE 6 update 10 will give you faster deployment time.

Webstartsmall2

Here's the code for the ProgressNode custom node, in a file named ProgressNode.fx:

/*
*  ProgressNode.fx -
*  A custom node that functions as a progress bar
*  TODO: Add the ability to have an "infinite progress" look as well
*
*  Developed 2008 by James L. Weaver (jim.weaver at lat-inc.com)
*  to demonstrate how to create custom nodes in JavaFX
*/

package com.javafxpert.custom_node;

import javafx.scene.*;
import javafx.scene.geometry.*;
import javafx.scene.paint.*;
import javafx.scene.text.*;

public class ProgressNode extends CustomNode {

  /*
   * A number from 0.0 to 1.0 that indicates the amount of progress
   */
  public attribute progress:Number;
   
  /*
   * The fill of the progress part of the progress bar.  Because
   * this is of type Paint, a Color or gradient may be used.
   */
  public attribute progressFill:Paint = Color.BLUE;
   
  /*
   * The fill of the bar part of the progress bar. Because
   * this is of type Paint, a Color or gradient may be used.
   */
  public attribute barFill:Paint = Color.GREY;
   
  /*
   * The color of the progress percent text on the progress bar
   */
  public attribute progressPercentColor:Color = Color.WHITE;
   
  /*
   * The color of the progress text on the right side of the progress bar
   */
  public attribute progressTextColor:Color = Color.WHITE;
   
  /*
   * The progress text string on the right side of the progress bar
   */
  public attribute progressText:String;
   
  /*
   * Determines the width, in pixels, of the progress bar
   */
  public attribute width:Integer = 200;
   
  /*
   * Determines the height, in pixels, of the progress bar
   */
  public attribute height:Integer = 20;
   
  /**
   * Create the Node
   */
  public function create():Node {
    Group {
      var textRef:Text;
      var progTextRef:Text;
      var progBarFont =
        Font {
          name: "Sans serif"
          style: FontStyle.BOLD
          size: 12
        };
      content: [
        // The entire progress bar
        Rectangle {
          width: bind width
          height: bind height
          fill: bind barFill
        },
        // The progress part of the progress bar
        Polygon {
          points: bind [
            0.0, 0.0,
            0.0, height as Number,
            width * progress + height / 2.0, height as Number,
            width * progress - height / 2.0, 0.0
          ]
          fill: bind progressFill
          clip:
            Rectangle {
              width: bind width
              height: bind height
            }
        },
        // The percent complete displayed on the progress bar
        textRef = Text {
          translateX: width / 3
          translateY: 3
          textOrigin: TextOrigin.TOP
          font: progBarFont
          fill: bind progressPercentColor
          content: bind "{progress * 100 as Integer}%"
        },
        // The progress text displayed on the right side of the progress bar
        progTextRef = Text {
          translateX: bind width - progTextRef.getWidth() - 5
          translateY: 3
          textOrigin: TextOrigin.TOP
          font: progBarFont
          fill: bind progressTextColor
          content: bind progressText
        }
      ]
    }   
  }

Most of the concepts used here were discussed in the posts referenced above.  One thing that I'd like to point out is the use of binding, for example, to display the current value of the progress attribute as a percentage in the last line of the listing.  Now take a look at the main program, in a file named ProgressNodeExampleMain.fx:

/*
*  ProgressNodeExampleMain.fx -
*  An example of using the ProgressNode custom node.  It also demonstrates
*  the DeckNode, MenuNode and ButtonNode custom nodes
*
*  Developed 2008 by James L. Weaver (jim.weaver at lat-inc.com)
*  to demonstrate how to create custom nodes in JavaFX
*/
package com.javafxpert.progress_node_example.ui;

import javafx.application.*;
import javafx.ext.swing.*;
import javafx.scene.*;
import javafx.scene.geometry.*;
import javafx.scene.image.*;
import javafx.scene.layout.*;
import javafx.scene.paint.*;
import javafx.scene.text.*;
import javafx.scene.transform.*;
import java.lang.System;
import com.javafxpert.custom_node.*;
import com.javafxpert.progress_node_example.model.*;

var deckRef:DeckNode;

Frame {
  var model = ProgressNodeExampleModel.getInstance();
  var stageRef:Stage;
  var menuRef:MenuNode;
  title: "ProgressNode Example"
  width: 500
  height: 400
  visible: true
  stage:
    stageRef = Stage {
      fill: Color.BLACK
      content: [
        deckRef = DeckNode {
          fadeInDur: 700ms
          content: [
            // The "Splash" page
            Group {
              var vboxRef:VBox;
              var splashFont =
                Font {
                  name: "Sans serif"
                  style: FontStyle.BOLD
                  size: 12
                };
              id: "Splash"
              content: [
                ImageView {
                  image:
                    Image {
                      url: "{__DIR__}images/splashpage.png"
                    }
                },
                vboxRef = VBox {
                  translateX: bind stageRef.width - vboxRef.getWidth() - 10
                  translateY: 215
                  spacing: 1
                  content: [
                    Text {
                      content: "A Fictitious Audio Application that Demonstrates"
                      fill: Color.WHITE
                      font: splashFont
                    },
                    Text {
                      content: "Creating JavaFX Custom Nodes"
                      fill: Color.WHITE
                      font: splashFont
                    },
                    Text {
                      content: "Application Developer: Jim Weaver"
                      fill: Color.WHITE
                      font: splashFont
                    },
                    Text {
                      content: "Graphics Designer: Mark Dingman"
                      fill: Color.WHITE
                      font: splashFont
                    },
                  ]
                }
              ]
            },
            // The "Play" page
            Group {
              id: "Play"
              content: [
                ImageView {
                  image:
                    Image {
                      url: "{__DIR__}images/playlist.png"
                    }
                }
              ]
            },
            // The "Burn" page
            Group {
              var vboxRef:VBox;
              id: "Burn"
              content: [
                vboxRef = VBox {
                  translateX: bind stageRef.width / 2 - vboxRef.getWidth() / 2
                  translateY: bind stageRef.height / 2 - vboxRef.getHeight() / 2
                  spacing: 15
                  content: [
                    Text {
                      textOrigin: TextOrigin.TOP
                      content: "Burning custom playlist to CD..."
                      font:
                        Font {
                          name: "Sans serif"
                          style: FontStyle.PLAIN
                          size: 22
                        }
                      fill: Color.rgb(211, 211, 211)
                    },
                    ProgressNode {
                      width: 430
                      height: 15
                      progressPercentColor: Color.rgb(191, 223, 239)
                      progressTextColor: Color.rgb(12, 21, 21)
                      progressText: bind "{model.remainingBurnTime} Remaining"
                      progressFill:
                        LinearGradient {
                          startX: 0.0
                          startY: 0.0
                          endX: 0.0
                          endY: 1.0
                          stops: [
                            Stop {
                              offset: 0.0
                              color: Color.rgb(0, 192, 255)
                            },
                            Stop {
                              offset: 0.20
                              color: Color.rgb(0, 172, 234)
                            },
                            Stop {
                              offset: 1.0
                              color: Color.rgb(0, 112, 174)
                            },
                          ]
                        }
                      barFill:
                        LinearGradient {
                          startX: 0.0
                          startY: 0.0
                          endX: 0.0
                          endY: 1.0
                          stops: [
                            Stop {
                              offset: 0.0
                              color: Color.rgb(112, 112, 112)
                            },
                            Stop {
                              offset: 1.0
                              color: Color.rgb(88, 88, 88)
                            },
                          ]
                        }
                      progress: bind model.burnProgressPercent / 100.0
                    },
                    ComponentView {
                      component:
                        FlowPanel {
                          background: Color.BLACK
                          content: [
                            Label {
                              text: "Slide to simulate burn progress:"
                              foreground: Color.rgb(211, 211, 211)
                            },
                            Slider {
                              orientation: Orientation.HORIZONTAL
                              minimum: 0
                              maximum: 100
                              value: bind model.burnProgressPercent with inverse
                              preferredSize: [200, 20]
                            }
                          ]
                        }
                    }
                  ]
                }
              ]
            },
            // The "Config" page
            Group {
              id: "Config"
              content: [
                ImageView {
                  image:
                    Image {
                      url: "{__DIR__}images/config.png"
                    }
                }
              ]
            },
            // The "Help" page
            Group {
              id: "Help"
              content: [
                ImageView {
                  image:
                    Image {
                      url: "{__DIR__}images/help.png"
                    }
                }
              ]
            }
          ]
        },
        menuRef = MenuNode {
          translateX: bind stageRef.width / 2 - menuRef.getWidth() / 2
          translateY: bind stageRef.height - menuRef.getHeight()
          buttons: [
            ButtonNode {
              title: "Play"
              imageURL: "{__DIR__}icons/play.png"
              action:
                function():Void {
                  deckRef.visibleNodeId = "Play";
                }
            },
            ButtonNode {
              title: "Burn"
              imageURL: "{__DIR__}icons/burn.png"
              action:
                function():Void {
                  deckRef.visibleNodeId = "Burn";
                }
            },
            ButtonNode {
              title: "Config"
              imageURL: "{__DIR__}icons/config.png"
              action:
                function():Void {
                  deckRef.visibleNodeId = "Config";
                }
            },
            ButtonNode {
              title: "Help"
              imageURL: "{__DIR__}icons/help.png"
              action:
                function():Void {
                  deckRef.visibleNodeId = "Help";
                }
            },
          ]
        }
      ]
    }
}

deckRef.visibleNodeId = "Splash";

Using the ProgressNode control

You'll notice that the main program above is almost identical to the main program in the Getting Decked post, except that instead of showing the mock-up graphic that Mark Dingman supplied, we're using JavaFX code to display something similar, including our new ProgressNode control.  Examine the Group block right after the //The "Play" page comment in the listing to see this additional code.  One thing that deserves repeating from previous posts is that JavaFX is moving to a node-centric approach, so 2D graphics as well as components will all be graphical nodes.  Because of this, I'm using the ComponentView class (which is a subclass of Node), to contain the Slider, which is a component.  The JavaFX team is rapidly developing a set of controls (e.g. Button) that are subclasses of Node, so very soon the ComponentView class won't be necessary.

Note that as the complexity of the individual pages grow, I'll tend to put them in their own files, subclassing CustomNode just like we're doing with this UI controls.


Introducing a Model into this Program

If you've followed this blog, you know that JavaFX inherently supports the model-view-controller pattern through constructs such as declarative programming syntax, binding, and triggers.  In this program, our model has an attribute named burnProgressPercent, for example, that holds the completion percent of the CD burn, as shown in the ProgressNodeExampleModel.fx listing below.  Notice that in the ProgressNodeExampleMain.fx listing above that the value attribute of the Slider is bound bi-directionally to this variable, and that the progress attribute of the ProgressNode is bound to it as well.  This is what causes the progress bar to be updated as you move the slider.  Here's the ProgressNodeExampleModel.fx listing:

/*
*  ProgressNodeExampleModel.fx -
*  The model behind the ProgressNode example
*
*  Developed 2008 by James L. Weaver (jim.weaver at lat-inc.com)
*/
package com.javafxpert.progress_node_example.model;

/**
* The model behind the ProgressNode example
*/
public class ProgressNodeExampleModel {
 
  /**
   * The total estimated number of seconds for the burn.
   * For this example program, we'll set it to 10 minutes
   */
  public attribute estimatedBurnTime:Integer = 600;

  /**
   * The percent progress of the CD burn, represented by a number
   * between 0 and 100 inclusive.
   */
  public attribute burnProgressPercent:Integer on replace {
      var remainingSeconds = estimatedBurnTime * (burnProgressPercent / 100.0) as Integer;
      remainingBurnTime = "{remainingSeconds / 60}:{%02d (remainingSeconds mod 60)}";
  };

  /**
   * The time remaining on the CD burn, expressed as a String in mm:ss
   */
  public attribute remainingBurnTime:String;

  //-----------------Use Singleton pattern to get model instance -----------------------
  private static attribute instance:ProgressNodeExampleModel;

  public static function getInstance():ProgressNodeExampleModel {
    if (instance == null) {
      instance = ProgressNodeExampleModel {};
    }
    else {
      instance;
    }
  }
}

Take a look at the burnProgressPercent attribute and you'll notice a couple of things: 

  • It has an on replace trigger that gets executed whenever the value of burnProgressPercent changes.  In the on replace block we're altering the value of the remainingBurnTime attribute, which you may have noticed is being bound to by the progressText attribute of the ProgressNode.  See the ProgressNodeExampleMain.fx listing above to see this bind. 
  • Another item of interest in the on replace block is the use of a format string to pad the seconds with a leading zero.  The set of format strings available can be found in the java.util.Formatter class API documentation.

One last observation about this model class is that I'm using a singleton pattern to get a reference to it.  As our program grows, and more classes need a reference to the model class, this is an alternative to supplying a model reference via public attributes to every class in the program that needs a reference.

By the way, the other classes used by this example (ButtonNode, MenuNode and DeckNode) are located in the Rolling Your Own, and Getting Decked posts referred to above.  As always, please post a comment if you have any questions!

Regards,
Jim Weaver

July 28, 2008

Getting Decked: Another JavaFX Custom Node

In the Rolling Your Own JavaFX "Custom Nodes": A Graphical Menu Example post, I began showing you how to create your own UI controls in JavaFX.  I also said that the JFX Custom Nodes category has been started in this blog in which custom nodes that you and I create will be featured.

Today's example features a simple custom node, named DeckNode, that I developed as a way to store a set of Node instances and display one of these nodes at a time.  It is being used to augment the graphical menu example by showing the Node that pertains to a given menu button.  Note that the concept is very similar to the Java CardLayout, so I wouldn't be surprised to see a similar class appear in the javafx.scene.layout package at some point.  Just for grins, I provided the ability to specify a fade-in duration for the node being displayed.  Here's a screenshot of the example application, with one of the nodes in the "deck" being displayed as the splash page: 

Decknodeexample_5  

One of the goals of JavaFX is for graphics designers and developers to be able to work together effectively in creating great looking applications.  To demonstrate this, Mark Dingman (the User eXperience director at Malden Labs) created some graphical mock-ups for the fictitious CD application shown above.  Future blog posts will turn these mock-ups into functioning pages using a combination of UI controls from the JavaFX SDK and custom nodes that we create in this blog.  Here's an example mocked-up page in the form of a graphic that is being displayed as a result of pressing the Play button:

Decknodeexample_play

By the way, Malden Labs, under CTO Thom Theriault's technical leadership, has made a strong commitment to using JavaFX for some pretty exciting systems in the works.  Anyway, if you would like to try today's example out, click on this Java Web Start link, keeping in mind that you'll need at least JRE 5.  Also, installing Java SE 6 update 10 will give you faster deployment time.

Webstartsmall2

Here's the code for the DeckNode class, contained in a file named DeckNode.fx:

/*
 *  DeckNode.fx - 
 *  A node that shows a deck of nodes one at a time.
 *
 *  Developed 2008 by James L. Weaver (jim.weaver at lat-inc.com)
 *  to demonstrate how to create custom nodes in JavaFX
 */

package com.javafxpert.custom_node;
 
import javafx.animation.*;
import javafx.lang.*;
import javafx.scene.*;

/**
 *  A node that shows a deck of nodes one at a time.  When the
 *  visibleNodeId attribute is assigned a value, the Node whose
 *  id is the same as the name becomes visible.  Note that the
 *  the id attribute of each Node must be assigned a unique name.
 *  This class also has an attribute in which a fade-in duration
 *  may be specified.
 */
public class DeckNode extends CustomNode { 
  /**
   * A sequence that contains the Node instances in this "deck"
   */
  public attribute content:Node[];
  
  /**
   * An optional node that will appear in the background of all nodes
   */
  public attribute backgroundNode:Node;

  /**
   * The id of the node that is to be visible
   */
  public attribute visibleNodeId:String on replace {
    var nodes = for (node in content where node.id == visibleNodeId) node;
    visibleNodeRef = if (sizeof nodes > 0) nodes[0] else null;
    fadeTimeline.start(); 
  }
   
  /**
   * The amount of time to fade-in the new Node 
   */
  public attribute fadeInDur:Duration = 1ms;

  /**
   * This attribute is interpolated by a Timeline, and the opacity
   * attribute of this DeckNode class is bound to it.  This helps
   * enable the fade-in effect.
   */
  private attribute opa:Number;

  /**
   * Override the opacity attribute so that it can be bound to the
   * opa attribute that is interpolated by a Timeline
   */
  override attribute opacity = bind opa;

  /**
   * A Timeline to control the fade-in behavior
   */
  private attribute fadeTimeline =
    Timeline {
      keyFrames: [
        KeyFrame {
          time: bind fadeInDur
          values: [
            opa => 1.0 tween Interpolator.LINEAR,
          ]
        }
      ]
    };

  /**
   * A reference to the Node in the Node instances that is visible
   */
  protected attribute visibleNodeRef:Node;
   
  /**
   * Create the Node
   */
  public function create():Node {
    Group {
      content: bind [
        backgroundNode,
        visibleNodeRef
      ]
    };
  }
}  
 

Shown below is the main script for this program, in a file named DeckNodeExampleMain.fx:

/*
*  DeckNodeExampleMain.fx -
*  An example of using the DeckNode custom node.  It also demostrates
*  the MenuNode and ButtonNode custom nodes
*
*  Developed 2008 by James L. Weaver (jim.weaver at lat-inc.com)
*  to demonstrate how to create custom nodes in JavaFX
*/
package com.javafxpert.deck_node_example.ui;

import javafx.application.*;
import javafx.scene.*;
import javafx.scene.geometry.*;
import javafx.scene.image.*;
import javafx.scene.layout.*;
import javafx.scene.paint.*;
import javafx.scene.text.*;
import javafx.scene.transform.*;
import java.lang.System;
import com.javafxpert.custom_node.*;

var deckRef:DeckNode;

Frame {
  var stageRef:Stage;
  var menuRef:MenuNode;
  title: "DeckNode Example"
  width: 500
  height: 400
  visible: true
  stage:
    stageRef = Stage {
      fill: Color.BLACK
      content: [
        deckRef = DeckNode {
          fadeInDur: 700ms
          content: [
            Group {
              var vboxRef:VBox;
              var splashFont =
                Font {
                  name: "Sans serif"
                  style: FontStyle.BOLD
                  size: 12
                };
              id: "Splash"
              content: [
                ImageView {
                  image:
                    Image {
                      url: "{__DIR__}images/splashpage.png"
                    }
                },
                vboxRef = VBox {
                  translateX: bind stageRef.width - vboxRef.getWidth() - 10
                  translateY: 215
                  spacing: 1
                  content: [
                    Text {
                      content: "A Fictitious Audio Application that Demonstrates"
                      fill: Color.WHITE
                      font: splashFont
                    },
                    Text {
                      content: "Creating JavaFX Custom Nodes"
                      fill: Color.WHITE
                      font: splashFont
                    },
                    Text {
                      content: "Application Developer: Jim Weaver"
                      fill: Color.WHITE
                      font: splashFont
                    },
                    Text {
                      content: "Graphics Designer: Mark Dingman"
                      fill: Color.WHITE
                      font: splashFont
                    },
                  ]
                }
              ]
            },
            Group {
              id: "Play"
              content: [
                ImageView {
                  image:
                    Image {
                      url: "{__DIR__}images/playlist.png"
                    }
                }
              ]
            },
            Group {
              id: "Burn"
              content: [
                ImageView {
                  image:
                    Image {
                      url: "{__DIR__}images/burning.png"
                    }
                }
              ]
            },
            Group {
              id: "Config"
              content: [
                ImageView {
                  image:
                    Image {
                      url: "{__DIR__}images/config.png"
                    }
                }
              ]
            },
            Group {
              id: "Help"
              content: [
                ImageView {
                  image:
                    Image {
                      url: "{__DIR__}images/help.png"
                    }
                }
              ]
            }
          ]
        },
        menuRef = MenuNode {
          translateX: bind stageRef.width / 2 - menuRef.getWidth() / 2
          translateY: bind stageRef.height - menuRef.getHeight()
          buttons: [
            ButtonNode {
              title: "Play"
              imageURL: "{__DIR__}icons/play.png"
              action:
                function():Void {
                  deckRef.visibleNodeId = "Play";
                }
            },
            ButtonNode {
              title: "Burn"
              imageURL: "{__DIR__}icons/burn.png"
              action:
                function():Void {
                  deckRef.visibleNodeId = "Burn";
                }
            },
            ButtonNode {
              title: "Config"
              imageURL: "{__DIR__}icons/config.png"
              action:
                function():Void {
                  deckRef.visibleNodeId = "Config";
                }
            },
            ButtonNode {
              title: "Help"
              imageURL: "{__DIR__}icons/help.png"
              action:
                function():Void {
                  deckRef.visibleNodeId = "Help";
                }
            },
          ]
        }
      ]
    }
}

deckRef.visibleNodeId = "Splash";

Please notice the last line of the listing above is what causes the node that we named "Splash" to be displayed when the application starts. 

The code for the other custom nodes used in this program (ButtonNode.fx and MenuNode.fx) are in the Rolling Your Own JavaFX "Custom Nodes": A Graphical Menu Example post, so you may want to visit that post.  At some point I'll put these custom nodes in a JAR file if developers find them useful.  As always, please leave a comment if you have any questions of comments!

Thanks,
Jim Weaver

July 23, 2008

Rolling Your Own JavaFX "Custom Nodes": A Graphical Menu Example

Now that the JavaFX SDK Technology Preview has been released, I'd like to get you up to speed on how to create your own "custom nodes".  This is JavaFX-speak for widgets, gadgets, UI components, whatever, but the purpose is the same: to be able to create a potentially reusable UI thingy for JavaFX programs.  Today's example demonstrates how to create a custom node (in fact, two), and here's a screenshot:

Menunodeexample

By the way, a big thanks goes to Edgar Merino for pointing out some simplifications to the code that have now been implemented in this example.  If you would like to try it out, click on this Java Web Start link, keeping in mind that you'll need at least JRE 6.  Also, installing Java SE 6 update 10 will give you faster deployment time.

Webstartsmall2

As I mentioned in the JavaFX SDK Packages are Taking Shape post, JavaFX is adopting a graphical "node-centric" approach to UI development, so nearly everything in a JavaFX user interface is a Node.  When you want to create your own custom node, you'll extend the CustomNode class, giving it your desired attributes and behavior.  Shown below is the code for the custom node in the example that displays an image and responds to mouse events (e.g. becoming more translucent and showing the text when rolling the mouse over). 

Note: You may be wondering why I don't just use the Button class that is located in the javafx.ext.swing package.  The reason is that the Button class is a Component, not a Node, and I think that it is best to follow the stated direction of moving to a node-centric approach.  At some point there will be a button that subclasses Node, at which point the ButtonNode class in this example may not be needed anymore.

ButtonNode.fx

/*
*  ButtonNode.fx -
*  A node that functions as an image button
*
*  Developed 2008 by James L. Weaver (jim.weaver at lat-inc.com)
*  and Edgar Merino (http://devpower.blogsite.org/) to demonstrate how
*  to create custom nodes in JavaFX
*/

package com.javafxpert.custom_node;

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

public class ButtonNode extends CustomNode {
  /**
   * The title for this button
   */
  public attribute title:String;

  /**
   * The Image for this button
   */
  private attribute btnImage:Image;

  /**
   * The URL of the image on the button
   */
  public attribute imageURL:String on replace {
    btnImage =
      Image {
        url: imageURL
      };
  }
   
  /**
   * The percent of the original image size to show when mouse isn't
   * rolling over it. 
   * Note: The image will be its original size when it's being
   * rolled over.
   */
  public attribute scale:Number = 0.9;

  /**
   * The opacity of the button when not in a rollover state
   */
  public attribute opacityValue:Number = 0.8;

  /**
   * The opacity of the text when not in a rollover state
   */
  public attribute textOpacityValue:Number = 0.0;

  /**
   * A Timeline to control fading behavior when mouse enters or exits a button
   */
  private attribute fadeTimeline =
    Timeline {
      toggle: true
      keyFrames: [
        KeyFrame {
          time: 600ms
          values: [
            scale => 1.0 tween Interpolator.LINEAR,
            opacityValue => 1.0 tween Interpolator.LINEAR,
            textOpacityValue => 1.0 tween Interpolator.LINEAR
          ]
        }
      ]
    };

  /**
   * This attribute is interpolated by a Timeline, and various
   * attributes are bound to it for fade-in behaviors
   */
  private attribute fade:Number = 1.0;
 
  /**
   * This attribute represents the state of whether the mouse is inside
   * or outside the button, and is used to help compute opacity values
   * for fade-in and fade-out behavior.
   */
  private attribute mouseInside:Boolean;

  /**
   * The action function attribute that is executed when the
   * the button is pressed
   */
  public attribute action:function():Void;
   
  /**
   * Create the Node
   */
  public function create():Node {
    Group {
      var textRef:Text;
      content: [
        Rectangle {
          width: bind btnImage.width
          height: bind btnImage.height
          opacity: 0.0
        },
        ImageView {
          image: btnImage
          opacity: bind opacityValue;
          scaleX: bind scale;
          scaleY: bind scale;
          translateX: bind btnImage.width / 2 - btnImage.width * scale / 2
          translateY: bind btnImage.height - btnImage.height * scale
          onMouseEntered:
            function(me:MouseEvent):Void {
              mouseInside = true;
              fadeTimeline.start();
            }
          onMouseExited:
            function(me:MouseEvent):Void {
              mouseInside = false;
              fadeTimeline.start();
              me.node.effect = null
            }
          onMousePressed:
            function(me:MouseEvent):Void {
              me.node.effect = Glow {
                level: 0.9
              };
            }
          onMouseReleased:
            function(me:MouseEvent):Void {
              me.node.effect = null;
            }
          onMouseClicked:
            function(me:MouseEvent):Void {
              action();
            }
        },
        textRef = Text {
          translateX: bind btnImage.width / 2 - textRef.getWidth() / 2
          translateY: bind btnImage.height - textRef.getHeight()
          textOrigin: TextOrigin.TOP
          content: title
          fill: Color.WHITE
          opacity: bind textOpacityValue
          font:
            Font {
              name: "Sans serif"
              size: 16
              style: FontStyle.BOLD
            }
        },
      ]
    };
  }

Some things to note in the ButtonNode.fx code listing above are:

  • Our ButtonNode class extends CustomNode
  • This new class introduces attributes for storing the image and text that will appear on the custom node.
  • The create() function returns the declarative expression of our custom node's UI appearance and behavior.
  • The Glow effect in the javafx.scene.effect package is used to brighten the image when clicked.
  • The opacity of the image, the size of the image, and the title of the custom node, are transitioned as the mouse enters and exits the button.  A Timeline is employed to make these transitions gradual.
  • After adjusting opacity and applying a glow effect, the onMouseClicked function calls the action() function attribute defined earlier in the listing.  This make our custom node behave like the familiar Button.

Arranging the ButtonNode instances into a "menu"

As shown in the Setting the "Stage" for the JavaFX SDK post, the HBox class is located in the javafx.scene.layout package, and is a node that arranges other nodes within it.  The MenuNode custom node shown below arranges the ButtonNode instances horizontally, and it uses the Reflection class in the javafx.scene.effects package to add a nice reflection effect below the buttons.  Here's the code:

MenuNode.fx

/*
*  MenuNode.fx -
*  A custom node that functions as a menu
*
*  Developed 2008 by James L. Weaver (jim.weaver at lat-inc.com)
*  to demonstrate how to create custom nodes in JavaFX
*/

package com.javafxpert.custom_node;

import javafx.scene.*;
import javafx.scene.effect.*;
import javafx.scene.layout.*;

public class MenuNode extends CustomNode {

  /*
   * A sequence containing the ButtonNode instances
   */
  public attribute buttons:ButtonNode[];
   
  /**
   * Create the Node
   */
  public function create():Node {
    HBox {
      spacing: 10
      content: buttons
      effect:
        Reflection {
          fraction: 0.50
          topOpacity: 0.8
        }
    }   
  }

Using our custom nodes

Now that the custom nodes have been defined, I'd like to show you how to use them in a simple program.  If you've followed this blog, you know that "the way of JavaFX is to bind the UI to a model".  In this simple example, since I really want to focus on teaching you how to create custom nodes, I'm not going to complicate things by creating a model and binding the UI to it.  Rather, I'm simply printing a string to the console whenever a ButtonNode instance is clicked.  Here's the code for the main program in this example:

MenuNodeExampleMain.fx

/*
*  MenuNodeExampleMain.fx -
*  An example of using the MenuNode custom node
*
*  Developed 2008 by James L. Weaver (jim.weaver at lat-inc.com)
*  to demonstrate how to create custom nodes in JavaFX
*/
package com.javafxpert.menu_node_example.ui;

import javafx.application.*;
import javafx.scene.paint.*;
import javafx.scene.transform.*;
import java.lang.System;
import com.javafxpert.custom_node.*;

Frame {
  var stageRef:Stage;
  var menuRef:MenuNode;
  title: "MenuNode Example"
  width: 500
  height: 400
  visible: true
  stage:
    stageRef = Stage {
      fill: Color.BLACK
      content: [
        menuRef = MenuNode {
          translateX: bind stageRef.width / 2 - menuRef.getWidth() / 2
          translateY: bind stageRef.height - menuRef.getHeight()
          buttons: [
            ButtonNode {
              title: "Play"
              imageURL: "{__DIR__}icons/play.png"
              action:
                function():Void {
                  System.out.println("Play button clicked");
                }
            },
            ButtonNode {
              title: "Burn"
              imageURL: "{__DIR__}icons/burn.png"
              action:
                function():Void {
                  System.out.println("Burn button clicked");
                }
            },
            ButtonNode {
              title: "Config"
              imageURL: "{__DIR__}icons/config.png"
              action:
                function():Void {
                  System.out.println("Config button clicked");
                }
            },
            ButtonNode {
              title: "Help"
              imageURL: "{__DIR__}icons/help.png"
              action:
                function():Void {
                  System.out.println("Help button clicked");
                }
            },
          ]
        }
      ]
    }
}

Notice that the action attributes are assigned functions that are called whenever the user clicks the mouse on the corresponding ButtonNode, as pointed out earlier.  Also notice the the __DIR__  expression evaluates to the directory in which the CLASS file resides.  In this case, the graphical images are located in a com/javafxpert/menu_node_example/ui/icons directory.

By the way, the images for this article can be downloaded so that you can build and run this example with the graphics. This is a zip file that you can expand in the project's classpath.

It is my intent to build up a library of useful custom nodes for the JavaFX SDK Technology Preview and post them in the JFX Custom Nodes category of this blog.  If you have ideas for custom nodes, or would like to share ones that you've developed, please drop me a line at jim.weaver at lat-inc.com

By the way, after this post ran, Weiqi Gao reported some cool news in his Java WebStart Works On Debian GNU/Linux 4.0 AMD64 post.  I'm partial to Weiqi (pronounced way-chee), of course, because he did a great job on the technical review of our JavaFX Script book ;-)

Thanks,
Jim Weaver
JavaFX Script: Dynamic Java Scripting for Rich Internet/Client-side Applications

Immediate eBook (PDF) download available at the book's Apress site

July 17, 2008

Mighty Mouse Handling in JavaFX Script: Examples by Sven

A few days ago, I posted a mouse handling related question to the JavaFX GUI mailing list, and Sven Drieling responded with a link to some helpful examples that he created.  One of these examples is a program that displays the information that can be retrieved from the MouseEvent object that is passed to the JavaFX mouse event handlers.  Here's a screenshot of that application, named MouseEvents.fx:

Mouseeventssven

As shown in the JavaFX SDK Packages are Taking Shape post, the MouseEvent class is located in the new javafx.input package.  Take a look at the Check out the JavaFX Script Documentation post to examine the MouseEvent class, as well as the mouse handler methods in the Node class which is located in the javafx.scene package.

Sven's second example demonstrates the mouse cursors that are available via constants in the Cursor class, also located in the javafx.scene package.  Here's a screenshot of this program:

Mousecursorssven

Sven's examples have full source code and JNLP links, so go check them out and learn some Mighty Mouse handling.  Thanks again, Sven!

_mighty_mouse

Jim Weaver
JavaFX Script: Dynamic Java Scripting for Rich Internet/Client-side Applications

Immediate eBook (PDF) download available at the book's Apress site

July 13, 2008

Learn Some JavaFX Script, and Neville's Algorithm, from Luis!

In his Exploring Beautiful Languages blog, programming language enthusiast Luis Diego Fallas created a nice JavaFX Script applet example, and threw in a mini-lesson on Neville's Algorithm for free :-)

Here's a screenshot of the applet:

Nevilleapplet1


Luis walks you through all of the code in his example, which uses the newly introduced Application and Stage classes located in the javafx.application package.  Go check it out, and leave a nice comment about his post!  Incidentally, you can learn more about the Stage class in my Setting the "Stage" for the JavaFX SDK blog post.

Thanks Luis!

Jim Weaver
JavaFX Script: Dynamic Java Scripting for Rich Internet/Client-side Applications

Immediate eBook (PDF) download available at the book's Apress site

July 11, 2008

Radar Love: JavaFX Animation Examples by Sven

You may recall that I've featured some of Sven Drieling's work previously in this blog, for example in the JavaFX Animation - Some of Sven's Canvas Tricks post.  Well, Sven has updated his animation examples and added to the collection to help us prepare for the JavaFX SDK.  Here's a screenshot of Sven's AnimationRadar example, in which he animates an arc-shaped clip of an image to look like the sweep of a radar.  Because it's an animation, the running program looks cooler than the static screenshot. :-)

Animationradar

In another example, Sven uses nested Timelines to progressively start three blocks animating.  Here's a screenshot:

Nestedtimelines

Take a look at the page in which Sven stashes his many animation examples, and try out the examples via Java Web Start links on that page while studying his great code.

Thanks Sven!
Jim Weaver
JavaFX Script: Dynamic Java Scripting for Rich Internet/Client-side Applications

July 09, 2008

Setting the "Stage" for the JavaFX SDK

As I mentioned in the JavaFX SDK Packages are Taking Shape post, the JavaFX SDK will have a "node-centric" UI approach.  This approach will make it easier to create UIs that have an "iPhone look and feel" that users will increasingly expect.  The first step that the JavaFX team took in implementing this was to create a javafx.application package and put classes in it (e.g. a new version of the Frame class) that adopt this node-centric philosophy.

Today, in the JavaFX SDK Technology Preview branch, an attribute named stage was introduced into the Window class from which Frame and Dialog are derived, and it was also introduced into the Application class.  This stage attribute holds a reference to a Stage instance, which is also located in the javafx.application package.  According to the JavaFXdocs (shown below), the Stage class is the root area for all scene content, and it contains a sequence of one or more graphical Node instances.

Stagedocs

Also shown in the JavaFXdoc screenshot above is a fill attribute so that you can control the background of the stage, and find out its current height and width.  The stage expands to the area of its container, so the stage contained within a Frame, for example, will expand to the size of the usable area of the frame (e.g. minus the title bar).

An Example of Using the Stage Class, and the HBox/VBox Layout Classes

In keeping with the node-centric approach, today's example uses classes from the javafx.scene.layout package, namely HBox and VBox.  I expect that we'll see more classes in this package soon, and these classes will be for the purpose of providing cross-platform layout management of graphical nodes.  These layout classes are graphical nodes, so the entire UI containment hierarchy will (as I understand it) evolve to a frame or applet that contains a stage, that in turn contains a hierarchy of graphical nodes.  My understanding is that components such as buttons and text fields will be "nodified" so that they can be used as nodes, a side benefit of which is having a more consistent API between graphical nodes and components.  Here's a screenshot of today's example, in which resizing the Frame causes the Stage to be resized, which in turn (because of the bind operators) causes the Rectangle instances contained in the HBox and VBox layout nodes to be resized:

Stageexample

Here's the code for this example:

/*
*  StageExample.fx -
*  An example of using the new Stage class in JavaFX Script
*
*  Developed 2008 by James L. Weaver (jim.weaver at lat-inc.com)
*/

import javafx.application.*;
import javafx.scene.*;
import javafx.scene.text.*;
import javafx.scene.geometry.*;
import javafx.scene.layout.*;
import javafx.scene.paint.*;

Frame {
  var stageRef:Stage
  var rows:Integer = 4;
  var columns:Integer = 3;
  title: "Example of Using the New Stage Class"
  width: 350
  height: 400
  visible: true
  stage:
    stageRef = Stage {
      fill: Color.WHITE
      content:
        VBox {
          spacing: bind stageRef.height / (rows) * .10
          content:
            for (row in [1..rows])
              HBox {
                spacing: bind stageRef.width / (columns) * .10
                content:
                  for (column in [1..columns])
                    Group {
                      var rectRef:Rectangle
                      var textRef:Text
                      translateX: bind stageRef.width / (rows) * .10
                      translateY: bind stageRef.height / (columns) * .10
                      content: [
                        rectRef = Rectangle {
                          fill: Color.BLUE
                          stroke: Color.WHITE
                          width: bind stageRef.width / (columns) * .85
                          height: bind stageRef.height / (rows) * .85
                        },
                        textRef = Text {
                          content: "{row},{column}"
                          fill: Color.WHITE
                          textOrigin: TextOrigin.TOP
                          x: bind (rectRef.getWidth() / 2) - (textRef.getWidth() / 2)
                          y: bind (rectRef.getHeight() / 2) - (textRef.getHeight() / 2)
                          font:
                            Font {
                              size: 24
                              style: FontStyle.BOLD
                            }
                        }
                      ]
                    }
              }
        }   
    }
}


Running this Example

The JavaFX SDK Technology Preview branch of the compiler build may be downloaded here.  This branch is what will become the JavaFX SDK Preview Release.  After adding the openjfx-compiler-tp1/dist/bin directory to your PATH environment variable, and verifying that you have the Java Runtime Environment (JRE) 6 installed, use the following command at your operating system prompt to compile the program:

javafxc StageExample.fx

To run the program, use the following command:

javafx StageExample

Alternatively, you can take the easy way out and run this application via Java Web Start.  As mentioned in previous posts, installing Java SE 6 Update 10 will cause this application to deploy more quickly.  Also, keep in mind that JRE 6 is required regardless of your platform.  Regarding running this on a Mac, here's a tip that Ken Russell of Sun and Thom Theriault of MaldenLabs passed on to me after the Java Web Start link didn't successfully invoke the program:

  • Launch Applications > Utilitiies > Java > Java Preferences
  • Under Java Application Runtime Settings, drag Java SE 6 (64-bit) to the top of the box as shown below.
  • Save the configuration.

Snapz_pro_xscreensnapz053

Have fun, and please post a comment if you have any questions!
Jim Weaver
JavaFX Script: Dynamic Java Scripting for Rich Internet/Client-side Applications

Immediate eBook (PDF) download available at the book's Apress site

July 07, 2008

SequenceExample: Book Example Updated for JavaFX Script SDK

As I mentioned earlier in the Book Example Updated for JavaFX Script SDK: HelloJFX post, several of the upcoming blog posts are going to contain the examples from my JavaFX Script book, rewritten to work with the JavaFX SDK Technology Preview Release.  For your convenience, I'm placing these posts in the JavaFX Script Book Examples category. Today's post contains the SequenceExample program, which was developed to demonstrate creating and manipulating sequences.  Here is the console output when running the program:

The third planet is:Earth
Mercury is a planet in our solar system
Venus is a planet in our solar system
Earth is a planet in our solar system
Mars is a planet in our solar system
Saturn is a planet in our solar system
Neptune is a planet in our solar system

Inserting Uranus before Neptune
Inserting Jupiter after Mars
Inserting Pluto as the last planet
Inserting the Sun into the beginning
Sun is a planet in our solar system
Mercury is a planet in our solar system
Venus is a planet in our solar system
Earth is a planet in our solar system
Mars is a planet in our solar system
Jupiter is a planet in our solar system
Saturn is a planet in our solar system
Uranus is a planet in our solar system
Neptune is a planet in our solar system
Pluto is a planet in our solar system

Deleting the Sun in position 0
Querying for the indexof Pluto
Pluto is in position 8
Deleting Pluto
[ Neptune, Uranus, Saturn, Jupiter, Mars, Earth, Venus, Mercury ]

The 5 planets with more than 5 characters are:
    MercuryJupiterSaturnUranusNeptune

The 4 planets in the second half of the alphabet are:
    Venus,Saturn,Uranus,Neptune,

By the way, the eBook version of the JavaFX Script book is available for download from this Apress site.

The Updated Code for SequenceExample

Here is the code, updated by a colleague and JavaFX developer named Matt Shirey:

/*
*  SequenceExample.fx - An example creating a sequence literal and using
*                       some sequence related features of JavaFX to work
*                       with it.
*
*  Developed 2007 by James L. Weaver (jim.weaver at lat-inc dot com)
*
*  Updated July 2008 by Matt Shirey (matt.shirey at lat-inc dot com)
*  for JavaFX SDK Technology Preview 1
*/
package jfx_book;

import java.lang.System;

// Literally define the planets sequence, leaving out Jupiter and Uranus
var planets = ["Mercury", "Venus", "Earth", "Mars",
                       "Saturn", "Neptune"];

// Print out the third element in the array, which is element 2
// since arrays are zero-based.
System.out.println("The third planet is:{planets[2]}");

// Print out all planets by iterating over the planets sequence
for (planet in planets) {
  System.out.println("{planet} is a planet in our solar system");
}
System.out.println("");

// Insert Uranus before Neptune (which is currently in position 5)
System.out.println("Inserting Uranus before Neptune");
insert "Uranus" before planets[5];

// Insert Jupiter after Mars (which is currently in position 3)
System.out.println("Inserting Jupiter after Mars");
insert "Jupiter" after planets[3];

// Add Pluto to the end of the sequence.
System.out.println("Inserting Pluto as the last planet");
insert "Pluto" into planets;

// Add Sun to the beginning of the sequence
System.out.println("Inserting the Sun into the beginning");
insert "Sun" before planets [0];

// Print out all planets by iterating over a sequence that is the same
// size as the planets sequence, and reference the corresponding element
// in the planets array.
for (i in [0.. sizeof planets - 1]) {
  System.out.println("{planets[i]} is a planet in our solar system");
}
System.out.println("");

// Delete the Sun (which is currently in position 0)
System.out.println("Deleting the Sun in position 0");
delete planets[0];

// Query for the indexof Pluto and delete it
System.out.println("Querying for the indexof Pluto");
var indices = for (planet in planets
                       where planet == "Pluto") indexof planet;
System.out.println("Pluto is in position {indices}");
if (sizeof indices >= 0) {
  System.out.println("Deleting Pluto");
  delete "Pluto" from planets;
}

// Query for and print all of the planets run together in reverse order
var allPlanetsRunTogether = reverse planets;
System.out.println(allPlanetsRunTogether);
System.out.println("");

// Use for to query for all of the planets with more than 5 characters
var somePlanets:String[];
somePlanets = for (planet in planets where planet.length() > 5) planet;
System.out.println("The {sizeof somePlanets} planets with more than 5 characters are:
    {somePlanets}");
System.out.println("");

// Use for to query for all of the planets that begin with
// the letter "N" or later
somePlanets = for (planet in planets where planet.compareTo("N") >= 0) "{planet},";
System.out.println("The {sizeof somePlanets} planets in the second half of the alphabet are:
    {somePlanets}");

Running this Example

The JavaFX SDK Technology Preview branch of the compiler build may be downloaded here.  This branch is what will become the JavaFX SDK Preview Release.  After adding the openjfx-compiler-tp1/dist/bin directory to your PATH environment variable, and verifying that you have the Java Runtime Environment (JRE) 6 installed, use the following command at your operating system prompt to compile the program:

javafxc -d . SequenceExample.fx

To run the program, use the following command:

javafx jfx_book.SequenceExample

Have fun, and please post a comment if you have any questions!
Jim Weaver
JavaFX Script: Dynamic Java Scripting for Rich Internet/Client-side Applications

Immediate eBook (PDF) download available at the book's Apress site

July 06, 2008

HelloJFXBind2: Book Example Updated for JavaFX Script SDK

As I mentioned earlier in the Book Example Updated for JavaFX Script SDK: HelloJFX post, several of the upcoming blog posts are going to contain the examples from my JavaFX Script book, rewritten to work with the JavaFX SDK Technology Preview Release.  For your convenience, I'm placing these posts in the JavaFX Script Book Examples category. Today's post contains the HelloJFXBind2 program, which builds upon the HelloJFXBind program to demonstrate putting a model in its own file.  Here is a screenshot of the UI:

Hellojfxbind2

By the way, the eBook version of the JavaFX Script book is available for download from this Apress site.

The Updated Code for HelloJFXBind2

This example program is comprised of two files.   Here is the code, updated by a colleague and JavaFX developer named Matt Shirey:

HelloJFXBind2.fx

/*
*  HelloJFXBind2.fx - A JavaFX Script "Hello World" style example
*                     binding to a model that is located in its own file.
*
*  Developed 2007 by James L. Weaver (jim.weaver at lat-inc dot com)
*
*  Updated July 2008 by Matt Shirey (matt.shirey@lat-inc.com)
*  for JavaFX SDK Technology Preview 1
*/
package jfx_book;

import javafx.ext.swing.*;
import javafx.scene.*;
import javafx.scene.paint.*;
import javafx.scene.text.*;

/**
* This is a JavaFX Script that binds to data from the model.
*/
Frame {
  var hellojfxModel =
    HelloJFXModel {
      greeting: "Howdy JavaFX Script Developer!"
    }
  title: "JavaFX Script example that binds to a model"
  height: 100
  width: 400
  content:
    Canvas {
      content:
        Text {
          font:
            Font {
              name: "Sans Serif"
              style: FontStyle.BOLD_ITALIC
              size: 24
            }
          textOrigin: TextOrigin.TOP 
          // Put some color into the app
          stroke: Color.RED
          fill: Color.RED
          x: 10
          y: 10
          content: bind  hellojfxModel.greeting
        }
    }
  visible: true
}


HelloJFXModel.fx

/*
*  HelloJFXModel.fx - The model behind a JavaFX Script "Hello World" style
*                     example of binding.
*
*  Developed 2007 by James L. Weaver (jim.weaver at lat-inc.com)
*
*  Updated July 2008 by Matt Shirey (matt.shirey at lat-inc.com)
*  for JavaFX SDK Technology Preview 1
*/
package jfx_book;

/**
* This class serves as the model behind the user interface
*/
class HelloJFXModel {
  attribute greeting:String;
}

Running this Example

The JavaFX SDK Technology Preview branch of the compiler build may be downloaded here.  This branch is what will become the JavaFX SDK Preview Release.  After adding the openjfx-compiler-tp1/dist/bin directory to your PATH environment variable, and verifying that you have the Java Runtime Environment (JRE) 6 installed, use the following command at your operating system prompt to compile the program:

javafxc -d . *.fx

To run the program, use the following command:

javafx jfx_book.HelloJFXBind2

Have fun, and please post a comment if you have any questions!
Jim Weaver
JavaFX Script: Dynamic Java Scripting for Rich Internet/Client-side Applications

Immediate eBook (PDF) download available at the book's Apress site

July 05, 2008

HelloJFXBind: Book Example Updated for JavaFX Script SDK

As I mentioned earlier in the Book Example Updated for JavaFX Script SDK: HelloJFX post, several of the upcoming blog posts are going to contain the examples from my JavaFX Script book, rewritten to work with the JavaFX SDK Technology Preview Release.  For your convenience, I'm placing these posts in the JavaFX Script Book Examples category. Today's post contains the HelloJFXBind program, which builds upon the HelloJFX program to demonstrate binding the UI to a model.  Here is a screenshot of the UI:

Hellojfxbind

By the way, the eBook version of the JavaFX Script book is available for download from this Apress site.

The Updated Code for HelloJFXBind

Here is the code, updated by a colleague and JavaFX developer named Matt Shirey:

/*
*  HelloJFXBind.fx - A JavaFX Script "Hello World" style example
*                    binding to a model
*
*  Developed 2007 by James L. Weaver (jim.weaver at lat-inc.com)
*
*  Updated July 2008 by Matt Shirey (matt.shirey at lat-inc.com)
*  for JavaFX SDK Technology Preview 1
*/
package jfx_book;

import javafx.ext.swing.*;
import javafx.scene.*;
import javafx.scene.paint.*;
import javafx.scene.text.*;

/**
* This class serves as the model behind the user interface
*/
class HelloJFXModel {
  attribute greeting:String;
}

/**
* This is a JavaFX Script that binds to data from the model.
*/
var hellojfxModel =
  HelloJFXModel {
    greeting: "Hello JavaFX Script Developer!"
  };

Frame {
  title: "JavaFX Script example that binds to a model"
  height: 100
  width: 400
  content:
    Canvas {
      content:
        Text {
          font:
            Font {
              name: "Sans Serif"
              // Example of an attribute with a collection of values
              style: FontStyle.BOLD_ITALIC
              size: 24
            }
          textOrigin: TextOrigin.TOP 
          // Put some color into the app
          stroke: Color.RED
          fill: Color.RED
          x: 10
          y: 10
          content: bind  hellojfxModel.greeting
        }
    }
  visible: true
}

Running this Example

The JavaFX SDK Technology Preview branch of the compiler build may be downloaded here.  This branch is what will become the JavaFX SDK Preview Release.  After adding the openjfx-compiler-tp1/dist/bin directory to your PATH environment variable, and verifying that you have the Java Runtime Environment (JRE) 6 installed, use the following command at your operating system prompt to compile the program:

javafxc -d . HelloJFXBind.fx

To run the program, use the following command:

javafx jfx_book.HelloJFXBind

Have fun, and please post a comment if you have any questions!
Jim Weaver
JavaFX Script: Dynamic Java Scripting for Rich Internet/Client-side Applications

Immediate eBook (PDF) download available at the book's Apress site

My Photo

Upcoming Speaking Engagements:


  • Stephen Chin and Jim Weaver speaking about JavaFX Platform

  • Speaking on JavaFX and Java at Øredev in Malmö, Sweden on 2-6 November, 2009

Upcoming JavaFX Training:


  • Developing Secure, Rich Internet Applications Hosted on a Variety of Clients Using JavaFX Technology

Enter your email address:

Delivered by FeedBurner

Available now as early access eBook


  • Click book image above to obtain eBook

Twitter Updates

    follow me on Twitter

    Affiliations:

    DZone Links:


    July 2009

    Sun Mon Tue Wed Thu Fri Sat
          1 2 3 4
    5 6 7 8 9 10 11
    12 13 14 15 16 17 18
    19 20 21 22 23 24 25
    26 27 28 29 30 31  

    Disclaimer:

    • By reading this site, you are agreeing that under no circumstances will Veriana Networks, Inc. or its affiliates be responsible for (1) any information contained on or omitted from the site, (2) any person's reliance on any such information, whether or not the information is correct, current or complete, (3) the consequences of any action you or any other person takes or fails to take, whether or not based on information provided by or as a result of the use of the sites.