JFX

December 03, 2008

About Chris Oliver: The "Contrarian Mind" Behind JavaFX

JavaFX SDK 1.0 will be released tomorrow, December 4th, 2008, so while we're waiting I thought you might want to learn more about the history of JavaFX and the "contrarian mind" behind it.

Oliver.gosling

Al Riske wrote an excellent piece in Sun's "Contrarian Minds" series in which he interviewed Chris Oliver, delving into Chris' background and his role in founding JavaFX.  By the way, Chris is the guy on the left in the picture above, presenting JavaFX with Dr. Gosling :-)

Chris said in the article about teamwork: "It's about people coming together and thinking that, by assisting each other, supporting each other, it's going to be beneficial. Everything we do, we do for selfish reasons, but when you're part of a team you realize that what's best for you is for the overall team to be successful."

Playing off of Chris' teamwork comment, I'd like to acknowledge and thank the people, both within Sun and in the open JavaFX community, that have been involved in moving JavaFX forward to the ready-for-launch state it is in today.  I'd also like to give you fair warning that on December 4th I'll begin a series in this blog in which we'll use the shiny new JavaFX SDK 1.0 to create a JavaFX application from the ground up.  We'll involve an expert graphics designer (Mark Dingman of Malden Labs) so that it looks cool as well :-)

Regards,
Jim Weaver
JavaFXpert.com

December 01, 2008

Speed-Learning JavaFX Script: Read Weiqi Gao's "Experiments with JavaFX Script"

Want to get up to speed quickly on the JavaFX Script language?  Weiqi Gao of Object Computing, Inc. (OCI) has written an excellent article that covers the concepts and language elements of JavaFX Script in a fast-paced but approachable manner.  If you've followed this blog or read the first Apress JavaFX Script book, you'll recognize Weiqi as a leader in the JavaFX Script community as well as the Technical Reviewer of the book.

641px-Mad_scientist_transparent_background.svg

After you read Experiments with JavaFX Script, be sure to check out Weiqi Gao's Observations blog.

Regards,
Jim Weaver
JavaFXpert.com

November 29, 2008

Chris Oliver: JavaFX 1.0 - Now you can judge for yourself

Chris Oliver, founder of JavaFX, shares his thoughts about JavaFX a few days before its release.  Congratulations to Chris, to the excellent JavaFX team at Sun, and to the JavaFX community!

Regards,
Jim Weaver
JavaFXpert.com

November 25, 2008

Up the Nile *with* a Paddle: Fun with JavaFX Graphics

I've often mentioned the importance of programmers and graphics designers working together to create great looking RIAs.  The declarative syntax of JavaFX facilitates this approach, and the "Project Nile" tool available with the JavaFX SDK makes this even easier.

Sten_breakout  

Try your hand at the "Breakout" style game that Sten posted on his blog, and then read about how he collaborated with a graphic designer (his four-year-old daughter) to create this game in JavaFX.  Sten discusses in his post how he used Project Nile to export graphics-related JavaFX code from Adobe Illustrator using Project Nile.

