Friday, February 12, 2010

JavaFx: Building a GUI

JavaFx is targeted toward building graphical applications, and you'll find plenty of tutorials that walk you through such apps. Given this focus, I felt I should at least touch on GUI features.

Building the Containment Hierarchy

In a traditional Swing application, you start with a top-level window (a JFrame, or JApplet, or JDialog), and programmatically build out a “containment hierarchy” of components. For example, you'd build your main menu by instantiating a JMenuBar, then instantiating JMenu instances and adding them to it. For each JMenu instance, you'd then instantiate and add JMenuItem instances for the various menu choices. All Swing programs have a similar structure, which means there's a lot of boilerplate code. You could theoretically define the entire containment hierarchy as, say, an XML file, and eliminate the boilerplate. However, in practice you need to link together components and apply rules: for example, a menu item that should only be enabled when there's data for it to process.

A JavaFx application, however, is a step closer to declarative GUI design: the containment hierarchy (JavaFx calls it a “scene graph”) can be specified as a nested object initializer:

Stage {
    title: "Hello",
    width: 250,
    height: 80,
    scene: Scene {
        content: [
            Text {
                font : Font { size : 16 },
                x: 10,
                y: 30,
                content: "Hello, World"
            }
        ]
    }
}

The top level of the scene graph is the Stage. Unlike Swing, which uses different top-level classes for stand-alone applications versus applets, JavaFx uses Stage everywhere. As with other JavaFx GUI classes, Stage presents a much simpler interface than its Swing equivalents: a single scene for content, and a couple of callbacks for high-level events such as closing the window.

There are a couple of things that may not be obvious from this example. First: because the scene graph is constructed using an object initializer, you can extract parts of the initialization into variables — even bound variables:

var curText = "Hello, World";

Stage {
    // ...
                content : bind curText,
    // ...

The other important thing is this: creating a Stage creates and displays the window representing that stage. Swing, by comparison, separates construction and display. In the example above, we called the constructor without assigning the result to a variable. For simple applications, this is all you'll normally do. For more complex applications, you may construct multiple Stages, or hold onto them via variables so that you can modify them while the program is running.

Layout

Layout is one of the biggest pains of Swing programming. The Swing layout managers are all based on the idea that you need to position components mathematically, in order to deal with differing display resolutions and/or user actions (such as changing the size of a window). The result, more often than not, is an ugly UI. Or, alternatively, a custom layout manager that better suits the goals of a UI designer (I have several almost-complete articles on Swing layout, including a how-to on writing a layout manager; keep watching my main website feed to see when they appear).

JavaFx provides only a few, very basic layout managers. Most of the examples and tutorials use explicit layout: you provide the X and Y position of the object, and if needed, its width and height. There's also an option to extract layout information into an external CSS file. I haven't tried this, and am not certain why you'd do this; perhaps to deal with displays that have differing resolutions?

Interaction

JavaFx responds to user actions just like any other GUI environment: it invokes callbacks. If you've spent much time writing Swing ActionListeners, you'll probably like the fact that JavaFx supports first-class functions that you can specify as part of declarative initialization. If you've spent much time with JavaScript, you'll probably hate the fact that those functions are strongly typed, meaning that you have to specify parameters that you may never use.

JavaFx has a much simpler set of actions than Swing, and you can only attach a single function to a particular action. But be honest: how often did you actually use a list of listeners in Swing? Or intercepted Document events rather than just calling getText() on a field?

As I mentioned in my previous post, I think that bound fields will eliminate a lot of the code inside an action. As an example, consider a name/address form, along with a button that writes the form's contents to a database. In a traditional Swing app, that button's action handler would have to explicitly retrieve the contents of every field on the form, use those values to populate an object, then pass that object on to the database. With JavaFx, you could bind the fields of the object to the fields on the form, and the action would simply submit the already-populated object (although if the object's fields remained bound, the user could continue to update them — perhaps JavaFx could benefit from a value-based copy constructor).

CustomNode

A scene graph is a tree of javafx.scene.Node objects, with the Scene at the top, and various combinations of grouping and rendered components under it. The CustomNode class allows you to create new combinations of components that occupy a place in that hierarchy. For example, a field object that consists of both a label and a text field, with consistent sizing.

Note that I said “create”: one of the anti-patterns of Swing programming is to subclass JPanel just to add components to it (that anti-pattern is the topic of another forthcoming article on my website). When you subclass CustomNode, you are actually creating a controller class. You must override the abstract function create() to build out the node's containment hierarchy.

Graphics and Animation

JavaFx provides a simplified interface to the Java2D library. In Swing, if you want to access the drawing primitives, you must subclass a Swing component (usually JPanel) and override its paintComponent() method. JavaFx gives you components that already do this; you simply create a node in the scene graph. You specify the object's position, size, stroke, fill, and transforms in its initializer.

Graphical objects and data binding come together in JavaFx animation. Here's a simple countdown timer, with explanation to follow:

var counter = 10;

Stage {
    title: "Countdown",
    width: 100,
    height: 40,
    scene: Scene {
        content: [
            Text {
                font : Font { size : 18 },
                textOrigin: TextOrigin.TOP,
                x: 60, y : 10,
                content: bind counter.toString();
            }
        ]
    }
}

Timeline {
    keyFrames: [
        at (10s) {
            counter => 0 tween Interpolator.LINEAR;
        }
    ]
}.play();

The scene graph is simple: a single text field. Its content is bound to the variable counter; every time that variable gets updated, the text field will be updated as well. Where this program gets interesting is the Timeline object, particularly its keyFrames property.

A Timeline is like a javax.swing.Timer on steroids. You tell it the desired value for one or more variables at fixed points in time, along with a way to interpolate between the starting value and the desired value, and the Timeline will make that happen. Here I specify that, at 10 seconds after the timeline starts, the value of counter should be zero, and that it should change linearly. I start with the value 10, the Timeline constantly invokes the interpolator to compute the “current” value, and then sets the specified variable (which is in turn bound by the text field).

Animation is considered such a big deal that the at operator is a part of the language. As far as I can tell, all that it does is instantiate a KeyFrame object. Unfortunately, the Animation chapter of the Language Reference is currently empty. As a result, figuring out animations is a matter of looking at examples and experimenting.

Profiles

One of the problems with J2ME development is that you don't have the full Java environment: there's no floating point, and a very limited subset of the API. JavaFx attempts to rectify this, but even so, its API breaks out into two categories: common and desktop. The desktop profile provides OpenGL graphical effects (the entire javafx.scene.effect package), as well as the ability to use Swing components.

This Isn't Your Father's GUI

Looking through the API docs, I was struck by what's missing: there aren't any menus. Nor tables. This is intentional: as I learned from a HanselMinutes podcast, the JavaFx team is not trying to build the next toolkit for corporate applications. Indeed, looking at what they've done, I hear “mobile app”: a simplified UI, with flashy controls, designed to fit in a small space. Which is not a bad thing.

No comments: