This entry is just an attempt to start rolling something useful with JavaFX. I wanted to create a simple list of the most recent blog entries on OnJava using JavaFX. Starting simple, I wrote a very simple JavaFX application that parses the OnJava ATOM feed and just draws an array JavaFX Script Groups containing a Rect an two Text nodes.

Impressions after a few days

Pay attention to this technology. It’s at a very early stage, but, from what I see if could become very useful, very quickly with some minor improvements. It is very easy to dismiss JavaFX as hype, or to say that Sun will never compete with Adobe’s Flex, but I’m less interested in the horse race and more interested in the technology. While there are a few people out there blogging about initial experiences with JavaFX coding, the vast majority of commentary on JavaFX is being written by developers who haven’t bothered to use it. I’m not going to render judgement on this technology for another few weeks. In the meantime, I’m going to get involved, get my hands on the technology and use it.

  • A little buggy? Anyone else having issues running a JavaFX script via the ScriptEngine? I’m getting concurrent modification exceptions every other time I try to run this application from NetBeans 6. Either I’m doing something terribly wrong or the JavaFX runtime is an early stage alpha. Update: JavaFX isn’t buggy as I had previously suggested. Any exceptions that were being thrown were a problem in my Main class. I was trying to execute the FX script in the EDT. For an update to this post, read Correcting a Swing Mistake.
  • Not nearly as capable as Adobe Flex, but I can see the potential for easier integration with existing Java libraries.
First, a Disclaimer

JavaFX is so new there’s little documentation. (Actually that’s not fair, there’s a good deal of reference and some tutorials, but there is very little “here’s how you do X” documentation yet.) I’ve assembled a sample JavaFX application that includes a JavaFX component as a JComponent in a Swing application, but don’t view this application as a blueprint for your own application. There’s a good reason this isn’t an article - it is not a tutorial but my attempt to capture the first few hours of my experience with the technology. (Read: experimental)

Now on to the code…

Application Output

I’m going to write this blog entry backwards - first, here is what the following application produces. A simple list of blog entries on the OnJava site. Each entry consists of a rounded rectangle which contains the title and the author. Here’s a screenshot:

ora-ticker.jpg

No super-sexy rich media here, just some boxes. Eventually this application might grow to include some animations and interactivity, but my primary goal for this “first step” was to integrate a GUI written in JavaFX script with some existing Java classes that can parse an Atom feed. Maybe the next step will include some interesting visual effects.

The Code

First things first. I used Netbeans to write this application, and I followed the tutorial from the OpenJFX.org site to set up my project. The project depends on two external dependencies: JDOM 1.0 and ROME 0.8. There are two Java classes: Main and FeedReader, and one JavaFX file Ticker.fx. If you want to try to recreate this code, please feel free to copy and past the code into your project. Now to the code:

Main.java

Here are the contents of my Main class. (Thanks to Chris Oliver for quickly answering a question on the javafx-user mailing list - he gets credit for most of this main function). The line “String script = ‘import….” is the one that executes the script in Ticker.fx. If you want to know what the code does, just follow along in the comments…

IMPORTANT UPDATE: There is a problem in the following class. I was trying to execute the FX script in the EDT. For an update to this post and to see the new Main class source code, read Correcting a Swing Mistake. I made a newbie Swing mistake, which is expected I gave up on Swing a few years ago, and only now do I have a reason to pay attention to GUI programming.

package com.oreilly.onjava.feedticker;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Arrays;
import java.util.Date;
import javax.script.Bindings;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.script.SimpleScriptContext;
import javax.swing.JFrame;

public class Main {

    public Main() {
    }