On a related note, Silveira Neto (whose JavaFX fun I've featured on this blog before) participated in creating a plug-in for Inkscape that exports SVG in the form of JavaFX code.  

Inkscape_draw_with

That Silveira has all the fun!  Regards,

Jim Weaver
JavaFXpert.com

November 24, 2008

Sanity Will Be Restored to Internet Application Development on December 4, 2008

Yes, that's a bold statement, but I've waited 13 years to be able to say it.  If you've followed this blog or have heard me speak about JavaFX, you know that I was disappointed with the browser wars in the late 90s that fragmented and slowed down the ubiquity of the Java Virtual Machine.  Instead of writing rich-client Java applications, the last 13 years of software development has largely consisted of trying to make browsers do what they were never designed to do.  If I may speak plainly, the result has been applications that consist of a big mess of varying flavors of JavaScript, HTML, etc. that leave developers overwhelmed and users underwhelmed.

According Danny Coward of Sun, in less than ten days from now version 1.0 of the JavaFX SDK will be released.  JavaFX SDK 1.0 (with the support of Java SE 6 update 10) will make rich-client Java a reality.  It is my opinion that this release will be a tipping point in software development that will play a big part in restoring sanity to rich internet application development.

If you are wondering what practical steps you can take to leverage this seismic shift, here are a few that I would suggest:

  • If you are currently designing an internet application or remodeling an existing one:
  1. Think about how the full power of a rich client Java UI could improve the user experience. 
  2. Engage a graphics designer to create comps of this experience, reminding him/her that UI limitations imposed by the browser are history.
  3. Define an interface to the application/business logic layer.  Because JavaFX can instantiate and use Java classes, any interface that is implemented in Java can be used.  I often use a simple HTTP-based interface that uses GET parameters to pass arguments, and returns a stream of data articulated in the JSON protocol.  This approach will work well with JavaFX on all platforms (e.g. desktop and mobile phones).
  • Although Swing components are available in JavaFX, there is a strong trend in JavaFX as well as in RIAs (rich internet applications) toward graphical-node-centric UIs.  Users are beginning to expect (and deserve) iPhone-style applications, so think less about Swing and more about Java 2D capabilities.
  • Because the JavaFX SDK will be a 1.0 release, there is a good chance that you'll want to create some of your own higher level custom controls.  The JFX Custom Nodes category of this blog explains how to do this, and offers a few custom nodes that you might want to use.  Also, Stephen Chin and Keith Combs, mentioned in the You Say You Want a (RIA) Revolution? post, have said that they will create custom nodes for use in JavaFX applications to augment what is released in JavaFX SDK 1.0.
  • And, of course, join me in downloading the JavaFX SDK 1.0 release from JavaFX.com on December 4.

JfokusLogo

By the way, my first speaking engagement after this release will be at Jfokus in Stockholm, Sweden.  I'm going to do a 3.5 hour university session as well as a conference session.  If you're in the area, please do attend!

Regards,
Jim Weaver
JavaFXpert.com

October 26, 2008

Getting Ready for the Imminent JavaFX SDK 1.0 Release

As you may know, the OpenJFX Compiler project has been making improvements to the JavaFX language out in the open, so I'd like to get you up to speed on these in preparation for when the entire JavaFX SDK 1.0 is released yet this fall.  Today I'll discuss some of these language changes:

def Jam

A keyword has been introduced, named def, that is designed to be used to define constants in JavaFX.  The value of a variable declared with def may not be subsequently changed, except through the bind mechanism.  For example, in the code snippet below, the second line won't compile because it is attempting to assign a value to PI after it has been declared with def:

 def PI:Number = 3.14159;
PI = 22.0 / 7.0; // This line doesn't compile

Use var Everywhere -- attribute is History

For consistency, the var keyword is now used to declare instance variables, replacing the attribute keyword.  The result is that var is used to declare local variables, instance variables, and module (script) level variables.  Of course, we've been using var all along to declare local variables in a function.  The following (contrived) JavaFX program contains examples of using var and def in the ways just discussed:

/*
 *  DeclaringVariablesMain.fx - 
 *  An example of declaring variables in JavaFX SDK 1.0
 *
 *  Developed 2008 by James L. Weaver (jim.weaver at jmentor.com)
 *  to demonstrate how to declare variables in using def and var in JavaFX
 */

 //script-level variable that also is a constant (def)
 def PI:Number = 3.14159; 
 
 class Vehicle {
   //instance variables
   var wheels:Integer; 
   var wheelDiameter:Number;
   
   function getVehicleType():String {
     //local variable
     var types:String[] = [  
       "Unicycle",
       "Motorcycle",
       "Tricycle",
       "Car"
     ];
     types[wheels - 1];
   }

   function totalWheelsCircumference():Number {
     wheels * wheelDiameter * PI;
   }
 }

//script-level variable 
var myG6 = 
  Vehicle {
    wheels: 4
    wheelDiameter: 14
  }

//println is a built-in function now
println("Total wheels circumference of my G6 {myG6.getVehicleType()} "
        "is:{myG6.totalWheelsCircumference()}");

Here is the output that I received when running this program:

Total wheels circumference of my G6 Car is:175.92904

Other Noteworthy Items

As shown above, we no longer have to import java.lang.System to use the print and println functions.  Also, remember that since JavaFX is a block expression language, return statements aren't necessary in the functions above.  The value of a block is the last expression in the block.

In future posts I'll continue discussing JavaFX languages enhancements that have been introduced in the OpenJFX Compiler project.  This will help you prepare for the JavaFX SDK 1.0 release that is just around the corner!

Regards,

Jim Weaver
http://JavaFXpert.com

September 29, 2008

Some Perspective in JavaFX

I've been asked to post the slides for the JavaFX presentation that I delivered at JavaZone 2008.  Because the presentation itself was written in JavaFX, I've bundled it up so that you can run it from a Java Web Start link.  First, however, I'd like to show you some screenshots and provide an explanation of what you'll be looking at:

JavaZone08_Cube1

As shown above, the presentation (entitled Vikings and Wizards in JavaFX) is shown on the faces of a rotating cube.  The Norwegian Java Users Group, named javaBin, does a great job in organizing the JavaZone conference, which also explains the graphics on the first "slide".

To view the next or previous slide, you can click the image buttons that have a right or left pointing triangle, respectively.  This will rotate the cube, which is accomplished using the PerspectiveTransform effect, located in the javafx.scene.effect package.

JavaZone08_Cube2

Some of the slides are "live" (running JavaFX functionality within them).  For example, you can interact with the TetrixJFX game, the "Are You A Viking" wizard, the custom node examples (MenuNode and DeckNode), and the morphing example.  For the latter, just click on the yellow circle and watch the morphing begin!  To see more information about these applications, visit the blog posts for TetrisJFX, Vikings and Wizards, Rolling Your Own Custom Nodes, and Getting Decked

JavaZone08_Cube4

By the way, the presentation runs in an undecorated Frame, which means that there is no border, Close box, etc.  I make my desktop background black and minimize any open windows before presenting so that the cube appears to be rotating in a big dark space.  As soon as the graphics designer (Mark Dingman of Malden Labs) gets a chance to create a cool looking graphical Close box, I'll put it in the application.  For now, however, you'll have to end the application manually.  You'll need JRE 6 to run this, and Java SE 6 update 10 will give you a faster deployment experience.  Go ahead and give it a whirl by clicking the Java Web Start button below!

Note: Please keep in mind that this is running with the JavaFX Preview SDK which has not been optimized for performance.  Behavior and performance of the PerspectiveTransform on Vista, for example, are definitely sub-optimal at the moment.  These are known issues that the JavaFX GUI team is confident have already been corrected in the upcoming JavaFX SDK 1.0 release.

Webstartsmall2

Regards,

Jim Weaver
JavaFXpert.com

September 19, 2008

Using the Java Deployment Toolkit with JavaFX Applets

First, let me apologize for resurrecting the very humble JavaFX program shown below, but I want to keep this example very succinct.  This will enable you to use it as "starter code" for JavaFX applet deployment.  Note: To see more functional JavaFX programs, please see articles in the JFX Custom Nodes category.

BindToFunctionApplet_SDK_Preview

Note: Thanks to reader "mbien" (see comments) for pointing our that the colors of the original applet in this post were hideous (my words).  I then consulted graphics designer Mark Dingman of Malden Labs who gave me a graphical mock-up from which I created the above applet.  Here's the code for this applet, updated for the JavaFX SDK preview:

/*
 *  BindToFunctionApplet.fx - A compiled JavaFX program that demonstrates
 *                            how to create JavaFX applets.
 *                            It also demonstrates binding to a function.
 *
 *  Developed 2008 by Jim Weaver (development) and Mark Dingman (graphic design)
 *  to serve as a JavaFX Script example.
 */
package com.javafxpert.bind_to_function;

import javafx.application.*;
import javafx.ext.swing.*;
import javafx.scene.*;
import javafx.scene.geometry.*;
import javafx.scene.paint.*;
import javafx.scene.text.*;
import javafx.scene.transform.*;
import java.lang.Math;

class CircleModel {
  attribute diameter:Integer;
  
  bound function getArea():Number {
    Math.PI * Math.pow(diameter / 2, 2);
  }
}

Application {
  var cModel = CircleModel {};
  var componentViewRef:ComponentView;
  var stageRef:Stage;
  stage: 
    stageRef = Stage {
      var labelFont = Font {
        name: "Sans Serif"
        style: FontStyle.PLAIN
        size: 32
      }
      fill:
        LinearGradient {
          startX: 0.0
          startY: 0.0
          endX: 0.0
          endY: 1.0
          stops: [
            Stop { 
              offset: 0.0 
              color: Color.rgb(0, 168, 255) 
            },
            Stop { 
              offset: 1.0 
              color: Color.rgb(0, 65, 103) 
            }
          ]
        }
      content: [
        Circle {
          centerX: 250
          centerY: 250
          radius: bind cModel.diameter / 2
          fill:
            LinearGradient {
              startX: 0.0
              startY: 0.0
              endX: 0.0
              endY: 1.0
              stops: [
                Stop { 
                  offset: 0.0 
                  color: Color.rgb(74, 74, 74) 
                },
                Stop { 
                  offset: 1.0 
                  color: Color.rgb(9, 9, 9) 
                }
              ]
            }
        },
        Text {
          font: labelFont
          x: 30
          y: 70
          fill: Color.BLACK
          content: bind "Diameter: {cModel.diameter}"
        },
        Text {
          font: labelFont
          x: 260
          y: 70
          fill: Color.BLACK
          content: bind "Area: {%3.2f cModel.getArea()}"
        },
        componentViewRef = ComponentView {
          transform: bind 
            Translate.translate(40, stageRef.height - 30 -
                                   componentViewRef.getHeight())
          component:
            Slider {
              minimum: 0
              maximum: 400
              preferredSize: bind [stageRef.width - 80, 20]
              value: bind cModel.diameter with inverse
            }
        }
      ]
    }
}

Why Use the Java Deployment Toolkit for Java Applets?

According to Sun's Java Deployment Toolkit overview page, "Desktop clients have a wide variety of Java Platforms installed, from the Microsft VM to Sun's latest Java SE 6 updates. They run various operating systems from Sun, Microsoft, Apple, Red Hat, and others, and are connected to the internet at a wide range of connection speeds. How are content providers to deliver Java content to all of these clients with the best possible user experience?

Various sources have published JavaScript techniques for detecting and deploying the Java Platform for use by Java Plug-In applets and Java Web Start applications. These scripts generally have serious limitations and fail to support the varied combinations of browser, OS, and configuration options found on today's clients.

The Java Deployment Toolkit allows developers to easily deploy applets and applications to a large variety of clients with JavaScripts. It also provides advice on using some of the most powerful features available in Java Web Start and Java Plug-In, and an outline of the differences between these two deployment vehicles.
"

In a nutshell, the Java Deployment Toolkit is a JavaScript library maintained by Sun and always available at runtime by your HTML code.  This library has several methods that perform tasks such as sensing Java-related infrastructure and installing the JRE on client machines.  We'll use one of these methods, namely runApplet, to run a JavaFX applet with a specified minimum JRE version.  Here's the HTML and JavaScript code I'm using to deploy today's example applet:

<html>
<script src="http://java.com/js/deployJava.js"></script>
<script>
  var attributes = {codebase:'http://jmentor.com/JFX/BindToFunctionApplet',
    code:'javafx.application.Applet.class',
    archive:'BindToFunctionApplet.jar, javafxrt.jar, Scenario.jar, javafxgui.jar, javafx-swing.jar',
    width:500, height:500, java_arguments:'-Djnlp.packEnabled=true'};
  var parameters = {"ApplicationClass":"com.javafxpert.bind_to_function.BindToFunctionApplet",
                    "draggable":"true"};
  var version = '1.6.0' ;
  deployJava.runApplet(attributes, parameters, version);
</script>
</html>


Notice that the above code enables dragging the applet onto the desktop, as well as using Pack200 formatted JAR files, if the client machine has Java SE 6 update 10 installed.  Give the applet a whirl to see its deployment behavior on your machine.  By the way, according to the Java SE 6 Update 10 plug-in docs, "by default, the gesture to drag the applet out of the web browser is Alt + Left click + Drag."

Thanks,
Jim Weaver
JavaFXpert.com weblog

September 13, 2008

Vikings and Wizards in JavaFX

Stockholm-09-Horizontal

I have three objectives for today's article:

  1. Continue teaching you how to create UI custom controls in JavaFX.  This lesson is a new addition to the JFX Custom Nodes category, and it provides an infrastructure on which you can easily create "wizards".

  2. Show my appreciation to the JavaZone committee for inviting me to speak on JavaFX at JavaZone 2008 in Oslo, Norway, which occurred on September 17 & 18.

  3. Show my appreciation to the Jfokus committee for inviting me to speak on JavaFX at Jfokus in Stockholm, Sweden which will occur on February 27 & 28, 2009.  Also, as noted in the banner above, I will be teaching a two day public JavaFX course entitled "Rich Internet Application Development with JavaFX"

JfokusLogo

To accomplish these objectives, I've created a "wizard" (in the spirit of fun) in which you can discover whether you are a Viking or not.  Having visited Norway in the past, and having talked to the Jfokus leadership , I've found that Norwegians and Swedes tend to be very proud of their Viking heritage.  The program in this post was created out of respect for that sentiment, and I consulted one of the Norwegian JavaZone organizers for fun ideas to include in this wizard.  As with the rest of the series, the designers at Malden Labs created the graphical mock-ups and assets.  Here are a couple of screenshots of the wizard that I call "Are You a Viking?"

VikingWizard-Shaving   

As shown in the previous screen shot, this wizard asks the user questions, The user's response to a given question determine what wizard page will be shown next. If you answer the questions as a true Viking would, then the wizard will congratulate you with the page shown in the screenshot below:

VikingWizard-Congrats
 

Go ahead and try out the program.  You'll need JRE 6, and please note that Java SE 6 Update 10 will give you a faster deployment experience.

Creating a Wizard

In addition to using some of the classes from the JFX Custom Nodes category of the blog (namely the MenuNode, ButtonNode and DeckNode classes), this example introduces three more classes: WizardNode, WizardPoint and OptionsNode.  Before showing you the code for these new classes, I’d like you to see the main program in this "Are You a Viking?" example, which is in a file named WizardNodeExampleMain.fx:

/*
 *  WizardNodeExampleMain.fx - 
 *  An example of using the WizardNode custom node.  It also demonstrates
 *  the OptionsNode, MenuNode and ButtonNode custom nodes, as well as the
 *  WizardPoint class.
 *
 *  Developed 2008 by James L. Weaver (jim.weaver at lat-inc.com)
 *  to demonstrate how to create custom nodes in JavaFX
 */
package com.javafxpert.wizard_node_example.ui;

import javafx.application.*;
import javafx.ext.swing.*;
import javafx.scene.*;
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 wizardRef:WizardNode;

Frame {
  var stageRef:Stage;
  var menuRef:MenuNode;
  var pageFont =
    Font {
      name: "Sans serif"
      size: 28
    };
  title: "WizardNode Example"
  width: 505
  height: 400
  visible: true
  stage:
    stageRef = Stage {
      fill: Color.BLACK
      content: [
        wizardRef = WizardNode {
          flowPath: 
            WizardPoint {
              nodeID: "Shaving"  // The id attribute of the Node to be displayed
              nextPoints: [
                WizardPoint {
                  advanceState: "ElectricRazor"
                  nodeID: "NotViking"
                },
                WizardPoint {
                  advanceState: "SafetyRazor"
                  nodeID: "NotViking"
                },
                WizardPoint {
                  advanceState: "StraightRazor"
                  nodeID: "NotViking"
                },
                WizardPoint {
                  advanceState: "ShaveWithSword"
                  nodeID: "DiscoveredAmerica"
                  nextPoints: [
                    WizardPoint {
                      advanceState: "ChristopherColumbus"
                      nodeID: "NotViking"
                    },
                    WizardPoint {
                      advanceState: "LeifEricson"
                      nodeID: "WearHelmetHowOften"
                      nextPoints: [
                        WizardPoint {
                          advanceState: "AlwaysWearsHelmet"
                          nodeID: "OwnWoodenBoat"
                          nextPoints: [
                            WizardPoint {
                              advanceState: "FiberglassBoat"
                              nodeID: "NotViking"
                            },
                            WizardPoint {
                              advanceState: "WoodenBoat"
                              nodeID: "IsViking"
                            },
                            WizardPoint {
                              advanceState: "NoBoat"
                              nodeID: "NotViking"
                            },
                          ]
                        },
                        WizardPoint {
                          advanceState: "WearsHelmetOnOccasions"
                          nodeID: "NotViking"
                        },
                        WizardPoint {
                          advanceState: "WearsHelmetOncePerWeek"
                          nodeID: "NotViking"
                        },
                        WizardPoint {
                          advanceState: "NeverWearsHelmet"
                          nodeID: "NotViking"
                        },
                      ]
                    },
                    WizardPoint {
                      advanceState: "TheBeatles"
                      nodeID: "NotViking"
                    },
                  ]
                },
              ]
            }
          fadeInDur: 700ms
          canCancel: true
          canFinish: false
          backgroundNode:
            Group {
              content: [
                ImageView {
                  image: 
                    Image {
                      url: "{__DIR__}images/viking_jprep_background.png"
                    } 
                },
                ImageView {
                  image: 
                    Image {
                      url: "{__DIR__}images/viking_jprep_helmet.png"
                    } 
                },
              ]
            }
          content: [
            Group {
              var optionsNode:OptionsNode;
              transform: Translate.translate(50, 130);
              id: "Shaving"
              content: [
                optionsNode = OptionsNode {
                  heading: "How Do You Shave?"
                  options: [
                    "I shave with an electric razor",
                    "I shave with a safety razor",
                    "I shave with a straight razor",
                    "I shave with my sword",
                  ]
                  optionAdvanceStates: [
                    "ElectricRazor",
                    "SafetyRazor",
                    "StraightRazor",
                    "ShaveWithSword"
                  ]
                  action:
                    function():Void {
                      wizardRef.candidateAdvanceState = optionsNode.optionAdvanceState;  
                    }
                }
              ]
            },
            Group {
              var optionsNode:OptionsNode;
              transform: Translate.translate(50, 130);
              id: "DiscoveredAmerica"
              content: [
                optionsNode = OptionsNode {
                  heading: "Who Discovered America?"
                  options: [
                    "Christopher Columbus",
                    "Leif Ericson",
                    "The Beatles",
                  ]
                  optionAdvanceStates: [
                    "ChristopherColumbus",
                    "LeifEricson",
                    "TheBeatles",
                  ]
                  action:
                    function():Void {
                      wizardRef.candidateAdvanceState = optionsNode.optionAdvanceState;  
                    }
                }
              ]
            },
            Group {
              var optionsNode:OptionsNode;
              transform: Translate.translate(50, 130);
              id: "WearHelmetHowOften"
              content: [
                optionsNode = OptionsNode {
                  heading: "How Often Do You Wear Your Helmet?"
                  options: [
                    "I always wear it, even in bed",
                    "Only on special occasions",
                    "Once a week",
                    "Never - it gives me Helmet Hair"
                  ]
                  optionAdvanceStates: [
                    "AlwaysWearsHelmet",
                    "WearsHelmetOnOccasions",
                    "WearsHelmetOncePerWeek",
                    "NeverWearsHelmet",
                  ]
                  action:
                    function():Void {
                      wizardRef.candidateAdvanceState = optionsNode.optionAdvanceState;  
                    }
                }
              ]
            },
            Group {
              var optionsNode:OptionsNode;
              transform: Translate.translate(50, 130);
              id: "OwnWoodenBoat"
              content: [
                optionsNode = OptionsNode {
                  heading: "Do You Own a Wooden Boat?"
                  options: [
                    "No, mine is fiberglass",
                    "Of course it's made out of wood!",
                    "I don't own a boat",
                  ]
                  optionAdvanceStates: [
                    "FiberglassBoat",
                    "WoodenBoat",
                    "NoBoat",
                  ]
                  action:
                    function():Void {
                      wizardRef.candidateAdvanceState = optionsNode.optionAdvanceState;  
                    }
                }
              ]
            },
            HBox {
              id: "IsViking"
              transform: Translate.translate(50, 110);
              content: [
                ImageView {
                  image: 
                    Image {
                      url: "{__DIR__}images/viking_yes.png"
                    } 
                },
                VBox {
                  transform: Translate.translate(0, 20);
                  spacing: 40
                  content: [
                    Text {
                      textOrigin: TextOrigin.TOP
                      content: "Congratulations!"
                      fill: Color.WHITE
                      font: pageFont
                    },
                    Text {
                      textOrigin: TextOrigin.TOP
                      content: "You are a Viking!"
                      fill: Color.WHITE
                      font: pageFont
                    },
                  ]
                }
              ]
            },
            HBox {
              id: "NotViking"
              transform: Translate.translate(50, 110);
              content: [
                ImageView {
                  image: 
                    Image {
                      url: "{__DIR__}images/viking_no.png"
                    } 
                },
                VBox {
                  transform: Translate.translate(0, 20);
                  spacing: 40
                  content: [
                    Text {
                      textOrigin: TextOrigin.TOP
                      content: "Sorry! You are most"
                      fill: Color.WHITE
                      font: pageFont
                    },
                    Text {
                      textOrigin: TextOrigin.TOP
                      content: "likely not a Viking!"
                      fill: Color.WHITE
                      font: pageFont
                    },
                  ]
                }
              ]
            },
          ]
        },
        menuRef = MenuNode {
          translateX: bind stageRef.width / 2 - menuRef.getWidth() / 2
          translateY: bind stageRef.height - menuRef.getHeight() * 0.75
          buttons: [
            ButtonNode {
              imageURL: "{__DIR__}images/viking_jprep_btn_previous.png"
              enabled: bind wizardRef.canGoBack
              scale: 1.0
              action:
                function():Void {
                  wizardRef.goBack();
                }
            },
            ButtonNode {
              imageURL: "{__DIR__}images/viking_jprep_btn_next.png"
              enabled: bind wizardRef.canAdvance
              scale: 1.0
              action:
                function():Void {
                  wizardRef.advance();
                }
              },
            ButtonNode {
              imageURL: "{__DIR__}images/viking_jprep_btn_cancel.png"
              enabled: bind wizardRef.canCancel
              scale: 1.0
              action:
                function():Void {
                  wizardRef.reset();
                }
            },
            ButtonNode {
              imageURL: "{__DIR__}images/viking_jprep_btn_finish.png"
              enabled: bind wizardRef.atEndpoint
              scale: 1.0
              action:
                function():Void {
                  wizardRef.reset();
                }
            },
          ]
        }
      ]
    }
  closeAction:
    function():Void {
      System.exit(0);
    }
}

As shown above, one of the important features of the wizard is what I call its flow path. The flowPath attribute of the WizardNode class enables you to articulate all of the points in the wizard’s flow, as well as to articulate the navigation paths between points. Each point in a wizard’s flow path is represented by a WizardPoint instamce, and each navigation path between points is represented by an element in the nextPoints sequence attribute. The nextPoints attribute contains a sequence of WizardPoint instances, each of which holds an advanceState string attribute that enables navigation to that point. As you can see from the listing above, each WizardPoint instance also contains a nodeID attribute that matches the id attribute of a Node (held within the content sequence attribute of the WizardNode). Looking at that attribute in the listing above, you’ll notice that all of the wizard pages (Node instances) are held in that sequence. As you’ll soon see, it is the responsibility of the WizardNode class to cause the associated Node instance to show after navigating to a given wizard point.

Another important attribute in the WizardNode class is backgroundNode. This attribute enables you to create a background that will show behind each page of the wizard. Take a look at the WizardNode.fx listing below, and notice that it extends the DeckNode class, which contains the functionality of switching between Node instances by their id attributes.


/*
 *  WizardNode.fx - 
 *  A custom node that functions as a "wizard", progressively displaying
 *  UI nodes according to a supplied "flow".  Each node, typically as a result
 *  of user interaction, may set a state that determines which node of the flow
 *  will be displayed. 
 *
 *  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.lang.*;
import java.lang.System;

/*
 *  A custom node that functions as a "wizard", progressively displaying
 *  UI nodes according to a supplied "flow".  Each node, typically as a result
 *  of user interaction, may set a state that determines which node of the flow
 *  will be displayed. 
 */
public class WizardNode extends DeckNode { 

  /**
   * The flow of the wizard, expressed as a series of related
   * WizardPoint instances
   */
  public attribute flowPath:WizardPoint;
  
  /**
   * The current path that the user is taking through the wizard, expressed as
   * a sequence of WizardPoint instances
   */
  public attribute currentPath:WizardPoint[];

  /**
   * Determines whether the user can advance to the next wizard page
   */
  public attribute canAdvance:Boolean = false;

  /**
   * Determines whether the user can go back to the previous wizard page
   */
  public attribute canGoBack:Boolean = bind sizeof currentPath > 1;

  /**
   * Determines whether the user can cancel out of this wizard
   */
  public attribute canCancel:Boolean;

  /**
   * Determines whether the user can finish this wizard
   */
  public attribute canFinish:Boolean;

  /**
   * Determines whether the user is at and end point in the wizard,
   * which is defined by not having any nextPoints
   */
  public attribute atEndpoint:Boolean = bind
    (sizeof currentPoint.nextPoints) == 0;

  /**
   * A reference to the WizardPoint instance that the user is currently on
   */
  private attribute currentPoint:WizardPoint on replace {
    if (currentPoint != null) {
      visibleNodeId = currentPoint.nodeID;
      canAdvance = false;
    }
  };

  postinit {
    reset();
  }

  /**
   * Resets the wizard to its initial state
   */
  public function reset():Void {
    currentPoint = flowPath;
    currentPath = [];
    insert currentPoint into currentPath;
  }

  /**
   * The requested advanceState to be navigated to from this point.  If this is
   * a valid advanceState, the canAdvance attribute is set to true, otherwise
   * it is set to false
   */
  public attribute candidateAdvanceState:String on replace {
    if (candidateAdvanceState != "") {
      var newPoint = currentPoint.getPointByAdvanceState(candidateAdvanceState);
      canAdvance = newPoint != null;
    }
  }

  /**
   * Request that the current WizardPoint be changed to the candidateAdvanceState
   * TODO: Consider throwing an exception if unsuccessful
   */
  public function advance():Void {
    var newPoint = currentPoint.getPointByAdvanceState(candidateAdvanceState);
    if (newPoint != null) {
      currentPoint = newPoint;
      
      // Put this WizardPoint on the current path through the flow path
      insert currentPoint into currentPath;
    }
  }

  /**
   * Request that the current WizardPoint be changed to the one that precedes
   * it in the current path that the user has taken through the flow.  In other
   * words, go back.
   * TODO: Consider throwing an exception if unsuccessful
   */
  public function goBack():Void {
    var idx = sizeof currentPath - 1;
    if (idx > 0) { 
      delete currentPath[idx];    
      // Put this WizardPoint on the current path through the flow path
      currentPoint = currentPath[idx - 1];
    }
  }
}  


Here is the listing for the WizardPoint class mention previously:

/*
 *  WizardPoint.fx - 
 *  A point in the flow of a "wizard", that is related to a given wizard page
 *  (graphical Node).  Wizard point instances are related to each other in
 *  a network of points that determines the potential flows within the wizard.
 *
 *  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 java.lang.System;

/**
 *  A point in the flow of a "wizard", that is related to a given wizard page
 *  (graphical Node).  Wizard point instances are related to each other in
 *  a network of points that determines the potential flows within the wizard.
 */
public class WizardPoint { 

  /**
   * The id attribute of the Node (wizard page) to be displayed
   */
  public attribute nodeID:String;

  /**
   * A String that controls the condition by which this WizardPoint may be
   * landed upon. If advanceState is an empty string, then this point may be 
   * landed upon by default
   */
  public attribute advanceState:String;

  /**
   * The WizardPoint instances that may be navigated to from this point.
   */
  public attribute nextPoints:WizardPoint[]; //TODO make sure that this creates an empty sequence, not a null reference

  /**
   * Returns the WizardPoint instance that may be navigated to from this point
   * for a given advanceState
   * TODO: Decide whether this is needed
   */
  public function getPointByAdvanceState(advanceState:String):WizardPoint {
    var points = 
      for (point in nextPoints where point.advanceState == advanceState) point;
    if (sizeof points > 0) points[0]
    else null;
  }
}  


Notice also in the WizardNodeExampleMain.fx listing shown previously that I'm using an OptionsNode custom node in some of the wizard pages. This class makes it quick and easy to articulate a set of mutually exclusive options as well as a heading for the list of options. It was designed for use with this wizard functionality, as it is capable of holding an advanceState for each of the options and storing the advanceState for an option that the user selected. Take a look at the listing for the OptionsNode.fx class below:

/*
 *  OptionsNode.fx - 
 *  A custom node that enables the user to choose 
 *  from a list of options.
 *
 *  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.ext.swing.*;
import javafx.scene.*;
import javafx.scene.paint.*;
import java.lang.System;

public class OptionsNode extends CustomNode { 

  /**
   * A header for the options
   */
  public attribute heading:String;
    
  /**
   * A sequence containing the choice of options
   */
  public attribute options:String[];
  
  /**
   * A sequence containing the WizardNode advanceState associated with the
   * options.
   */
  public attribute optionAdvanceStates:String[];
  
  /**
   * The font to use for the heading
   */
  public attribute headingFont = 
    Font {
      name: "Sans serif"
      size: 24
    };
  
  /**
   * The font to use for the options
   */
  public attribute optionsFont = 
    Font {
      name: "Sans serif"
      size: 22
    };
  
  /**
   * The color to use for the text
   */
  public attribute textColor = Color.rgb(253, 253, 253);
    
  /**
   * The advanceState associated with a selected option
   */
  public attribute optionAdvanceState:String;
    
  /**
   * The action function attribute that is executed when the
   * a selection is made
   */
  public attribute action:function():Void;
   
  /**
   * Reset the selected radio buttons
   */
  public function reset():Void {
    toggleGroup.clearSelection(); 
  }

  /**
   * Called when this page is shown in the wizard.  Reset the radio button
   * selections
   */
  public function onShow():Void {
    reset(); 
  }

  /**
   * A reference to the ToggleGroup associated with the RadioButton instances
   */ 
  private attribute toggleGroup:ToggleGroup = ToggleGroup{}; 
   
  /**
   * Create the Node
   */
  public function create():Node {
    ComponentView {
      component:
        GridPanel {
          background: 
            Color.rgb(255, 255, 255, 0.0) // Transparent
          rows: bind sizeof options + 1
          columns: 1
          content: bind [
            Label {
              text: heading
              font: headingFont
              foreground: textColor
            },
            for (option in options)
              RadioButton {
                var idx:Integer = indexof option;
                toggleGroup: toggleGroup
                text: option
                font: optionsFont
                foreground: textColor
                action:
                  function():Void {
                    optionAdvanceState = optionAdvanceStates[idx];
                    action();  
                  }
              }
          ]
        }
    }
  }
}  


