GroovyFX is an open source project whose goal is to combine the conciseness of Groovy with the power of JavaFX 2.0. Jim Clarke, the originator of the project, and I have been working hard to make GroovyFX the most advanced library for writing JavaFX code with alternative JVM languages. As you are about to see, it is more than a mere DSL that provides some syntactic sugar for JavaFX code. We have decided that it is past time to share our progress with the wider JavaFX community; this article is long overdue (right, Jonathan?).
This is the first of many articles I'll be writing about GroovyFX. If you want to stay up to date with the GroovyFX project you can follow this blog or follow me on Twitter.
How to Play
The GroovyFX website has all the information you need to get started but I will summarize it here:- Download and install the latest version of JavaFX and set a JAVAFX_HOME environment variable that points to the root directory of your JavaFX installation.
- Download the latest version of Gradle (1.0 milestone-4 or better), unzip it, and add it to your path. Gradle provides the easiest and quickest way to build and run the demos.
- Check out the GroovyFX source from http://svn.codehaus.org/gmod/groovyfx/trunk/.
gradle buildOnce the project builds successfully, you can start running one of the many demos included with the project by typing a command like
gradle AnalogClockDemoThat will start the application pictured at the top of this article. You can see a complete list of available demos by typing
gradle tasksand examining the "Demo" task group.
Setting the Table
Setting up and populating a TableView is something that is not only common but can take a surprising amount of code in Java. So we will start with the GroovyFX TableViewDemo shown in the image below.The code for this example, in its entirety, is as follows.
@Canonical class Person { @FXBindable String firstName @FXBindable String lastName @FXBindable String city @FXBindable String state } def data = [ new Person('Jim', 'Clarke', 'Orlando', 'FL'), new Person('Jim', 'Connors', 'Long Island', 'NY'), new Person('Eric', 'Bruno', 'Long Island', 'NY'), new Person('Dean', 'Iverson', 'Fort Collins', 'CO'), new Person('Jim', 'Weaver', 'Marion', 'IN'), new Person('Stephen', 'Chin', 'Belmont', 'CA'), new Person('Weiqi', 'Gao', 'Ballwin', 'MO'), ] GroovyFX.start { def sg = new SceneGraphBuilder() sg.stage(title: "GroovyFX TableView Demo", visible: true) { scene(fill: groovyblue, width: 650, height:450) { stackPane(padding: 20) { tableView(items: data) { tableColumn(text: "First Name", property: 'firstName') tableColumn(text: "Last Name", property: 'lastName') tableColumn(text: "City", property: 'city') tableColumn(text: "State", property: 'state') } } } } }Compare that with other "simple" JavaFX TableView examples, and it's easy to see that GroovyFX can save you both time and code. There are three main sections to this code: our Person class, the declaration of the data List, and the scene graph itself. The Person class contains the four properties that will be displayed in the TableView. It is annotated with the standard Groovy
@Canonical
AST transformation that adds a tuple constructor. This allows us to construct a Person instance using new Person('Jim', 'Clarke', 'Orlando', 'FL')
in our data List. @Canonical
also adds appropriate overrides for the hashCode
, equals
, and toString
methods. These are all generated for us at compile time; this is the power of Groovy's AST transformations.The
@FXBindable
annotation is a custom AST transform that Jim and I have added to GroovyFX. When you use it to annotate a standard Groovy property, the property will be transformed into a JavaFX property. Its job is to generate all of the boilerplate associated with declaring JavaFX properties. For each annotated property it will generate three methods:public void setFirstName(String value) public String getFirstName() public final StringProperty getFirstNameProperty()This setup allows you to access your JavaFX properties just as you would any standard Groovy property.
def name = person.firstName person.firstName = 'James' person.firstNameProperty.bind( /* some binding expression - more on that below */ )Considering all of the boilerplate involved when creating JavaFX properties in Java, this will be a real productivity win for GroovyFX users. One last thing to note is that you can also use
@FXBindable
to annotate a class. The following code is equivalent to the class declaration above. The FXBindable transform will iterate all of the class properties and transform each one into a JavaFX property.@Canonical @FXBindable class Person { String firstName String lastName String city String state }We'll now turn our attention to the GroovyFX scene graph declaration. All scene graphs in GroovyFX begin with the
GroovyFX.start
method, which takes a closure as its argument. The first few lines of the closure are almost always the same: instantiate a SceneGraphBuilder and use it to declare your stage and scene. The root of our scene graph is a StackPane layout container. This is a nice container to use as a root node since it will be resized as the scene size changes and will also grow and shrink its child nodes if they are resizable (like TableView is). After the stackPane we add a tableView with its data items and its four tableColumn declarations. It is a very concise way to declare your scene graph.You have probably noticed that the naming convention for GroovyFX scene graph nodes matches the JavaFX class names with the first letter converted to lower case. This is a convention we follow for all of our nodes. Another convention is that you specify properties for a node using Groovy's
propertyName: value
Map syntax. There are a couple of other fun things to note about the scene graph code.There is a new color "groovyblue" that is added to JavaFX's Color class at runtime. We use it as the background of all of our demos, but you are free to use it in your code as well (it's the color of the star in the Groovy logo). Any JavaFX color constant can be declared using just its lowercase name such as
red
, blue
, or burlywood
. There are also shortcuts for specifying the padding of a node. Above we have just used a single integer value which will be used as the padding on all sides. You can also specify a list with 1, 2, 3, or 4 integers that will be assigned just as they are in CSS. See the GroovyFX PaddingDemo for the options. These are just a few of the many short cuts and productivity boosters we've incorporated into GroovyFX.A Time for Binding
GroovyFX also has a nice surprise for those of you that miss the simple but powerful binding syntax in JavaFX Script. When JavaFX was ported to Java, the team at Oracle came up with a nice fluent API for specifying binding. Here is an example of this API:hourAngleProperty().bind(Bindings.add(hoursProperty().multiply(30.0), minutesProperty().multiply(0.5))); minuteAngleProperty().bind(minutesProperty().multiply(6.0)); secondAngleProperty().bind(secondsProperty().multiply(6.0));Not bad, but all of the method calls do tend to obfuscate the rather simple binding expression. Wouldn't it be great if you could write these kinds of binding expressions in a more natural way? Say, something like this?
hourAngleProperty.bind((hoursProperty * 30.0) + (minutesProperty * 0.5)) minuteAngleProperty.bind(minutesProperty * 6.0) secondAngleProperty.bind(secondsProperty * 6.0)This is exactly what Jim has just added to GroovyFX. This functionality uses Groovy's operator overriding ability combined with its ability to add methods to existing classes. The result is all-natural binding goodness with only the essence of the binding and little ceremony. In fact the above expressions are part of the AnalogClockDemo pictured at the start of this article. The code for the demo's Time class is below.
@FXBindable class Time { Integer hours Integer minutes Integer seconds Double hourAngle Double minuteAngle Double secondAngle public Time() { // bind the angle properties to the clock time hourAngleProperty.bind((hoursProperty * 30.0) + (minutesProperty * 0.5)) minuteAngleProperty.bind(minutesProperty * 6.0) secondAngleProperty.bind(secondsProperty * 6.0) // Set the initial clock time def calendar = Calendar.instance hours = calendar.get(Calendar.HOUR) minutes = calendar.get(Calendar.MINUTE) seconds = calendar.get(Calendar.SECOND) } /** * Add a second to the time */ public void addOneSecond() { seconds = (seconds + 1) % 60 if (seconds == 0) { minutes = (minutes + 1) % 60 if (minutes == 0) { hours = (hours + 1) % 12 } } } }Note the use of
@FXBindable
for easy property declarations, the simple expressive binding, and the natural way of accessing the properties. You use the property name by itself for getting and setting values. You use the property name followed by "Property" to access the underlying JavaFX property class when you need to add listeners or bindings. For completeness, the scene graph code used to draw the clock face is shown below.time = new Time() GroovyFX.start { def width = 240.0 def height = 240.0 def radius = width / 3.0 def centerX = width / 2.0 def centerY = height / 2.0 def sg = new SceneGraphBuilder() sg.stage(title: "GroovyFX Clock Demo", width: 245, height: 265, visible: true, resizable: false) { def hourDots = [] for (i in 0..11) { def y = -Math.cos(Math.PI / 6.0 * i) * radius def x = ((i > 5) ? -1 : 1) * Math.sqrt(radius * radius - y * y) def r = i % 3 ? 2.0 : 4.0 hourDots << circle(fill: black, layoutX: x, layoutY: y, radius: r) } scene(fill: groovyblue) { group(layoutX: centerX, layoutY: centerY) { // outer rim circle(radius: radius + 20) { fill(radialGradient(radius: 1.0, center: [0.0, 0.0], focusDistance: 0.5, focusAngle: 0, stops: [[0.9, silver], [1.0, black]])) } // clock face circle(radius: radius + 10, stroke: black) { fill(radialGradient(radius: 1.0, center: [0.0, 0.0], focusDistance: 4.0, focusAngle: 90, stops: [[0.0, white], [1.0, cadetblue]])) } // dots around the clock for the hours nodes(hourDots) // center circle(radius: 5, fill: black) // hour hand path(fill: black) { rotate(angle: bind(time.hourAngleProperty)) moveTo(x: 4, y: -4) arcTo(radiusX: -1, radiusY: -1, x: -4, y: -4) lineTo(x: 0, y: -radius / 4 * 3) } // minute hand path(fill: black) { rotate(angle: bind(time.minuteAngleProperty)) moveTo(x: 4, y: -4) arcTo(radiusX: -1, radiusY: -1, x: -4, y: -4) lineTo(x: 0, y: -radius) } // second hand line(endY: -radius - 3, strokeWidth: 2, stroke: red) { rotate(angle: bind(time.secondAngleProperty)) } } } } sequentialTransition(cycleCount: "indefinite") { pauseTransition(1.s, onFinished: {time.addOneSecond()}) }.playFromStart() }Once again, this is the AnalogClockDemo located in src/demo in the GroovyFX project. These binding expressions should still be considered experimental, but you can see them in action If you run the demo with Gradle. It should appear as shown here.
The GroovyFX AnalogClockDemo was inspired by one of the JRuby examples on javafx.com
Conclusion
GroovyFX is a young project and it is advancing rapidly. We are only just now getting close to releasing our first version, but it already has a lot of very useful functionality. It has support for virtually every JavaFX shape, control, and chart. It even provides great integration with the brand new FXML functionality (see the FXMLDemo).There is quite a lot of documentation and many examples on the project's web site. We invite you to get involved: download and play with the code then let us know what you think. You can file JIRA issues for things that don't work or features you would like to see. Our goal with GroovyFX is to make it fun and easy to write client Java applications. So join in!
Where did u get the JavaFX bundle for OS X?
ReplyDeleteThis is what JavaFX should have been in the first place! Keep up the good work!
ReplyDelete@Angry Developer - There is a build for Mac available from the Java Partner site. It's highly experimental and not updated as regularly as the Windows version (obviously, since getting the Windows version released is the priority for now).
ReplyDelete@O.Weiler - Thanks! Jim and I appreciate the kind words.
GroovyFX is Great!! i wish completion available for GroovyFX
ReplyDeleteCreating an IDEA gdsl file is one of the next things on my list, so we should have it soon. If you're an Eclipse user, we would love to have a dsld file contributed.
ReplyDelete@Dean
ReplyDeleteWhats the trick on getting partner status? We submitted our application weeks ago and never heard back.
Andreas,
ReplyDeleteI wish I knew the answer to that! I've heard the same thing from others who have submitted applications. What worked for me was to keep pestering them with email. Persistence pays off. :-)
Looking very good Dean. Need to check it out.
ReplyDeleteDo you guys have an examples of integration with Grails?
ReplyDelete