Making JavaFX groovier!

Tutorial - GroovyFX

GroovyFX is a library that makes writing JavaFX 2.x applications much simpler and more natural. GroovyFX exploits the power of Groovy’s Builder pattern to enable the developer to write in a concise, declarative style. The ability to construct UIs in this way makes it easier to visualize the user interface that is being built. Code that is short and easy to understand is also easy to maintain and extend. GroovyFX fully supports all of the many advanced features of JavaFX such as controls, layout, graphics, animation, sound, video, charts, and more.

Version 0.2 of GroovyFX is available as a snapshot from Sonatype’s Maven repository. Its group ID is org.codehaus.groovyfx and its artifact ID is simply groovyfx. In this article I will be using the latest version: 0.2-SNAPSHOT.

Editor's Note: At the time of this writing, the JavaFX team at Oracle is finishing work on JavaFX 2.1. I anticipate that this release will occur fairly soon after this article is published, therefore all of the code samples in this article will use GroovyFX 0.2-SNAPSHOT, which is the version of GroovyFX that is tracking the changes in JavaFX 2.1.

I have recently published a blog post that details how to use GroovyFX in Gradle projects and in Groovy scripts with the @Grab annotation. Alternatively, you can clone the project directly from GitHub and build it yourself by following the instructions on the project’s GitHub page (be sure you are working with the develop branch).

Building a Scene

A JavaFX user interface is created by building a scene graph. Therefore, GroovyFX has a SceneGraphBuilder class that wraps the JavaFX API classes and makes them available using Groovy’s Builder syntax. However, you will rarely instantiate and work with a SceneGraphBuilder object directly. Rather, you will typically use the static start method of the GroovyFX class, passing it a closure that executes your GroovyFX code. This is illustrated by the example shown in Listing 1, which shows a basic GroovyFX “Hello, World” program.

Listing 1: Hello World

 

import groovyx.javafx.GroovyFX

GroovyFX.start {
  stage(title: "GroovyFX Hello World", visible: true) {
    scene(fill: black, width: 530, height: 300) {
      hbox(padding: 80) {
        text(text: "Groovy", style: "-fx-font-size: 80pt") {
          fill linearGradient(endX: 0, stops: [palegreen, seagreen])
        }
        text(text: "FX", style: "-fx-font-size: 80pt") {
          fill linearGradient(endX: 0, stops: [cyan, dodgerblue])
          effect dropShadow(color: dodgerblue, radius: 25, spread: 0.25)
        }
      }
    }
  }
}

 

The primary application container in JavaFX is the stage. This corresponds to the application’s window when run as a desktop application, or its content area when run as an applet in the browser. The stage contains a scene that keeps a reference to the root node of the scene graph, a data structure that defines the content shown by the application. So every JavaFX application is made up of a Stage, a Scene, and one or more scene graph node classes. This structure is easy to pick out in the GroovyFX code shown in Listing 1, where the root of the scene graph is an instance of the JavaFX Hbox class. HBox is a layout node that arranges its child nodes in a horizontal line. Listing 1 shows that the root HBox node has two child Text nodes that display the strings “Groovy” and “FX” side by side as shown in Figure 1.

 

Figure 1: The GroovyFX “Hello, World” program

 

The attentive reader will no doubt have deduced the naming pattern used in GroovyFX. Nodes in the GroovyFX builder syntax have the same names as their corresponding JavaFX class with the first capital letter(s) of the class name converted to lower case. Therefore the JavaFX Stage class is referred to as stage in GroovyFX. Similarly LinearGradient becomes linearGradient, HBox becomes hbox, Button becomes button, and so on.

The properties of these classes can be set by using the property names as keys in a map that is passed as a parameter to the builder node. The following code creates a stage whose title property is set to “GroovyFX Hello World”.

stage(title: "GroovyFX Hello World")

GroovyFX also provides some handy shortcuts for declaring your scene graph nodes and properties. Note the linearGradient declarations in Listing 1. GroovyFX allows you to use shortcuts such as referring to colours by name instead of using their static values such as “Color.CYAN”. Further, you can simply pass an array of colours as a linearGradient’s stops property and GroovyFX will automatically create evenly spaced gradient stops using those colours. These handy shortcuts make it clean and quick to write GroovyFX code.

Controls and Layout

JavaFX comes with a complete set of modern UI controls such as a Button, Label, TextField, and CheckBox. Also included are more complex controls such as a ListView, TreeView, and TableView. JavaFX also includes powerful set of layouts such as HBox, VBox, BorderPane, TilePane, and the powerful and flexible GridPane. All of these controls and layout panes are just nodes in the JavaFX scene graph. As you have already seen in the Listing 1, any scene graph node that is a child of a layout pane will automatically have its position and size set by that layout pane (such as the two Text nodes that were children of the Hbox). Technically, there are other factors that come into play during layout, such as the value of a node’s managed property and whether or not a node is resizable, but in the vast majority of cases, you can simply assume that a layout pane will control the size and positioning of its child nodes.

Some layout panes require additional information in order to lay out their child nodes. The BorderPane needs to know whether a node should be placed in the north, south, east, west, or center of its layout space. A GridPane needs to know in which row and column to place a node. If you are working in Java, these constraints need to be set by additional method calls. In GroovyFX, these constraints can be passed directly to the node’s property map and GroovyFX will take care of calling the constraint methods for you. This allows you to keep your layout constraints in the same place at which you declare the node and makes the layout easier to understand. You can see an example of this in Listing 2, where GroovyFX uses a variety of controls placed into a GridPane in order to build a simple form.

Listing 2

 

import static groovyx.javafx.GroovyFX.start

start {
  stage(title: "GridPane Demo", width: 400, height: 500, visible: true) {
    scene(fill: groovyblue) {
      gridPane(hgap: 5, vgap: 10, padding: 25, alignment: "top_center") {
        columnConstraints(minWidth: 50, halignment: "right")
        columnConstraints(prefWidth: 250, hgrow: 'always')

        label("Please Send Us Your Feedback", style: "-fx-font-size: 18px;",
              textFill: white, row: 0, columnSpan: 2, halignment: "center",
              margin: [0, 0, 10]) {
          onMouseEntered { e -> e.source.parent.gridLinesVisible = true }
          onMouseExited { e -> e.source.parent.gridLinesVisible = false }
        }

        label("Name", hgrow: "never", row: 1, column: 0, textFill: white)
        textField(promptText: "Your name", row: 1, column: 1 )

        label("Email", row: 2, column: 0, textFill: white)
        textField(promptText: "Your email address", row: 2, column: 1)

        label("Message", row: 3, column: 0, valignment: "baseline", 
              textFill: white)
        textArea(prefRowCount: 8, row: 3, column: 1, vgrow: 'always')

        button("Send Message", row: 4, column: 1, halignment: "right")
      }
    }
  }
}

 

Listing 2 demonstrates several more GroovyFX conveniences. You will note that the alignment of the GridPane is set to “top_center”. GroovyFX will figure out that you are really referring to the enumerated value “Pos.TOP_CENTER” since GridPane’s alignment property is of type Pos. The same is true when setting an halignment of “center” (HPos.CENTER).

Another time-saving convenience allowed by GroovyFX is to define event handlers as Groovy closures. This can be seen in the onMouseEntered and onMouseExited event handlers declared on the first label node in Listing 2. These two event handlers make the outlines of the GridPane’s cells visible when the mouse is over the label. This can be a very handy trick when debugging layout issues in a GridPane. The form that is created by the code in Listing 2 is shown in Figure 2.

Figure 2: Creating a form layout in GroovyFX

Graphics and Animation

JavaFX is also known for its strong graphics and animation capabilities. GroovyFX naturally has full support in these areas as well. Take the code shown in Listing 3, which is a GroovyFX translation of the Java-based Colorful Circles example app written by the JavaFX team at Oracle.

Listing 3:

 

import static groovyx.javafx.GroovyFX.start

start {
  def circles
  stage(title: 'GroovyFX ColorfulCircles', resizable: false, show: true) {
    scene(width: 800, height: 600, fill: 'black') {
      group {
        circles = group {
          30.times {
            circle(radius: 200, fill: rgb(255, 255, 255, 0.05), 
                   stroke: rgb(255, 255, 255, 0.16),
                   strokeWidth: 4, strokeType: 'outside')
          }
          effect boxBlur(width: 10, height: 10, iterations: 3)
        }
        rectangle(width: 800, height: 600, blendMode: 'overlay') {
        def stops = ['#f8bd55', '#c0fe56', '#5dfbc1', '#64c2f8', 
                     '#be4af7', '#ed5fc2', '#ef504c', '#f2660f']
        fill linearGradient(start: [0f, 1f], end: [1f, 0f], stops: stops)
      }
    }
  }

  parallelTransition(cycleCount: 'indefinite', autoReverse: true) {
    def random = new Random()
    circles.children.each { circle ->
      translateTransition(40.s, node: circle, fromX: random.nextInt(800), 
                          fromY: random.nextInt(600),
                          toX: random.nextInt(800), 
                          toY: random.nextInt(600))
    }
  }.play()
}
  }
}

 