Room for Improvement

There is at least one area of functionality in which I'd like to make an improvement:  Currently, a wizard page is a subclass of Node, but it may be good to define a custom node (perhaps named WizardPageNode) that has life-cycle functions and some helper functionality.  I welcome your thoughts on this, as well as any other input or questions that you have.  In either case, please post a comment.

Also, if you're at JFocus, please introduce yourself as I'd like to meet you!

Thanks,
Jim Weaver
JavaFXpert.com

September 04, 2008

JavaFX Applets Meet Google Chrome

In the JFX Custom Nodes category of this blog, graphics designer Mark Dingman of Malden Labs and I have been collaborating on an imaginary Sound Beans application. This category contains a growing series of posts in which we are demonstrating how to create JavaFX UI custom controls.  This series also provide a case study in how a graphics designer and an application developer can work together effectively in developing JavaFX applications.  Today I'd like to highlight the recent Google Chrome browser announcement by showing you how to create and run a JavaFX applet in Chrome.  Here's a screenshot of the TableNode example from an earlier post running as a JavaFX applet in Chrome:


TableNodeExampleApplet

To try this out, first obtain Google Chrome and install it.  Then obtain Java SE 6 Update 10 and install it as well.  By the way, installing Java SE 6 update 10 will enable this JavaFX applet to run on Firefox 3 and Internet Explorer as well.  Go ahead and run this example, being sure to scroll the custom TableNode control and to click on its rows.  Also, select the Burn icon and move the slider to demonstrate the custom ProgressNode control.