    public static void main(String[] args) throws Exception {
        
        // Constructs a FeedReader that reads the OnJava blog Atom feed
        FeedReader reader = new FeedReader(new URL("http://www.oreillynet.com/onjava/blog/atom.xml"));
        reader.read();
        
        JFrame frame = new JFrame("Ticker");
        frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
        frame.setSize( 500, 400 );

        // WARNING THIS IS WRONG, SEE THE UPDATE POST...
        // Get the fx scripting engine - javafxrt.jar in classpath...
        ClassLoader loader =
        Thread.currentThread().getContextClassLoader();
        ScriptEngineManager manager = new ScriptEngineManager(loader);
        ScriptEngine engine = manager.getEngineByExtension("fx");

        // Bind the feed reader to the script's bindings...
        Bindings bindings = engine.createBindings();
        bindings.put("READER:com.oreilly.onjava.feedticker.FeedReader", reader);

        // Bind a JComponent to the script's bindings... the JavaFX script creates a canvas and adds
        // itself to this component.
        bindings.put("MY_CONTAINER:javax.swing.JComponent", frame.getContentPane());

        ScriptContext context = new SimpleScriptContext();
        // Bug workaround  (don't ask me, I have no idea what this means...TO)
        context.setBindings(bindings, ScriptContext.GLOBAL_SCOPE);
        context.setBindings(bindings, ScriptContext.ENGINE_SCOPE);
        engine.setContext(context);

        // Evaluate the Script
        String script = "import com.oreilly.onjava.feedticker.Ticker;";
        engine.eval(script);

        // Show the frame
        frame.setVisible(true);                
    }
}

Ticker.fx

Ignore the FeedReader class, just accept that it reads an Atom feed using the ROME API. We’ll get to that next. What should be more interesting is the contents of the Ticker.fx file. Some things to notice - it’s pretty lightweight compared to what you’d need to do in a regular Swing application. Also, note that the last line adds the var canvas to the JComponent that was set as a binding.

package com.oreilly.onjava.feedticker;
import javafx.ui.*;
import javafx.ui.canvas.*;
import javax.swing.JComponent;
import com.oreilly.onjava.feedticker.FeedReader;
import javafx.ui.filter.*;

var reader:FeedReader = READER;

var canvas = Canvas {
  height: 500
  width: 445
  content: 
    VBox {
        content:     
        foreach (i in reader.entries) 
        Group {
          content: 
            [Rect {
              x: 5
              y: 5
              height: 40
              width: 435
              arcHeight: 20
              arcWidth: 20
              fill: lightgrey
              stroke: black
              strokeWidth: 1
              filter: [ShadowFilter]
            },
            Text {
                content: bind i.title
                font: Font {face: VERDANA, style: [ITALIC, BOLD], size: 14}
                transform: translate(10, 10)
            },
            Text {
                content: bind i.author,
                font: Font {face: VERDANA, size: 12}
                transform: translate(310, 30)
            }]
        }
    }
};


MY_CONTAINER:JComponent.add(canvas.getComponent());

FeedReader.java

And, finally, here are the contents of my FeedReader class:

package com.oreilly.onjava.feedticker;

import com.sun.syndication.feed.synd.SyndEntry;
import com.sun.syndication.feed.synd.SyndFeed;
import com.sun.syndication.io.FeedException;
import com.sun.syndication.io.SyndFeedInput;
import com.sun.syndication.io.XmlReader;
import java.io.IOException;
import java.net.URL;

public class FeedReader {
    
    private URL url;
    private SyndEntry[] entries;
    private Integer titleLength = 50;
    
    public FeedReader(URL url) {
        this.url = url;
    }
    
    public void read() throws IOException, IllegalArgumentException, FeedException {
        SyndFeedInput input = new SyndFeedInput();
        SyndFeed feed = input.build(new XmlReader(url));
        entries = (SyndEntry[]) feed.getEntries().toArray(new SyndEntry[0]);

        // Abbreviate Titles
        for( SyndEntry entry : entries ) {
            entry.setTitle( entry.getTitle().length() > titleLength ? entry.getTitle().substring(0, titleLength - 3 ) + "..." : entry.getTitle() );
        }
    }

    public SyndEntry[] getEntries() {
        return entries;
    }

    public void setEntries(SyndEntry[] entries) {
        this.entries = entries;
    }
}