10 Minute Tutorial - JavaFX: Event handling using trigger and bind
Update:
I have updated this tutorial for JavaFX 1.0
Clearly, the creators of JavaFX Script want to make it a great MVC programming language. Nothing says this more than baking triggers and data 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 unidirectional binding between a variable and a right side expression. These concepts help remove some of the tedium and typical bolier plate code in handling UI modification events.
To (hopefully) help illustrate how these concepts work, I decided to create a tutorial based on setting the state of a traffic light. 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…not one of my strong points.
Anyway, let’s get started!
Prerequisites
- All the prerequisites defined in the
10 Minute JavaFX Tutorial - Develop and deploy JavaFX Applets and Applications while online and offline
QuickStart
If you want to see the end result of this tutorial and you have installed all the prerequisites, then please download the ZIP file below, unzip it and open the trafficlight-applet.html file in your browser.
- Traffic Light Sample (you may read this software’s license here)
If you want to see the sample in action right now, use the links below:
- Traffic Light JavaFX applet
- Traffic Light JavaFX Webstart application (you may need to save the JNLP file first, then open it)
Step 1: Define traffic constants
Representing the different states of the traffic light will require a few constants. Create a file named “TrafficLight.fx” and put this code in it:
import javafx.scene.paint.Color;
def trafficColors : Color[] = [Color.RED, Color.YELLOW, Color.GREEN];
def trafficColorNames : String[] = ["Red", "Yellow", "Green"];
def trafficColorActions : String[] = ["Stop", "Slow", "Go"];
These three arrays, or “sequences” in JavaFX speak, represent all the possible states of a traffic light in this application. The def keyword makes the variables assigned to these sequences constants, meaning the developer can’t assign them any other value for the rest of the application (using the var keyword would make them re-assignable). Also, note that each of these sequences have a type, and that type name followed by double brackets ([]) makes those variables into arrays.
Unlike Java, JavaFX allows developers to define global constants and variables.
Step 2: Declare the model
Like Swing, JavaFX really comes into its own when you use the MVC paradigm. We will represent the model using a class, like so:
...
def trafficColorActions : String[] = ["Stop", "Slow", "Go"];
class Model {
var action: String;
var selectedColorIndex: Integer on replace oldValue {
action = trafficColorActions[selectedColorIndex];
}
}
Use of the var keyword inside of a class creates instance variables. The “action” variable will hold the current applicable state from the “trafficColorActions” sequence. The “selectedColorIndex” variable represents the currently selected color by holding the index of that color in the “trafficColors” sequence. Putting the on replace keywords after the “selectedColorIndex” variable declaration assigns a replace trigger to that variable. So, whenever “selectedColorIndex” changes, the JavaFX runtime executes the code between the braces. Within the scope of the braces, the “oldValue” variable represents the previous value held by “selectedColorIndex”, while the “selectedColorIndex” variable itself holds the new value. Thus, everytime the “selectedColorIndex” variable changes, “action” gets set to a new value from the “trafficColorActions” sequence.
Step 3: Instantiate a combobox
As stated earlier, the user changes the traffic light by selecting options from a combobox. The code below creates that combobox:
import javafx.ext.swing.SwingComboBox;
import javafx.ext.swing.SwingComboBoxItem;
class Model {
...
}
var trafficLightComboBox = SwingComboBox {
translateX: 113
width: 75
selectedIndex: 2
items: for (colorName in trafficColorNames)
SwingComboBoxItem {
text: colorName
}
}
First, note the highlighted import statements make the SwingComboBox and SwingComboBoxItem classes available to this application. The “translateX” variable sets its position on the X-axis and the “width” variable simply sets the width of the control. The “selectedIndex” variable represents which item the combobox has selected and setting it to 2 on initialization makes that the default item. And finally, a for loop initializes the “items” sequences by adding a new SwingComboBoxItem instance to the sequence during every iteration. As you can see, the for loop iterates over the “trafficColorNames” sequence and assigns the current name to each SwingComboBoxItem, meaning that the combobox will select “Green” by default.
Also, note that instead of declaring all UI elements in a Stage definition as done in 10 Minute Tutorial - JavaFX: Hello World, here the “trafficLightComboBox” holds the instance of SwingComboBox which we will place onto a stage later.
Step 4: Instantiate the model
Before going any further, let’s instantiate the model class:
...
var trafficLightComboBox = SwingComboBox {
...
}
}
var model = Model {
selectedColorIndex: bind trafficLightComboBox.selectedIndex;
}
This class’ one and only variable assignment employs the use of the bind keyword. The bind keyword tells the JavaFX runtime to watch the value of the expression on the right, and whenever the value of that expression changes, assign it to the variable on the left. So, in this example, the “selectedColorIndex” variable will always equal the value of the combobox’s currently selected index.
Now, let’s continue creating the GUI.
Step 5: Instantiate a circle
This application will use a circle that changes colors to simulate a traffic light. I’ve put the code needed to instantiate a Circle below:
...
import javafx.scene.shape.Circle;
class Model {
...
}
var trafficLight = Circle {
centerX: 150
centerY: 20
radius: 20
fill: bind trafficColors[model.selectedColorIndex]
}
First, note the highlighted import statement, making the Circle class available to this application. While the position variables on this class should require little explanation, the highlighted “fill” variable makes use of the bind keyword again. This time, the variable binds to an element inside of a sequence.
Step 6: Instantiate a text field
Now, we need a text field that shows the action corresponding to the traffic light:
...
import javafx.scene.text.Text;
var trafficLight = Circle {
...
}
var trafficLightText = Text {
x: 135
y: 10
content: bind model.action
}
Nothing out of the ordinary here. After slogging through all the binding above, the bind statement here shouldn’t give you any trouble.
GUI element creation completed! So, let’s add them to the Stage.
Step 7: Instantiate the Stage
We’ll start with the code first:
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.scene.layout.VBox;
...
Stage {
title: "Die, Ajax! - Traffic Light Sample"
width: 300
height: 250
scene: Scene {
content: [
VBox {
spacing: 20
content: [trafficLight, trafficLightText, trafficLightComboBox]
}
]
}
}
If you read my JavaFX introductory tutorial, then the Stage and Scene object instationations should look very familar. In that tutorial, the Scene object contains the individual UI elements in its “content” sequence, here however, they get added to the highlighted VBox class’ “content” sequence. This usage of VBox gives us our first look at JavaFX’s layout capability. Since this tutorial focuses on binding and triggering, I won’t delve too deeply into JavaFX’s (sorely lacking) layout containers. However, I will say that the VBox container positions all objects in its “content” sequence vertially, one underneath the other. The “spacing” variable simply defines how much space to put between elements.
Deceptively simple, but figuring out the final “y” position values confused me a bit at first. I’ll attempt to explain it here
In order of display:
- The center of the Circle’s “y” value equals 20, as denoted by the “centerY” property.
- The Text’s “y” value equals trafficLight.centerY + spacing + 10. This means that if we set both trafficLightText.y and VBox.spacing to a really low value, the Text control would overlap the circle.
- The SwingComboBox’s “y” value equals trafficLight.centerY + spacing + 10 + spacing. Note, we don’t define a “translateY” for SwingComboBox, so nothing gets added to its y-position.
I HOPE that’s right.
I could not find much on specifics for JavaFX layout, however I would guess that Sun plans on adding more sophisticated layout capabilities to JavaFX down the line to match the considerable wealth of layout containers for Swing and its third party add-ons.
For completeness, I pasted the entire source below:
package com.dieajax.sample.TrafficLight;
import javafx.scene.Scene;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.scene.shape.Circle;
import javafx.scene.paint.Color;
import javafx.ext.swing.SwingComboBox;
import javafx.ext.swing.SwingComboBoxItem;
import javafx.scene.layout.VBox;
def trafficColors : Color[] = [Color.RED, Color.YELLOW, Color.GREEN];
def trafficColorNames : String[] = ["Red", "Yellow", "Green"];
def trafficColorActions : String[] = ["Stop", "Slow", "Go"];
class Model {
var action: String;
var selectedColorIndex: Integer on replace oldValue {
action = trafficColorActions[selectedColorIndex];
}
}
var trafficLightComboBox = SwingComboBox {
translateX: 113
width: 75
selectedIndex: 2
items: for (colorName in trafficColorNames)
SwingComboBoxItem {
text: colorName
}
}
var model = Model {
selectedColorIndex: bind trafficLightComboBox.selectedIndex;
};
var trafficLight = Circle {
centerX: 150
centerY: 20
radius: 20
fill: bind trafficColors[model.selectedColorIndex]
}
var trafficLightText = Text {
x: 135
y: 10
content: bind model.action
}
Stage {
title: "Die, Ajax! - Traffic Light Sample"
width: 300
height: 250
scene: Scene {
content: [
VBox {
spacing: 20
content: [trafficLight, trafficLightText, trafficLightComboBox]
}
]
}
}
Step 8: Run it!
Without an IDE, building JavaFX applications for the desktop, browser and Webstart remains a slightly involved process. I explain this process step-by-step in my 10 Minute Tutorial - JavaFX: Hello World and 10 Minute JavaFX Tutorial - Develop and deploy JavaFX Applets and Applications while online and offline articles, so I will just walk you through the basics here. Feel free to reference those articles for more detail.
Compile the code
No matter the deployment platform, we need to compile the code first. This line takes care of it:
"C:\Program Files\JavaFX\javafx-sdk1.0\bin\javafxc.exe" -d . TrafficLight.fx
Now, we’ll look at each platform individually.
Run the application on the desktop
We only need one command to do this:
"C:\Program Files\JavaFX\javafx-sdk1.0\bin\javafx.exe" com.dieajax.sample.TrafficLight.TrafficLight
Run the application as an applet
Per my 10 Minute JavaFX Tutorial - Develop and deploy JavaFX Applets and Applications while online and offline article, first let’s make a JAR file:
"C:\Program Files\Java\jdk1.6.0_11\bin\jar.exe" cvf TrafficLight.jar com\dieajax\sample\TrafficLight\TrafficLight*.class
Can’t make it appear in the browser without some HTML, so let’s create an HTML file named “trafficlight-applet.html” and add that next:
<html>
<head>
<title>Traffic Light Applet</title>
</head>
<body>
<script src="http://dl.javafx.com/dtfx.js"></script>
<script>
javafx(
{
archive: "TrafficLight.jar",
draggable: true,
width: 300,
height: 250,
code: "com.dieajax.sample.TrafficLight.TrafficLight",
name: "TrafficLight"
}
);
</script>
</body>
</html>
Finally, we need the applet JNLP file named “TrafficLight_browser.jnlp”:
<?xml version="1.0" encoding="UTF-8"?>
<jnlp spec="1.0+">
<information>
<title>Traffic Light Sample</title>
<vendor>Acme Corporation</vendor>
<description>Traffic Light Sample Applet</description>
</information>
<resources>
<j2se version="1.5+"/>
<jar href="TrafficLight.jar" main="true" download="eager"/>
<extension name="JavaFX Runtime" href="http://dl.javafx.com/javafx-rt.jnlp"/>
</resources>
<applet-desc main-class="com.sun.javafx.runtime.adapter.Applet" name="TrafficLight" height="300" width="250">
<param name="MainJavaFXScript" value="com.dieajax.sample.TrafficLight.TrafficLight"/>
</applet-desc>
</jnlp>
Test this by opening the “trafficlight-applet.html” file in your browser.
Run the application through Java Webstart
Deploying applications through Java Webstart requires HTML, too, so put this code inside of a new file named “trafficlight-webstart.html”:
<html>
<head>
<title>Traffic Light Webstart</title>
</head>
<body>
<a href="TrafficLight.jnlp">Traffic Light Webstart</a>
</body>
</html>
Our Webstart JNLP file, stored in “TrafficLight.jnlp” will look very similar to the applet version, but with a few tweaks:
<?xml version="1.0" encoding="UTF-8"?>
<jnlp spec="1.0+" codebase="file:/C:\Documents and Settings\David Miles\My Documents\javafx\trafficlight">
<information>
<title>Traffic Light Sample</title>
<vendor>Acme Corporation</vendor>
<description>Traffic Light Sample Applet</description>
</information>
<resources>
<j2se version="1.5+"/>
<jar href="TrafficLight.jar" main="true" download="eager"/>
<extension name="JavaFX Runtime" href="http://dl.javafx.com/javafx-rt.jnlp"/>
</resources>
<application-desc main-class="com.dieajax.sample.TrafficLight.TrafficLight" />
</jnlp>
Remember to change the <jnlp> “codebase” attribute to match your disk or web server directory structure.
To run the application through Java Webstart you should only need to open the “trafficlight-webstart.html” file and click on the link.
Conclusion
I hope this tutorial has given you a deeper look at the JavaFX language, data binding and triggering in particular. By baking these two concepts directly into the language, Sun has created the perfect foundation for MVC-like architectures to build on. Down the line, I expect them to add two-way data binding and more advanced triggering support, but for now, I’ll take what I can get ;).