Looking at the Code

In addition to the ButtonNode.fx, MenuNode.fx, DeckNode.fx, ProgressNode.fx and TableNode.fx files from previous posts in this series, you'll need the following files:

TableNodeExampleApplet.fx:

/*
 *  TableNodeExampleApplet.fx -
 *  An example of using the TableNode custom node in an Applet.  It also
 *  demonstrates the ProgressNode, 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 and applets in JavaFX
 */
package com.javafxpert.table_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.Object;
import java.lang.System;
import com.javafxpert.custom_node.DeckNode;
import com.javafxpert.custom_node.TableNode;
import com.javafxpert.custom_node.ProgressNode;
import com.javafxpert.custom_node.ButtonNode;
import com.javafxpert.custom_node.MenuNode;
import com.javafxpert.table_node_example.model.TableNodeExampleModel;

var deckRef:DeckNode;

Application {
  var model = TableNodeExampleModel.getInstance();
  var stageRef:Stage;
  var menuRef:MenuNode;
  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
            VBox {
              var tableNode:TableNode
              id: "Play"
              spacing: 4
              content: [
                Group {
                  content: [
                    ImageView {
                      image:
                        Image {
                          url: "{__DIR__}images/playing_currently.png"
                        }
                    },
                    Text {
                      textOrigin: TextOrigin.TOP
                      content: bind "{tableNode.selectedIndex}"
                      font: Font {
                        size: 24
                      }
                    }
                  ]
                },
                tableNode = TableNode {
                  height: 135
                  rowHeight: 25
                  rowSpacing: 2
                  columnWidths: [150, 247, 25, 70]
                  tableFill: Color.BLACK
                  rowFill: Color.#1c1c1c
                  selectedRowFill: Color.#2d2d2d
                  selectedIndex: -1
                  vertScrollbarWidth: 20
                  vertScrollbarFill: LinearGradient {
                    startX: 0.0
                    startY: 0.0
                    endX: 1.0
                    endY: 0.0
                    stops: [
                      Stop {
                        offset: 0.0
                        color: Color.#0b0b0b
                      },
                      Stop {
                        offset: 1.0
                        color: Color.#343434
                      }
                    ]
                  }
                  vertScrollbarThumbFill: Color.#efefef
                  content: bind
                    for (obj in model.playlistObjects) {
                      if (obj instanceof String)
                        Text {
                          textOrigin: TextOrigin.TOP
                          fill: Color.#b7b7b7
                          content: obj as String
                          font:
                            Font {
                              size: 11
                            }
                        }
                      else if (obj instanceof Image)
                        ImageView {
                          image: obj as Image
                        }
                      else
                        null
                    }
                  onSelectionChange:
                    function(row:Integer):Void {
                      System.out.println("Table row #{row} selected");
                    }
                }
              ]
            },
            // 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.#d3d3d3
                    },
                    ProgressNode {
                      width: 430
                      height: 15
                      progressPercentColor: Color.#bfdfef
                      progressTextColor: Color.#0c1515
                      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.#00c0ff
                            },
                            Stop {
                              offset: 0.20
                              color: Color.#00acea
                            },
                            Stop {
                              offset: 1.0
                              color: Color.#0070ae
                            },
                          ]
                        }
                      barFill:
                        LinearGradient {
                          startX: 0.0
                          startY: 0.0
                          endX: 0.0
                          endY: 1.0
                          stops: [
                            Stop {
                              offset: 0.0
                              color: Color.#707070
                            },
                            Stop {
                              offset: 1.0
                              color: Color.#585858
                            },
                          ]
                        }
                      progress: bind model.burnProgressPercent / 100.0
                    },
                    ComponentView {
                      component:
                        FlowPanel {
                          background: Color.BLACK
                          content: [
                            Label {
                              text: "Slide to simulate burn progress:"
                              foreground: Color.#d3d3d3
                            },
                            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";
                }
            },
          ]
        }
      ]
    }
}


