Thursday Oct 08, 2009

Florida Linux Show, Oct 24 - Orlando - JavaONE Best Sellers eBook - and updates

I will be doing a presentation on JavaFX to the Florida Linux Show on Oct 24 in Orlando. If you are interested in attending here is the link, Florida Linux Show. I have developed a JavaFX Slide Show application that combines JavaFX with the the pdf-renderer project at java.net. Eventually, I will be posting this code for all to see. Here is a preview:

Also,  our book, "JavaFX - Developing Rich Internet Applications", has been included in the anthology, "The 2009 JavaONE Best Sellers".  This is a free eBook and for more information check out informIT.

 I recently checked in a Tree Control to the JFXtras project. I will be posting a blog on this control in the next few days. There is a lot happening in JFXtras these days and if you have not checked it out, I highly recommend it. A new release is in the works with many added controls and new features.

jim 

Thursday Aug 06, 2009

Particle Emitter Animation in JavaFX

I was a little intrigued with emitter animation effects, like water flowing out of a shower head or snow flakes falling to the ground (see http://en.wikipedia.org/wiki/Particle_system). So I decided to play around and create a reusable Emitter in JavaFX based on the javafx.animation.transition.Transition  class. A view of the application is shown in the following screen capture:


There are three basic classes in this implementation. "Particle" represents the particle that is emitted, "Emitter" generates the particles, and "ParticleTransition" implements the Particle's animation.

First lets examine the Particle.

The Particle is a Resizable CustomNode, that is it extends javafx.scene.layout.Resizable  and javafx.scene.CustomNode. To support the Resizable aspect, Particle implements the following code:

    public override function getPrefHeight(h: Number) : Number {
         5.0 // default height
    }
    public override function getPrefWidth(w: Number) : Number {
        5.0 // default width
    }

    override var layoutBounds = bind lazy {
        BoundingBox {
            minX: 0
            minY: 0
            width: this.width
            height: this.height
        }
    }

    postinit {
        if (not isInitialized(width)) then width = getPrefWidth(-1);
        if (not isInitialized(height)) then height = getPrefHeight(-1);
    }

For the actual particle node, there is a function variable, createParticle.

public var createParticle: function(particle:Particle) : Node;

This is used in the create():Node overloaded function from CustomNode, to create the visible node for the Particle.

    public override function create(): Node {
        return Group {
            content: createParticle(this)
        };
    }

This allows any type of Node to be created to represent a particle. In our main test application, we used a javafx.scene.shape.Ellipse

A particle has a lifespan and a current age defined with javafx.lang.Duration objects. When the lifespan is expired, the Particle will die by invoking its die() function. The default implementation of the die() function is to remove the particle from the parent group, thusly the particle is removed from the scene.

While the particle is alive, there is an "interpolate" variable that holds a function that manipulates the Particle as it ages. In the default implementation, the Particle is moved based on its velocity.

    public var velocity: Point2D = Point2D {  // Pixels per millisecond
        x: 1
        y: 1
    } 
    public var interpolate: function(particle:Particle):Void = function(particle:Particle) {
        var millis = age.toMillis();
        translateX = millis \* velocity.x;
        translateY = millis \* velocity.y;
    }

Next, the Emitter class causes Particles to be emitted on a periodic basis, controlled by a javafx.animation.Timeline  object.  For each period, the emitter will create a certain number of Particles ± a variance. Each Particle will also have variance in its initial location relative to the emitter, as well as variances in velocity and lifespan. The variance is a random distribution across a variance range. By using variances, a randomness is introduced to the emitter effect.

For each period, the Emitter calls the "emit()" function. Each time, it creates a ParticleTransition object that represents a transition timeline, it then adds the generated Particles to the transition. Lastly, it starts the transition animation by calling the ParticleTransition's play() function.

    var transitions: ParticleTransition[];
    var count: Integer;
    function emit():Void {
        var eCount = Math.round(getVariance(emitCountVariance) \* emitCount);
        var pt:ParticleTransition = ParticleTransition {
            id: count++
            action: function() {
                delete pt from transitions;
            }

        }
        for(i in [0..<eCount]) {
            var particle = createParticle(this);
            particle.layoutX = this.layoutX + getVariance(initialLocationVariance);
            particle.layoutY = this.layoutY + getVariance(initialLocationVariance);
            insert particle into group.content;
            insert particle into pt.particles;
        }
        insert pt into transitions;
        pt.play();
    }

The ParticleTransition holds a sequence of Particles and  overloads the "rebuildKeyFrames()" function from the javafx.animation.transition.Transition  class. The Transition class contains the animation framework that loads the KeyFrames and controls the animation play.

public class ParticleTransition extends Transition {

    /\*\*
     \* holds the particles that participate in the transition
     \*/
    public var particles: Particle[] on replace oldValues[lo..hi] = newValues {
        markDirty();
    }

    /\*\*
     \* Holds a unique id for this Transition
     \*/
    public-init var id: Integer;

    protected override function rebuildKeyFrames():KeyFrame[] {
        var sortedParticles = Sequences.sort(particles, Comparator {
            public override function compare(a: Object, b: Object):Integer {
                var ap = a as Particle;
                var bp = b as Particle;
                ap.lifespan.compareTo(bp.lifespan);
            }
        });

        [
            KeyFrame {
                time: 0ms
                values: for(p in sortedParticles) {
                    (p as Particle).alpha => 0.0 tween interpolator;
                }
            },
            for(p in sortedParticles) {
                KeyFrame {
                    time: (p as Particle).lifespan
                    values: [(p as Particle).alpha => 1.0 tween interpolator]
                    canSkip: false
                    action: function() {
                        if(indexof p >= sizeof sortedParticles - 1)
                            action();
                        (p as Particle).die();
                    }
                }
             }
        ]
    }
} 

When building the KeyFrames, the particles are sorted from shortest to longest lifespan so that the keyframes are built in time order.

The "Main" class kicks off the test by creating an Emitter object, including it in the SceneGraph, and starting its animation. The "createParticle" function on the Emitter creates a new Particle that defines itself as an Ellipse shape. The emitter is defined as:

def emitter = Emitter {
    layoutX: 20
    layoutY: 20
    initialLocationVariance: 5
    createParticle: function(emitter: Emitter) {
        Particle{
           velocity: Point2D {
               x: emitter.velocity.x + emitter.getVariance(emitter.velocityVariance.x)
               y: emitter.velocity.y + emitter.getVariance(emitter.velocityVariance.y)
           }
           width: 5 + emitter.getVariance(2.0)
           height: 4 + emitter.getVariance(2.0)
           lifespan: 10s + 5s.mul(emitter.getVariance(1000.0))
           createParticle: function (particle: Particle):Node {
                var rx = bind particle.width/2.0 - emitter.getVariance(1.0);
                var ry = bind particle.height/2.0 - emitter.getVariance(1.0);
                Ellipse {
                    centerX: bind rx
                    centerY: bind ry
                    radiusX: bind rx
                    radiusY: bind ry
                    fill: UtilsFX.deriveColor(Color.SKYBLUE, emitter.rand.nextFloat())
                    effect: MotionBlur {
                        angle: 45
                        radius: bind rx
                    }
                }
           }
       }
    }
} 

The entire NetBeans project can be downloaded at

ParticleEmitter project.


Thursday Jul 30, 2009

JavaFX - JMS Unexpected Null Pointer Exception.

In the book, JavaFX - Developing Rich Internet Applications, I wrote a Code Recipe on how to include a JMS feed into a JavaFX application. The basic premise is that when the JMS onMessage method is called on the javax.jms.MessageListener interface, the calling thread will be from the JMS framework, so this message needs to be pushed over to the main JavaFX thread using com.sun.javafx.runtime.Entry.deferAction( Runnable ). Here is the example from the book from Subscriber.java:

@Override
public void onMessage(final Message msg) {
    try {
            // must run this on the JavaFX Main thread
            // If you don't you will eventually get exceptions
            // in the JavaFX code.
            Entry.deferAction( new Runnable() {
                @Override
                public void run() {
                    fxListener.onMessage(msg);
                }
            } );

    } catch (Exception ex) {
            Logger.getLogger(Subscriber.class.getName()).log(Level.SEVERE, null, ex);
    }
}

This all runs fine on my machine when I spit out messages, 1 per millisecond. However, I was working with a colleague here at Sun who was using this code pattern with a real live log server, and when he cranked it up, he started to get Null Pointer Exceptions when he fetched from  the Message object within the JavaFX main thread.

This perplexed us for a while, and on the intuition that the JMS framework may have been reusing the Message object on subsequent messages, we inserted logic in the Java onMessage() code block to fetch the message contents before pushing them into the JavaFX main thread. Voila, the Null Pointer Exceptions stopped. Evidently, we were encountering a race condition where the JMS Framework was reusing or clearing the Message object faster than the JavaFX thread could consume it.  This behavior from the JMS framework was unexpected, but I guess it is an optimization for performance.

To overcome this race condition, it is necessary to make a copy of the message data prior to the javax.jms.MessageListener.onMessage() method returning. As a result, I have now modified the example to look like this:

    if(msg instanceof TextMessage ) {
        final Message copy = topicSession.createTextMessage(((TextMessage)msg).getText());⁞
        Entry.deferAction( new Runnable() {
            @Override
            public void run() {
                fxListener.onMessage(copy);
            }
        } );
    }

I will be updating the example from the book. But the lesson learned from this is to be wary when using frameworks that you have no control over.



Sunday Jul 05, 2009

How to use JavaFX in your Swing Application - JavaFX 1.2 version.

At one of my sessions showing off JavaFX, I was asked the question, how can I run JavaFX in my Swing Application? At first, I asked why do you want to do this? The answer was we have built up the Swing application over many years and cannot rewrite it all at once, but we would like to take advantage of  JavaFX's features. This sounded reasonable to me.

So I did some investigation and found that there is a blog about this at http://blogs.sun.com/javafx/entry/how_to_use_javafx_in. However, this only covered JavaFX 1.1. Now, that JavaFX 1.2 is out, this is no longer applicable. Here is how to do it in JavaFX 1.2.

We start with the obligatory warning, this is only for the Desktop profile, and do not try this at home unless you are a highly trained professional. This, like the previous way to do this, is a hack. It uses private APIs in JavaFX and is subject to change with any new release.

First, we need to pull the underlying Swing Component out from the Scene class. To do this, we need to use JavaFX reflection from a Java class. This is done by passing in the name of the Scene class, creating an object for it via reflection, then locating its underlying Swing implementation, and finally pulling out the Scenegraph JSGPanel, that is a Swing Component. The following listing shows how to do this.


package scene;

import com.sun.javafx.tk.swing.SwingScene;
import com.sun.scenario.scenegraph.JSGPanel;
import javafx.reflect.\*;
import javafx.reflect.FXLocal.ObjectValue;

public class LoadScene {
    private static FXLocal.Context context = FXLocal.getContext();

    public static JSGPanel loadScene(String classname) {
        FXClassType classRef = context.findClass(classname);
        FXLocal.ObjectValue obj = (ObjectValue) classRef.newInstance();
        FXFunctionMember getPeer = classRef.getFunction("impl_getPeer");
        FXLocal.ObjectValue peer = (ObjectValue) getPeer.invoke(obj);
        SwingScene scene = (SwingScene)peer.asObject();

        return scene.scenePanel;
    }
}

Next, to use this in a Swing application, we create the Swing Components normally, leaving a JPanel that will hold the JSGPanel that represents the JavaFX scene. An example using a JavaFX scene, "scene.MyScene", that runs a simple animation is:


public class TheFrame extends javax.swing.JFrame {

    /\*\* Creates new form TheFrame \*/
    public TheFrame() {
        initComponents();⁞
        jPanel1.add(LoadScene.loadScene("scene.MyScene"), BorderLayout.CENTER);
    }⁞
                       
    private void initComponents() {

        jPanel1 = new javax.swing.JPanel();

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
        setTitle("JFrame - JavaFX Panel Test");
        getContentPane().setLayout(new java.awt.FlowLayout());

        jPanel1.setLayout(new java.awt.BorderLayout());
        getContentPane().add(jPanel1);

        pack();
    }                

    public static void main(String args[]) {
        java.awt.EventQueue.invokeLater(new Runnable() {
            public void run() {
                new TheFrame().setVisible(true);
            }
        });
    }                   
    private javax.swing.JPanel jPanel1;                  
}

To complete the exercise, we need to add the JavaFX jar files (including javafxc.jar) to the classpath, then use "java" to start the application.

Here is the picture of the JavaFX scene running within the Swing JFrame. This simple JavaFX application shows an animation of "Hello World" moving across the scene.

Swing hosting JavaFX scene

I have contributed an implementation based on this class to the JFXtras project at http://code.google.com/p/jfxtras/. It is now included in JFXtras release 0.5 and the class to use is org.jfxtras.scene.SceneToJComponent.

Monday Jun 08, 2009

JavaFX Async Task

Today someone asked how to use the javafx.async package. The javafx.async package is useful if you have some background work to do that you don't want to run on the main JavaFX thread. Perhaps you are doing some serious number crunching, or maybe some file IO and if you did this on the main thread, the user would be blocked from doing anything and features like animation would scream to a halt. To illustrate how to use the async package, here is a trivial implementation that merely spawns a task that sleeps 5 seconds. Right before the sleep starts and right after the sleep ends, a message is sent back to the JavaFX main routine and displayed on the screen.

First, you need to create a Java class that implements javafx.async.RunnableFuture. RunnableFuture can only be implemented in a Java class and will throw a runtime exception if you try to use it directly on a JavaFX class. The following shows a minimal RunnableFuture that merely sleeps for 5 seconds.

// JavaSleepTask.java
public class JavaSleepTask implements RunnableFuture {
    @Override
    public void run() throws Exception {
        Thread.sleep(5000);
    }
}

Next you need to create a JavaFX class that extends javafx.async.JavaTaskBase and implements the create() : RunnableFuture function. The create() function will instantiate the JavaSleepTask object we just created in Java. For example,

//SleepTask.fx
public class SleepTask extends JavaTaskBase {
    protected override function create() : RunnableFuture {
        new JavaSleepTask();
    }
}
 
  

Lastly, to use the SleepTask you need to create an instance of it in JavaFX and call its start() function:

//Main.fx
var sleepTask =  SleepTask { };

sleepTask.start();

Under the cover, this creates a separate Thread of execution from the main JavaFX thread and in turn invokes the run() method on the JavaSleepTask object.

Now, what if our JavaFX application needs to be updated periodically while the JavaSleepTask object is performing its work. Perhaps, the task needs to crunch a lot of numbers, and you need to periodically be updated with progress.

A way to do this is to create a Java interface that acts as a callback mechanism. Let's say the interface looks like FXListener.java:

//FXListener.java
public interface FXListener {
    public void callback(String msg);
}

To use this, we first modify the JavaSleepTask.java file. We add a private attribute, listener, a constructor to save the passed in listener, and implement a postMessage() method to actually do the callback on the FXListener.Notice in the postMessage() method, we had to use the JavaFX  com.sun.javafx.runtime.Entry.deferAction() static method. JavaSleepTask will not be running on the main JavaFX thread, so it is necessary to actually make the call on the FXListener call back by pushing it onto the main JavaFX thread, using deferAction(). If you do not do this you will most likely get unpredictable results and exceptions.

//JavaSleepTask.java
import java.util.Date;
import com.sun.javafx.runtime.Entry;
import javafx.async.RunnableFuture;
 public class JavaSleepTask implements RunnableFuture {
    FXListener listener;
    public JavaSleepTask(FXListener listener) {
        this.listener = listener;

    }

    @Override
    public void run() throws Exception {
        postMessage("Start Task @ "+ new Date());
        Thread.sleep(5000);
        postMessage("End Task @ "+ new Date());
    }

    public void postMessage(final String msg) {
        Entry.deferAction(new Runnable() {
            public void run() {
                listener.callback(msg);
            }
        });
    }
}

Next, we modify the JavaFX SleepTask to add a listener variable that is passed to the constructor of the JavaSleepTask object.

//SleepTask.fx
import javafx.async.JavaTaskBase;
import javafx.async.RunnableFuture;

public class SleepTask extends JavaTaskBase {
    public-init var listener: FXListener;

    protected override function create() : RunnableFuture {
        new JavaSleepTask(listener); 
    }
}

Lastly, we modify our JavaFX application that uses the SleepTask. When the callback is invoked, the message is saved in a local message variable that is later bound to a Text object.

// Main.fx
var message: String;
var textFill: Color = Color.BLACK;
var count = 0;
var colors = [ Color.BLUE, Color.RED];

var listener = FXListener {
     override function callback(msg: String) : Void {
         message = msg;
         textFill = colors[count mod 2];
         count++;
     }
 }

var sleepTask =  SleepTask {
    listener: listener
};

sleepTask.start();

To actually see the change, we create a Stage with a Text shape, that displays the latest message while alternating the color.

//Main.fx
Stage {
    title : "Async Task Example"
    scene: Scene {
        width: 500
        height: 200
        content: [
            Text {
                font : Font { size: 24 }
                fill: bind textFill
                layoutX: 10
                layoutY: 30
                content: bind message
            }
        ]
    }
}

When this is first run, you see the message right before the sleep.

Start Date

Then 5 seconds later the window is updated with the End message:

End Message

The full source for this example is here.

Friday May 29, 2009

JavaFX Borders


When I am not busy with my day job or writing books on JavaFX, JavaFX - Developing Rich Internet Applications, I spend my spare time helping out on the open source JFXtras project, http://code.google.com/p/jfxtras/ . One part I have contributed is the borders package. The idea started off trying to duplicate some of the borders found in Swing. However, finding it relatively easy to implement the Border classes in JavaFX, the types of Borders started to grow.

The following screen snapshot shows the current set of  borders:

Border Ensemble

To create a border just import the Border package from the JFXtras project and then include it in your scenegraph. For example, to implement the PipeBorder from the ensemble above, the code looks like:

import org.jfxtras.scene.border.PipeBorder;
import javafx.scene.image.ImageView; 
import javafx.scene.image.Image;
def image = Image {
    url: "{__DIR__}images/Mona_Lisa.jpg"
    width: 100
    height: 150
    preserveRatio: true
};
var pipeBorder  = PipeBorder {
    content: ImageView { image: image }
}; 

All the borders are created as JavaFX Controls and have corresponding Skins that allow you to modify their appearance either programatically or via CSS Style sheets. For the PipeBorder example, it is possible to have the pipe raised or lowered. A lowered pipe has its raised variable set to false and reverses the coloring of the border. This ends up looking like:

Pipe Border Lowered

You can also control the coloring of the border through CSS Style like statements. In the above Ensemble, the FrameBorder used such a statement to get the coral colored border:

VBox {
   spacing: 5
   nodeHPos: HPos.CENTER
   content: [
      FrameBorder {
          style: "background-fill: coral;"
          content: ImageView { image: image }
      },
      Label { text: "FrameBorder" }
   ]
}

If we change this to another color, say aquamarine, we get the following:

Frame Border

One of the more interesting borders is the ShapeBorder.

Shape Border

This uses any JavaFX Shape object as a clip over the underlying content. In this case, the Mona Lisa image. This example uses the JFXtras Star2 Shape class.

import org.jfxtras.scene.border.ShapeBorder;
import org.jfxtras.scene.shape.Star2;

VBox {
    spacing: 5
    nodeHPos: HPos.CENTER
    content: [
        ShapeBorder {
            var anode: Node;
            shape: Star2 {
                centerX: bind anode.layoutBounds.width/2
                centerY: bind anode.layoutBounds.height/2
                outerRadius: bind anode.layoutBounds.width/2
                innerRadius: bind anode.layoutBounds.width/6
                count: 5
            }
            content: anode = ImageView { image: image }
            },
        Label { text: "ShapeBorder" }
   ]
}

As you can guess, there are way too many options to explain in a short blog, but I hope I have peaked your interest to check out the JFXtras project.

You can get the source code for the above examples from here and, of course, the Mona Lisa from here.

Next week is JavaONE and my schedule is already full to the max. See y'all there.


Thursday Apr 30, 2009

JavaFX - Developing Rich Internet Applications released on Safari

Our new  book on JavaFX, JavaFX - Developing Rich Internet Applications, has just been released on Safari @ http://my.safaribooksonline.com/9780137013524 .  This includes coverage for JavaFX 1.1 with some of the new features that will be in the upcoming JavaFX release. We are still on schedule for publishing the print copy for JavaONE, we'll see you there.

Right now I am working on some of the new JavaFX features and will include code samples for the new features on the book's web site. Check  http://jfxbook.com for more updates as the new JavaFX release is finalized.



Saturday Mar 21, 2009

"To init or not to init - That is the question"

JavaFX has two block functions called "init" and "postinit". The JavaFX reference documentation defines these as "The initBlock is an optional block of code which is executed as the final step of instance initialization." and "The postInitBlock is an optional block of code which is executed after instance initialization has completed." Personally, I have found these to be confusing. When should I use an init block vs a postinit block? What is the state of the instance variables when these blocks are executed? How do they relate to super classes?

I wrote a simple program to explore some of these issues. This includes 3 classes, each extending the previous class.

class A {
    var a: Integer on replace {println("A.a = {a}");}
    init {
        println("A: init a = {a}");
    }
    postinit {
        println("A: postinit a = {a}");
    }
}

class B extends A {
    var b: Integer on replace {println("B.b = {b}");}
    init {
        println("B: init a = {a}, b = {b}");
    }
    postinit {
        println("B: postinit a = {a}, b = {b}");
    }
}

class C extends B {
    var c: Integer on replace {println("C.c = {c}");}
    override var a on replace {println("override C.a = {a}");}
    init {
        println("C: init a = {a}, b = {b}, c = {c}");
    }
    postinit {
        println("C: postinit a = {a}, b = {b}, c = {c}");
    }
}

C{
    a: 5
    b: 10
    c:15
};

When this program executes it prints out the following:

A.a = 5
override C.a = 5
B.b = 10
C.c = 15
A: init a = 5
B: init a = 5, b = 10
C: init a = 5, b = 10, c = 15
A: postinit a = 5
B: postinit a = 5, b = 10
C: postinit a = 5, b = 10, c = 15

What this shows is the order of initialization. First the instance variables from all the classes are initialized, followed by the "init" block for each class, then followed by the "postinit" block for each class. From this you can assume that first all variables are initialized starting with the top most super class followed by each subclass in order. Within a class the variables are initialized in the order they are declared. Once this is done then each "init" block is executed starting at the top most superclass, following down the class hierarchy. Then, in turn, the "postinit" blocks are called in the same way. It is important to understand this initialization order, so that when you define a class you understand when an item may be initialized and when it is not. 

Let's take the example of javafx.scene.control.Control  and javafx.scene.control.Skin. A Control has a one to one relationship to a Skin. As such, the Control has an instance variable "skin", and the Skin has an instance variable "control". Typically, you create the Skin object, then assign it to the control's skin variable using an override in your subclass.

public class MyControl extends Control {
    override var skin = MyControlSkin{};
}

The trigger for skin within the Control class assigns the Skin object to "skin" then sets the Skin object's "control" variable to itself. The point here is that, when MyControlSkin is first created, its "control" variable will be null until the Skin is fully initialized and has its "control" variable set. During the Skin initialization, you cannot access anything from the control.  The other point is that even if the Control has set the Skin's "control" variable, there is a window when the Control itself has not been fully initialized. Even if the Skin has a trigger on its "control" variable as in:

var MyControl = bind control as MyControl on replace {
     if(control != null) {
          // do initializaton from control
     }
}

you cannot assume that the control is fully initialized from this "on replace" block.

This condition is exasperated by the fact the you usually need to subclass control. However, if you use the 'override var skin" way to assign the Skin, this initialization will actually happen before any initialization is done in your Control subclass.  What I have found is that if I move the skin assignment to the postinit part of MyControl, then I can be assurred that MyControl is fully initialized when the Skin becomes aware of its existence.

Here are the two alternatives for assigning the Skin. First is the override version.

public class MyControl extends Control {
    public var myVar:Number;
    override var skin = MyControlSkin{}; // initialized before myVar
}

Second is the "postinit" version.

public class MyControl extends Control {
    public var myVar:Number;
    postinit {skin = MyControlSkin{}; }// initialized after myVar
}

The conclusion is you need to be aware of the order of initialization and how that impacts your class implementation.  If you get it wrong, the effects may be subtle and difficult to flesh out.

\*\*\* One should note, the goal of the JavaFX compiler team is to eliminate "postinit" and have "init" fully do the proper initialization. So expect "postinit" to be removed in a future release.



Thursday Mar 12, 2009

JavaFX - Gator JUG April 8, Gainesville FL, Orlando JUG - April 23, Orlando

I will be doing a live JavaFX development presentation for the Gator and Orlando Java User Groups in April. Details can be found at Gator JUG - Gainesville and Orlando JUG - Orlando .

Instead of the plain old slide presentation, I plan on showing live development of a JavaFX application using Netbeans IDE for JavaFX (thanks to Josh Marinacci for the inspiration). During the session, I will touch on some of the key features of JavaFX and demonstrate a simple JSON REST application. Lately, I have been contributing to the JFXtras project, and will also demonstrate some of those classes.

If you are in Central Florida, please plan on attending one of these sessions.

If you can't attend, I will be posting the sample application on our book's website JavaFX-Developing Rich Internet Applications sometime in April.

jim

Monday Feb 16, 2009

Finishing first cut - JavaFX - Developing Rich Internet Applications

Jim Connors, Eric Bruno and I have feverishly been heads down writing a new Book on JavaFX, JavaFX - Developing Rich Internet Applications.  We have just finished the initial writing and soon the rough cut chapters will be up on Safari for review. Target date for publication is June, 2009, in time for JavaONE.

We are also in the process of building a web site, jfxbook.com, to post the code samples, and show off some of the applications featured in the book. Most of the code samples are already posted up there, so feel free to peruse them. This is a work in progress, so check back for updates.

There are two applets that you can run. The NasaImageBrowser shows the US National Aeronautics and Space Administration (NASA) image of the day catalog. Also, Jim Connors has posted his Sudoku game application, so give it a try.

Once, the rough cuts are up on Safari, I'll let you know so you can start reading. We always appreciate any feedback.


Saturday Jan 03, 2009

JavaFX Progress Bar with glow effect

Yesterday, I was looking at an install package for some game software my son was loading on his computer and I was intrigued by the progress bar. It had a glow effect going back and forth while the progress bar was "progressing".  Of course, I had to sit down and figure this out in JavaFX.

To start with I borrowed some code from my IndeterminateProgressBar that I posted here a few moon's ago,  JavaFX - Inverting text color with a changing background. This shows how to change the text color as the progress bar crosses over the text.

Next, I created a ProgressBar class, that shows a progress bar as it moves from zero to a maximum. There are 2 options for the display, the percentage completion or the current value. To show progress, I merely paint a Rectangle for the progress with a different color over a background Rectangle.  The width of this "progress bar" changes based upon the percentage completion. So far, nothing surprising here.

To create the moving glow effect, I created an Ellipse with a GuasianBlur effect:


Ellipse {
centerX: bind blurX
    centerY:  bind height/2
    radiusX: bind progressHeight
    radiusY: bind progressHeight/2
    fill: Color.rgb(255,255,255,.7)
effect: GaussianBlur{ radius: 30 }
},


The centerX of the Ellipse will be changed in an animation based on the local variable, blurX, that moves the "glow" back and forth over the progressBar part. Here is the animation:


    var blurAnim = Timeline {
        repeatCount: Timeline.INDEFINITE
        autoReverse: true
        keyFrames: [
            at(0s) { blurX => 0.0 }
            at(2s) { blurX => currentX - progressHeight/2 tween Interpolator.EASEBOTH }
        ]
    };


Next, I tie the animation play to when the Progress bar is visible:


var playing = bind visible on replace {

        if(playing) {
            blurAnim.play();
        }else {
            blurAnim.stop();
        }

    };


The effect looks like this. Notice the glow towards the left side.

Progress Bar %75

And at %83, the glow is after the %83.58 label.

Progress Bar @ %83

The glow actually moves back and forth over a 2 second cycle.

One thing I haven't taken the time to figure out, is how to have the glow move at a constant rate of speed. In the timeline, I set the duration to a fixed 2 seconds, but as the progressbar's width grows, the glow travels a longer distance, so it moves faster, each cycle is still 2 seconds. (Thanks to Mr Newton and Mr. Einstien for this phenomenon).

Next, when I get a chance to play, I want to modify this to use CSS Style Sheets  and change it to a javafx.scene.Control with a javafx.scene.Skin. StyleSheets is a cool feature in JavaFX, that lets you easily modify the look of the JavaFX components, similar to the way HTML uses them. More on this later.

ProgressBar.fx is here.

Main.fx is here.

Thursday Dec 18, 2008

JavaFX 1.0 - Reflections on the JSON Weather Service

In my previous posting on the JSON Weather Service, I hard coded the Pull Parser's population of the Weather object. I don't particularly like to hard-code anything, so I have been investigating how to make this a little more generic. I found an answer using the JavaFX reflection package.

 What I did was create a class called "ParseModel". ParseModel has public instance variables for the "model" object (the Weather Object that will be populated), an optional "map" instance variable that contains maps between the parsed element name and the name of the instance varaible within the model object, a "url" that will point to the GeoNames geographical database, a "documentType" that is either PullParser.XML or PullParser.JSON, and an "onDone" function that is called when all the parsing is complete. I also have 3 private variables that hold the javafx.reflect types for the model object. context holds the javafx.reflect.FXLocal.Context instance, classRef holds the class type of the model object, and fxModel to hold the javafx.reflect.FXObjectValue that will be a mirror of the model object.


public class ParseModel {

    def context:FXLocal.Context = FXLocal.getContext();
    var classRef: FXClassType;
    var fxModel: FXObjectValue;

    public-init var model:Object on replace {
        if(model != null) {
            classRef = context.findClass(model.getClass().getName());
            fxModel = context.mirrorOf(model);
        }else { classRef = null; fxModel = null;}
    };

    public-init var map: Map;
    public-init var url:String;
    public-init var documentType:String;
    public-init var onDone: function(model:Object) : Void;


When the model is set, the java.reflect.FXLocal.Context instance is used to locate the javafx.reflect.ClassRefType  for the model class, and to create a javafx.reflect.FXObjectValue  object that encapsualtes the model Object. The classRef will be used to query the model for instance variables, and the fxModel variable will be used when setting the values for those variables.

Next, I create a function to be used by the PullParser to handle the parse events. This function translates the type from the parsed element to the type within the model object. Notice that when I set the value of the instance variable member object, I have to encapsulate the event value using context.mirrorOf().


function handler(event: Event) : Void {
        if(event.type == PullParser.TEXT or
            event.type == PullParser.INTEGER or
            event.type == PullParser.NUMBER ) {
            var field:String; // holds the name of the instance variable within the model object.
            if(map != null and map.containsKey(event.name)) { // a map entry exists, use it
                field = map.get(event.name) as String;
            } else { // default to the event name
                field = event.name;
            }
            var member = classRef.getVariable(field); // get the instance variable within the model object, for the field.

            var type = "{member.getType()}"; // the type of the instance variable within the model object.
            // try to guess the conversion
            if(event.type == PullParser.TEXT){
                if(type == "Integer") {
member.setValue(fxModel, context.mirrorOf(java.lang.Integer.valueOf(event.text)));
                } else if (type == "Number") {
member.setValue(fxModel, context.mirrorOf(java.lang.Double.valueOf(event.text)));
                } else if (type == "Boolean") {
member.setValue(fxModel, context.mirrorOf(java.lang.Boolean.valueOf(event.text)));
                }else { // assuming string
member.setValue(fxModel, context.mirrorOf(event.text));
                }
            } else if(event.type == PullParser.INTEGER) {
                if(type == "Number") {
member.setValue(fxModel, context.mirrorOf(java.lang.Double.valueOf(event.integerValue)));
                } else {
member.setValue(fxModel, context.mirrorOf(java.lang.Integer.valueOf(event.integerValue)));
                }
            } else if(event.type == PullParser.NUMBER) {
                if(type == "Integer") {
member.setValue(fxModel, context.mirrorOf(java.lang.Integer.valueOf(event.numberValue.intValue())));
                } else {
member.setValue(fxModel, context.mirrorOf(java.lang.Double.valueOf(event.numberValue)));
                }
            } else if(event.type == PullParser.TRUE or event.type == PullParser.FALSE) {
member.setValue(fxModel, context.mirrorOf(event.booleanValue));
            }
        }
    }


Next, I created an init block to create a RemoteTextDocument with a PullParser. The PullParser's onEvent function variable is set to the handler described above. When RemoteTextDocument returns and the PullParser completes, I call the onDone function variable passing the model object. Doing it this way makes ParseModel a once use object. It could have just as easily been done in a function rather than in the init block, and you could change the public instance variables from public-init to public so they can change between invocations.


    init {
       var input:InputStream;
       println("Starting");
       var rtd:RemoteTextDocument = RemoteTextDocument {
            url: bind url
            onDone: function(success:Boolean):Void {
                if(success) {
                    var doc = rtd.document;
                    println(doc);
                    input = new ByteArrayInputStream(doc.getBytes());
var parser: PullParser = PullParser {
                        documentType: documentType
                        input: input
                        onEvent: handler
                    };

                    parser.parse();
                    input.close();
onDone(model);

                }else {
                    println("failure = {rtd.failureText}");
                }
                println("Done");
            }
        };
    }


To initialize this:


    var w = Weather {};
    var m: Map = HashMap{};
m.put("stationName", "station");// It is only necessary to put entries when names do not match

    var modl = ParseModel {
        model: w
        map: m
        documentType: PullParser.JSON
        url: "http://ws.geonames.org/weatherIcaoJSON?ICAO=KMCO"
        onDone: function(obj: Object ) : Void {
var weather = obj as Weather;
            println("MODEL: {weather}");

        }
    };


TODO: This program is still one dimensional, only handling the basic variable types of Boolean, Integer, Number, and String. An improvement would be to recognize complex object trees for the model and do the appropriate recursion during parsing.

A program to test these ideas out can be downloaded from here.


Saturday Dec 13, 2008

JavaFX 1.0 - JSON Weather Service Redux.

Last June, I demonstrated a JavaFX class to access the JSON Weather Service from the GeoNames geographical database at http://www.geonames.org/export/JSON-webservices.html#weatherJSON.  Please see my earlier blog, http://blogs.sun.com/clarkeman/entry/javafx_script_and_json_weather  for more information about the Weather Service and how I implemented it in a pre-release version of JavaFX.

With the release of JavaFX 1.0, much has changed so I decided to update this example. The main differences are that  XML and JSON parsing were totally refactored and the original json.async.AsyncJsonCall  class is no longer with us. However, you can still leverage the javafx.async.RemoteTextDocument  and the new javafx.data.pull.PullParser classes to obtain the same effect.

I enhanced the example a little, so now you can enter an airport code. While the data is being retrieved, I use the IndeterminateProgressBar class that is described in my recent post, JavaFX - Inverting Text Color.

The Weather class is basically the same as before. The new javafx.data.pull.PullParser class, is setup as a JSON parser and the onEvent action gets the data from the JSON stream and assigns it into the Weather class. PullParser can also be used for XML streams. 


package jsonweather;

import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.text.Text;
import javafx.scene.text.Font;
import javafx.scene.text.TextOrigin;
import javafx.scene.control.TextBox;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.data.pull.PullParser;
import javafx.data.pull.Event;
import javafx.async.RemoteTextDocument;
import java.net.URL;
import java.lang.System;
import java.io.InputStream;
import java.io.ByteArrayInputStream;

// ICAO code in US is typically 'K' + IATA airport code
// MCO is Orlando FL US IATA code, so ICAO is KMCO.
// New York - JFK is KJFK.
// See http://en.wikipedia.org/wiki/List_of_airports_by_ICAO_code
var airportCode = "MCO";
var url = bind "http://ws.geonames.org/weatherIcaoJSON?ICAO=K{airportCode.toUpperCase()}";

class Weather {
    public var station:String;
    public var clouds:String;
    public var windDirection:Integer;
    public var windSpeed:Number;
    public var temperature:Number;
    public var dewPoint:Number;
    public var humidity:Integer;
    public var seaLevelPressure:Number;
    public var observation:String;
}
var weather:Weather = Weather{};

var jsonInput: InputStream; // holds the input stream for the JSON data

// The JSON parser
var parser = PullParser {
    documentType: PullParser.JSON;
    input: bind jsonInput
    onEvent: function(event: Event) { // parse the JSON Weather data and populate the Weather object

        if(event.type == PullParser.END_VALUE) {
            //println("{event}");
            if(event.name == "clouds") {
                weather.clouds = event.text;
            }else if (event.name == "stationName") {
                weather.station = event.text;
            }else if (event.name == "windDirection") {
                weather.windDirection = event.integerValue;
            }else if (event.name == "windSpeed") {
                weather.windSpeed = java.lang.Double.valueOf(event.text);
            }else if (event.name == "temperature") {
                weather.temperature = java.lang.Double.valueOf(event.text);
            }else if (event.name == "dewPoint") {
                weather.dewPoint = java.lang.Double.valueOf(event.text);
            }else if (event.name == "humidity") {
                weather.humidity = event.integerValue;
            }else if (event.name == "seaLevelPressure") {
                weather.seaLevelPressure = event.numberValue;
            } else if (event.name == "observation") {
                weather.observation = event.text;
            }
        }
    }
}


var app:VBox;
var bar:IndeterminateProgressBar;
var input:TextBox;
var rtd:RemoteTextDocument;


var stage:Stage = Stage {
    title: "JSON Weather"
    width: 700
    height: 400
    scene: Scene {
        content: [ app=VBox {
            translateX: 50
            translateY: 50
            spacing: 10
            content: [
                HBox {
                    content: [
                        Text {
                            translateY: bind input.boundsInLocal.height/2;
                            content: "Airport Code:"
                        },
                        input = TextBox {
                            columns: 4
                            value: bind airportCode with inverse
                            selectOnFocus: true
                            action: function(): Void {
                                app.opacity = .5;
                                bar.visible = true;
                                println("airport code = {airportCode} url = {url}");
                                weather = Weather{};
                                if(rtd != null) rtd.cancel();

                                // The remote access from the Geo Names site.

                                rtd = RemoteTextDocument {
                                    url: bind url
                                    onDone: function(success:Boolean):Void {
                                        if(success) {
                                            var json = rtd.document;
                                            jsonInput = new ByteArrayInputStream(json.getBytes());
                                            parser.parse();
                                            jsonInput.close();
                                        }else {
                                            System.out.println("failure = {rtd.failureText}");
                                        }
                                        // reset back to original state
                                        app.opacity = 1.0;
                                        bar.visible = false;
                                        rtd = null;
                                    }
                                };

                            }
                        }
                    ]
                },
                HBox {
                    content: [
                        Text {
                            textOrigin: TextOrigin.TOP
                            content: "Station:"},
                        Text {
                            textOrigin: TextOrigin.TOP
                            content: bind weather.station
                            fill: Color.BLUE
                        }
                    ]
                },
                HBox {
                    content: [
                        Text {textOrigin: TextOrigin.TOP content: "Clouds:"},
                        Text {
                            textOrigin: TextOrigin.TOP
                            content: bind weather.clouds
                            fill: Color.BLUE
                        }
                    ]
                },
                HBox {
                    content: [
                        Text {textOrigin: TextOrigin.TOP content: "Wind Direction:"},
                        Text {
                            textOrigin: TextOrigin.TOP
                            content: bind "{weather.windDirection} degrees"
                            fill: Color.BLUE}
                    ]
                },
                HBox {
                    content: [
                        Text {textOrigin: TextOrigin.TOP content: "Wind Speed:"},
                        Text {
                            textOrigin: TextOrigin.TOP
                            content: bind "{weather.windSpeed} knots"
                            fill: Color.BLUE}
                    ]
                },
                HBox {
                    content: [
                        Text {textOrigin: TextOrigin.TOP content: "Temperature:"},
                        Text {
                            textOrigin: TextOrigin.TOP
                            content: bind "{weather.temperature}C degrees"
                            fill: Color.BLUE}
                    ]
                },
                HBox {
                    content: [
                        Text {textOrigin: TextOrigin.TOP content: "Dew Point:"},
                        Text {
                            textOrigin: TextOrigin.TOP
                            content: bind "{weather.dewPoint}C degrees"
                            fill: Color.BLUE}
                    ]
                },
                HBox {
                    content: [
                        Text {textOrigin: TextOrigin.TOP content: "Humidity:"},
                        Text {
                            textOrigin: TextOrigin.TOP
                            content: bind "{weather.humidity}%"
                            fill: Color.BLUE}
                    ]
                },
                HBox {
                    content: [
                        Text {textOrigin: TextOrigin.TOP content: "Sea Level Pressure:"},
                        Text {
                            textOrigin: TextOrigin.TOP
                            content: bind "{weather.seaLevelPressure}mb"
                            fill: Color.BLUE}
                    ]
                },
                HBox {
                    content: [
                        Text {textOrigin: TextOrigin.TOP content: "METAR Observation:"},
                        Text {
                            textOrigin: TextOrigin.TOP
                            content: bind "{weather.observation}"
                            fill: Color.RED}
                    ]
                },

            ]
        },
        bar = IndeterminateProgressBar {
            visible: false
            width: bind stage.width - 50
            height: 35
            text: "Loading..."
        }
        ]
    }
}

input.requestFocus();


I am using the new class, javafx.scene.control.TextBox, to enter an arbiritary airport code. After the code is entered, and the user hits the enter key, the action for the TextBox creates a RemoteTextDocument object with the latest airport code. This action also makes the IndeterminateProgressBar visible which in turn starts its animation. Once the RemoteTextDocument is done fetching the JSON stream, it invokes the PullParser to set the Weather attributes. When all is done, the progress bar is set to invisible, stoping its animation, and the new weather information is displayed. 

The following image is what the screen looks like while RemoteTextDocument is doing its work in the background. This shows a request to get the weather for Los Angeles (LAX).

LAX Weather Loading

And this is Orlando, after the weather data has been retrieved and parsed:

Orlando Weather

Thursday Dec 11, 2008

JavaFX Async operations

I am helping a client with a JavaFX application and the client caches data locally using Mysql, after fetching it from a web service. To start the application,  an animated splash screen is shown, then the initial fetch of data is done, before showing the main application screen. 

What I noticed is that if I did the fetch routines in JavaFX script, the animation on the splash screen would pause. This is because JavaFX script executes on a single thread (Event Dispatch Thread, EDT, in the Desktop environment). To get around this, I needed the database and web service access to happen off the EDT in their own Thread. JavaFX does not support creating you own threads, so I had to resort to the javafx.async package. 

 Currently, there is built in support for asynchrously retrieving a remote text document from the Web via the javafx.async.RemoteTextDocument  class. However, my problem is I had to implement logic that would first check locally in the database, and if not present there, then go out over the web. Because of this, I could not use this class; off to write my own accessor class.

To do this, I had to write a Java class that  extends com.sun.javafx.runtime.async.AbstractAsyncOperation.  This class requires a constructor that takes at a minimum, a com.sun.javafx.runtime.async.AsyncOperationListener  object. The AsyncOperationListener provides various call back methods allowing control over the asynchrounous call and reporting of status, such as completion or failure. The could news here is that in the JavaFX side, the javafx.async.AbstractAsyncOperation  class handles this plumbing.


import com.mycompany.model.Employee;
import com.sun.javafx.runtime.async.AbstractAsyncOperation;
import com.sun.javafx.runtime.async.AsyncOperationListener;
import java.util.List;
import javax.sql.DataSource;

/\* EmployeeLoadImpl.java \*/
public class EmployeeLoadImpl extends AbstractAsyncOperation  {
    private Employee employee;
    public EmployeeLoadImpl(Employee employee, AsyncOperationListener listener) {
        super(listener);
        this.employee = employee;
    }

    @Override
    public Object call() throws Exception {
        DataSource ds2 = MyDatasource.getCommonsPooledDatasource();
        java.sql.Connection con = ds2.getConnection();

        // getDirectReports checks local db, and then web service if needed
        List directReports = EmployeeReportsDB.getDirectReports(con, employee.getId());
        employee.setDirectReports(directReports);
        return employee;
    }
}


The reason this is done in Java, is that the Call method executes its own Thread, and is not on the Event Dispatch Thread (EDT). By not attempting this in JavaFX, you can avoid unintended clashes with the JavaFX runtime. For example, you avoid clashes with binding or triggers. To be totally safe, the Employee model object is in Java, also.

The second part of this is to write a JavaFX class that extends javafx.async.AbstractAsyncOperation.  javafx.async.AbstractAsyncOperation provides the basic plumbing to  interact with the asynchronous runtime. Here is an example:


import javafx.async.AbstractAsyncOperation;
import 
com.mycompany.model.Employee;
/\*\*
 \* @author jclarke
 \*/

public class EmployeeLoad extends AbstractAsyncOperation  {
    var peer: 
EmployeeLoadImpl;
    public-init var employee:
Employee;

    public var onComplete: function(
employeeEmployee): Void;

    public override function cancel() : Void {
        if (peer != null) then peer.cancel();
    }

    protected override function start() : Void {
        peer = new 
EmployeeLoadImpl(employee, listener);
        peer.start();
    }

    protected override function onCompletion(value : Object) : Void {
        onComplete(
employee);
    }
}


 To use this in a program:


EmployeeLoad {

    employee: employee

    onComplete: function(empEmployee) : Void {   

           employeeUI = EmployeeUI {

                employee: emp 

         } 

   } 

}; 


 This is a little cumbersome, having to jump down into Java to execute the asynchrounous logic. The JavaFX team is currently looking at solutions to asynchronous use cases and hopefully enhanced language support or more Aysnchrounous framework classes will make this easier.




Thursday Dec 04, 2008

JavaFX - Inverting text color with a changing background.

Today, JavaFX 1.0 was announced (http://www.javafx.com), so I thought I would start chiming in again on some of the cool features of JavaFX.

I have been working with a customer to convert their Java Swing app over to JavaFX, and was intrigued with an indeterminate JProgressBar. An indeterminate JProgressBar has an internal rectangle, called a bouncing box, that continuously goes back and forth while running. What I noticed was that when the bouncing box moved over the progressString, it caused the text to change color. This was not a complete change, but only changed that part of the text that was covered by the bouncing box.

In the Swing source for JProgressBar (actually the Basic PLAF UI), this is done in the draw method, with the original text drawn once in the normal color (the selection background), followed by the alternate color, (the selection foreground), using a clip region. To duplicate this in JavaFX turns out to be similar.

I first created the bouncing box as a filled Rectangle and assigned it to an instance variable named "cursor". Then, I created a Text node with the background color, followed by a Text node with the foreground color. This second node is exactly the same as the first, except its stroke and fill colors are different. Furthermore, it takes a clip argument using the "cursor" rectangle. 


                cursor = Rectangle {
                    translateX: bind cursorX
                    translateY: bind borderWidth + 2
                    width: bind cursorWidth
                    height: bind height - borderWidth\*2 - 4
                    arcWidth: 5
                    arcHeight: bind height - borderWidth\*2 - 4
                    fill: bind background
                    cache: true
                },
                Text {
                    x: bind width/2 - geomText.boundsInLocal.width/2;
                    y: bind height/2 + geomText.boundsInLocal.height/2;
                    textOrigin: TextOrigin.BOTTOM
                    font: bind font
                    stroke: bind background
                    fill: bind background
                    content: bind text
                },
                Text {
                    x: bind width/2 - geomText.boundsInLocal.width/2;
                    y: bind height/2 + geomText.boundsInLocal.height/2;
                    textOrigin: TextOrigin.BOTTOM
                    font: bind font
                    stroke: bind foreground
                    fill: bind foreground
                    content: bind text

                    clip: bind cursor
                },


Now, the second Text does not display until the cursor Rectangle is over it, and only that part under the cursor is actually displayed.  Note that it is important that the clipped Text comes after the normal Text, because the last nodes are painted on top of previous nodes.

FX version of Indeterminate Progress Bar

The above screen snapshot shows the effect with the "ng..." of "Loading..." painted in white.

The complete code is available here!

About

jimclarke

Search

Categories
Archives
« July 2014
SunMonTueWedThuFriSat
  
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
  
       
Today