Listing 3 demonstrates GroovyFX’s support for the JavaFX shape classes such as Circle and Rectangle, as well as the support of all the effects such as BoxBlur. The use of Groovy looping constructs such as 30.times to create the circles shows one of the nice advantages of using Groovy’s builder syntax: any Groovy expression or statement is valid within the builder. This even includes such constructs as if-statements that can allow you to conditionally build nodes.

Animation is definitely one of the strong points of JavaFX. You can define your own timelines or use one of the many pre-packaged transition classes that are available. Listing 3 demonstrates the use of a ParallelTransition (a transition that runs other animation in parallel) and a TranslateTransition (a transition that changes a node’s x,y coordinates in order to move it around). In this case, a random translation is applied to each of the circles so that they slowly wander around the screen. The output of this application is shown in Figure 3.

Figure 3: Graphics and animation in GroovyFX

FXBindable Annotation

One of the very powerful capabilities of JavaFX Script (the language used to write JavaFX 1.x applications) was its built-in support for binding. This feature lives on in the Java-based library of JavaFX 2.x in the form of the properties and binding API classes. This is a fluent API that allows you to use nearly all of the power of the old JavaFX Script binding functionality. One of the drawbacks of the Java-based API is the amount of boilerplate code required to define properties that are capable of being used by the binding classes. Take, for example, the Person class shown in Listing 4.

Listing 4 - Person Class

public class Person {
  private StringProperty firstName;
  public void setFirstName(String val) { firstNameProperty().set(val); }
  public String getFirstName() { return firstNameProperty().get(); }
  public StringProperty firstNameProperty() { 
    if (firstName == null) 
      firstName = new SimpleStringProperty(this, "firstName");
    return firstName; 
  }
 
  private StringProperty lastName;
  public void setLastName(String value) { lastNameProperty().set(value); }
  public String getLastName() { return lastNameProperty().get(); }
  public StringProperty lastNameProperty() { 
    if (lastName == null) // etc.
  } 
}

This listing shows the typical boilerplate required to define a class with two properties in JavaFX. These properties, since they are backed by the StringProperty class, are able to fully participate in all of the JavaFX binding functionality. But if you take that boilerplate and multiply it by every property of every class in your application, you may quickly become distressed by the amount of additional code it introduces – code that has to be debugged and maintained by you (or some other unfortunate developer).

GroovyFX can solve this problem for you by taking advantage of one of Groovy’s most powerful features, the AST transformation. Writing a custom AST transform allows you to modify or insert code at compile time in order to reduce the coding burden of developers. All you have to do is annotate a standard Groovy property with the @FXBindable annotation and GroovyFX will write all of that boilerplate code for you! Listing 5 shows how the Person class looks when using the @FXBindable annotation. Believe it or not, this code is equivalent to that shown in Listing 4.

Listing 5

public class Person {
  @FXBindable String firstName; 
  @FXBindable String lastName;
}

If all of the properties in your POGO should be bindable, you can annotate the class instead of each individual property, as shown in Listing 6.

Listing 6

@FXBindable 
public class Person {
  String firstName; 
  String lastName;
}

To learn more about @FXBindable and GroovyFX’s binding capabilities, I urge you to look at the AnalogClockDemo, which is included in the GroovyFX project on GitHub.

Time to write a full application?

When writing a complex application, you will typically want support for things like using MVC design patterns, easy threading, and perhaps an event bus. This is the domain of Griffon, an easy-to-use Grails-like framework for writing desktop Java applications (see Andres Almiray’s article on Griffon).

You will be happy to know that moving to Griffon does not mean that you need to give up the power of JavaFX and the convenience of GroovyFX. A JavaFX plug-in exists for Griffon that allows you to use GroovyFX to write the view code of your application. Instructions for getting started are included on my GitHub page for the Griffon JavaFX archetype. This archetype allows you to quickly get a new JavaFX Griffon application up and running.

If you are interested in learning more about JavaFX and GroovyFX, you can check out the recently published book “Pro JavaFX 2: A Definitive Guide to Rich Clients with Java Technology”, of which I was a co-author. Oracle has also published plenty of JavaFX documentation and examples on their JavaFX web site.

Author Bio: 

Dean Iverson has been writing software professionally for more than 15 years. He is employed by the Virginia Tech Transportation Institute, where he is a rich client application developer. He has been working with JavaFX technologies from the beginning and is a contributor to the GroovyFX and JFXtras open source projects as well as being a co-author of the Pro JavaFX books published by Apress.

This article originally appeared in Java Tech Journal - The Groovy Universe. Check out more Groovy projects there...

 

Dean Iverson
Dean Iverson

What do you think?

JAX Magazine - 2014 - 06 Exclucively for iPad users JAX Magazine on Android

Comments

Latest opinions