Note that the Application class has a stage attribute just as the Frame had in previous examples.  Here's the TableNodeExamplePage.html file that you'll open in your browser.  The draggable param, by the way, enables that neat "pull the applet out of the browser" trick that I'll show you in a bit:

<html>
  <body bgcolor="Black">
    <center>
      <applet code="javafx.application.Applet" width=500 height=400
          archive="javafxrt.jar, Scenario.jar, javafxgui.jar, javafx-swing.jar, TableNodeExample.jar">
        <param name="ApplicationClass" value="com.javafxpert.table_node_example.ui.TableNodeExampleApplet"/>
        <param name="jnlp_href" value="TableNodeExampleApplet.jnlp"/>
        <param name="draggable" value="true">
      </applet>
    </center>
  </body>
</html>


Finally, here's the Java Web Start TableNodeExampleApplet.jnlp file that is used by the HTML file above:

<?xml version="1.0" encoding="UTF-8"?>
<jnlp spec="1.0+" href="TableNodeExampleApplet.jnlp">
  <information>
    <title>TableNodeExampleApplet</title>
    <vendor>JMentor</vendor>
    <description>TableNodeExampleApplet</description>
    <description kind="short">TableNodeExampleApplet</description>
    <homepage href="http://jmentor.com"/>
    <offline-allowed />
  </information>
  <security>
    <all-permissions/>
  </security>
  <resources>
    <property name="jnlp.packEnabled" value="true"/>
    <j2se version="1.6+" href="http://java.sun.com/products/autodl/j2se" java-vm-args="-Xmx800m" />
    <!--j2se version="1.6+" java-vm-args="-Xmx800m" /-->
    <jar href="TableNodeExample.jar" main="true" download="eager"/>
    <jar href="Scenario.jar"/>
    <jar href="javafxrt.jar"/>
    <jar href="javafxgui.jar"/>
    <jar href="javafx-swing.jar"/>
  </resources>
  <applet-desc
    name="TableNodeExampleApplet"
    main-class="javafx.application.Applet"
    width="500"
    height="400">
  </applet-desc>
</jnlp>


Dragging the Applet out of the Browser and onto the Desktop

As shown in the following screenshot, one of the cool features of Java SE 6 update 10 is that you can drag a Java or JavaFX applet out of the browser and onto the desktop.  By default, you press the Alt key while dragging the applet:

TableNodeExampleApplet-Drag

Here is our JavaFX Applet living happily on the desktop after the browser has been closed, and the user has selected the Burn page:

TableNodeExampleApplet-Dragged

Google Chrome will be a Driving Force for RIA

According to Google, Java SE 6 Update 10 is the version that must be used in order to run Java in the Chrome browser.  As I've mentioned previously, one of the objectives of Java SE 6 Update 10 is to solve the JRE and Java/JavaFX deployment issues.  Because Google Chrome is destined to be a great, cross-platform browser, and because it requires the version of Java that makes rich-client Java/JavaFX programs feasible, this will increase the adoption rate of JavaFX applets and applications.

Thanks Google!

Jim Weaver
JavaFXpert.com

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.