10 Minute Tutorial - JavaFX: Event handling using trigger and bind
Clearly, the creators of JavaFX Script want to make it a great MVC programming language. Nothing says this more than baking triggers and binding into the language as first class concepts. According to the documentation, JavaFX triggers operate in the same manner as SQL triggers, allowing you to handle data modification events in an aspect-like fashion. And, the bind keyword allows bidirectional binding between two variables so that when one receives an update the other also gets updated. Using these concepts, its easy to remove some of the tedium in handling modification events of UI elements and variables that other languages and frameworks require developers to slog through.
So, I decided to create a tutorial based on setting the state of a traffic light to hopefully help illustrate how these concepts work. You can see a picture of it running here. Basically, the user will use a combobox to select three different states (red, yellow, green) and a circle will change color accordingly. Just for fun, I’ve added a label that will change text (stop, slow, go) in sync with the circle…
…yeah, I know….but coming up with demo ideas doesn’t represent one of my strong points.
Anyway, let’s get started!
Prerequisites
- All the prerequisites defined in the last tutorial
QuickStart
If you want to see the result of this tutorial and you have installed all the prerequisites, then please download the ZIP file below, unzip it, open and edit the TrafficLight.bat to match your directory structure and then run the TrafficLight.bat file.
If you don’t know how you should change the .bat file, please read my last tutorial.
- Traffic Light Sample (you may read this software’s license here)
You can find a screen shot of the running application, here.
Step 1: Create the model
Like Swing, JavaFX really comes into its own when you use the MVC paradigm. Thus, we will need a class that represents our model. So first, create a file named TrafficLight.fx. Then declare a class named Model, like so:
import javafx.ui.*;
import javafx.ui.canvas.*;
class Model {
attribute selectedColorIndex: Number;
attribute colors: Paint*;
attribute action: String;
attribute colorNames: String*;
attribute actions: String*;
}
This should read fairly easily: the class Model has five attributes and the colors, colorNames and actions attributes have a cardinality of * which means “zero or more”.
Note that I sneaked a few import statements in there. We’ll put those to use when we start declaring UI objects.
Step 2: Instantiate the model
Now, we need to create an instance of our Model class. However, before we do that, let’s specify a default value for the colors attribute:
attribute Model.colors = [red, yellow, green];
So when the Model gets instantiated, the colors attribute will contain a list of the Paint objects: red, yellow, green.
Now, create an instance of Model:
var model = Model {
var: self
selectedColorIndex: 2
action: self.actions[self.selectedColorIndex]
colorNames: [”Red”, “Yellow”, “Green”]
actions: [”Stop”, “Slow”, “Go”]
};
Again, pretty self-explanatory code, with the exception of the highlighted self variable, itself (wow, that sentence doesn’t sound correct at all
). The var: syntax (notice the colon) creates a self-referential variable, allowing access to other attributes during initialization.
You can call this variable whatever you like. I could have named it me, but I prefer Delphi/Object Pascal flash backs over VB6 ones.
Step 3: Place triggers on the Model
Thus far, most of the code has needed little explanation, however, the trigger statement can easily trip up the unsuspecting coder:
trigger on Model.selectedColorIndex = newValue {
this.action = this.actions[newValue];
}
This statement says, “for EVERY instance of the model class, whenever the variable selectedColorIndex gets set to a new value, execute the code within the body of the trigger, passing in newValue as a parameter”. Notice that in the body, I highlighted the use of newValue as an array index. I also highlighted the this variable to emphasize that all code executing in the body of the trigger executes within the scope of THE CURRENT INSTANCE THE TRIGGER fired on. Keep that in mind; it will give you headaches.
Step 4: Create the UI
First, let’s start with a simple Frame object:
Frame {
title: "Die, Ajax! - Traffic Light Sample"
width: 300
height: 200
visible: true
content:
Panel {}
}
Note that assigning the content attribute to a Panel will require that all controls within that panel to use exact positioning. Similar to Swing, JavaFX provides a number of different layout managers, but exact positioning will work for our demo.
Our Panel will contain three different elements: a Canvas (which contains a Circle), a Label and a ComoBox. First, the Canvas declaration:
...
Panel {
content: [
Canvas {
x: 0
y: 0
width: 300
height: 120
content:
[Circle {
cx: 150
cy: 60
radius: 20
fill: bind model.colors[model.selectedColorIndex]
}]
}]
}
I’ve highlighted the real important part of this declaration: the binding of the fill attribute directly to an element of the model.colors array as indexed by the model.selectedColorIndex variable.
This bind statement does exactly what your programmer’s intuition tells you: every time the value of model.selectedColorIndex changes, JavaFX re-indexes the array and then re-assigns the fill attribute.
Pretty slick.
Now, for the label:
...
Panel {
content: [
Canvas {
x: 0
y: 0
width: 300
height: 120
content:
[Circle {
cx: 150
cy: 60
radius: 20
fill: bind model.colors[model.selectedColorIndex]
}]
}, Label {
x: 135
y: 100
text: bind model.action
}]
}
With your new found knowledge about binding, this declaration should read pretty easily. Moving on to the ComboBox:
...
Panel {
content: [
Canvas {
x: 0
y: 0
width: 300
height: 120
content:
[Circle {
cx: 150
cy: 60
radius: 20
fill: bind model.colors[model.selectedColorIndex]
}]
}, Label {
x: 135
y: 100
text: bind model.action
}, ComboBox {
x: 120
y: 140
selection: bind model.selectedColorIndex
border: EtchedBorder
cells: foreach (colorName in model.colorNames)
ComboBoxCell {
text: colorName
}
}]
}
I didn’t mention it before, but when possible, JavaFX will make binding statements bi-directional. So, upon creation, the selection attribute will receive the value stored in model.selectedColorIndex, however when the user changes the selection, model.selectedColorIndex will receive the index of the new selection.
Sweet. Potentially dangerous, but sweet.
Step 5: Run it
Here’s the code in its entirety:
import javafx.ui.*;
import javafx.ui.canvas.*;
class Model {
attribute selectedColorIndex: Number;
attribute colors: Paint*;
attribute action: String;
attribute colorNames: String*;
attribute actions: String*;
}
attribute Model.colors = [red, yellow, green];
var model = Model {
var: self
selectedColorIndex: 2
action: self.actions[self.selectedColorIndex]
colorNames: ["Red", "Yellow", "Green"]
actions: ["Stop", "Slow", "Go"]
};
trigger on Model.selectedColorIndex = newValue {
this.action = this.actions[newValue];
}
Frame {
title: "Die, Ajax! - Traffic Light Sample"
width: 300
height: 200
visible: true
content:
Panel {
content: [
Canvas {
x: 0
y: 0
width: 300
height: 120
content:
[Circle {
cx: 150
cy: 60
radius: 20
fill: bind model.colors[model.selectedColorIndex]
}]
}, Label {
x: 135
y: 100
text: bind model.action
}, ComboBox {
x: 120
y: 140
selection: bind model.selectedColorIndex
border: EtchedBorder
cells: foreach (colorName in model.colorNames)
ComboBoxCell {
text: colorName
}
}]
}
}
To run it on my machine, I used this command line:
"C:\Program Files\Java\jre1.6.0_02\bin\java.exe" -classpath .;"C:\Documents and Settings\David Miles\dev\OpenJFX\trunk\lib\javafxrt.jar";"C:\Documents and Settings\David Miles\dev\OpenJFX\trunk\lib\swing-layout.jar";"C:\Documents and Settings\David Miles\dev\OpenJFX\trunk\lib\Filters.jar" net.java.javafx.FXShell TrafficLight.fx
You can also plug it into JavaFXPad, if you’d like.
Conclusion
Not ready for prime time
The simplicity of this tutorial belies the hell I had to go through to create it. As tempting as using declarative Swing component instantiation and attribute binding for your next project may sound, I can personal attest to the fact that JavaFX remains at an alpha quality state.
Psh, screw that. We’re talking pre, pre, alpha here. (…not that it will stop me from writing more tutorials.
)
So, keep in mind that at this stage, for anything other than fun or prototyping, you’ll want to stick with more mature options. (I hear Swing programming in NetBeans 6 makes a night with a pack of smokes and a pair of <insert your nationality fetish here> twins seem boring. And, I didn’t think ANYTHING could make that boring).
Also, on top of some pretty debilitating bugs, JavaFX has very little in the way of documentation. It looks Sun has replaced the API documentation that I normally reference with something more official. However, as of the publishing of this post, the official documentation doesn’t contain near as much information, so I’ll use both in tandem until the official documentation gets up to speed.
…but still, it’s nice
Notice how this application lacks the usual amount of listener code and addXXX method calls of a typical Swing application. Yes, you can still handle events using custom functions, but making strategic use of triggers and binding to “handle events for you” allows thinking at a higher level of abstraction and (hopefully) makes your code that more readable (but it can’t work miracles
).
JavaFX has some real potential here, but clearly has a long way to go. However, I will definitely watch the evolution binding and triggers with great interest.
Leave a Donation
If you found this article helpful, please leave a donation for Dave, so he can help you again. As always, thank you for your support!



