Tuesday, February 15, 2011

Data Binding and Trigger Basics

Data binding is one of the most powerful features of the JavaFX Script programming language. With just one keyword -- bind -- you can easily synchronize an application's graphical user interface (GUI) with its underlying data.

Contents

- Binding Basics
- LED Digit Example


The following clock applet is a real-world example of data binding in action. It is composed of GUI objects (the red LED digits), some internal state (the current time), and the bind keyword, which links it all together.

This LED clock applet is a real-world example of data binding in action.
For a discussion of how it was programmed, see Creating a Digital Clock.

The next tutorial in this series (Building GUI Application with JavaFX) teaches you all about GUI programming. The topics covered in that tutorial (for example, Stage, Scene, and Node) are not repeated here. In this lesson, you will learn about data binding with some simple command-line examples, followed by a graphical LED click-counter application. For advanced usage, see the binding chapter of the JavaFX Language Reference.
Binding Basics

The basic concepts can be demonstrated with just two variables. In the following example, variable a is bound to variable b. Whenever the value of a changes, the value of b automatically changes as well:
Source Code:

var a = "Hello";
var b = bind a;
println("a:{a} b:{b}");
a = "Good-Bye";
println("a:{a} b:{b}");


Output:

a:Hello b:Hello
a:Good-Bye b:Good-Bye



In this example, both variables are strings, but binding works with the other data types as well:
Source Code:

var a = 10;
var b = bind a;
println("a:{a} b:{b}");
a = 20;
println("a:{a} b:{b}");


Output:

a:10 b:10
a:20 b:20



In fact, you can bind to an entire expression, and it will still work:
Source Code:

var a = 10;
var b = bind (a+10);
println("a:{a} b:{b}");
a = 20;
println("a:{a} b:{b}");


Output:

a:10 b:20
a:20 b:30



You can even use bind with objects; the basic concept remains the same:
Source Code:

var myStreet = "1 Main Street";
var myCity = "Santa Clara";
var myState = "CA";
var myZip = "95050";

class Address {
var street: String;
var city: String;
var state: String;
var zip: String;
}

def address = Address {
street: bind myStreet;
city: myCity;
state: myState;
zip: myZip;
};

println("address.street == {address.street}");
myStreet = "100 Maple Street";
println("address.street == {address.street}");



In this example, address is bound to an instance of Address in which street is bound to myStreet. If the value of myStreet changes, the value of street in the existing object is also changed and you don't end up with an entirely new Address object.
Output:

address.street == 1 Main Street
address.street == 100 Maple Street


LED Digit Example

To see this example used in a real application, consider the following simple LED click counter. You will need to download the following images and place them in the same directory as your .fx source code.



You could draw these same digits by using only the JavaFX API, but using images makes the source code smaller, and ties in more closely with real-world workflow. The most common scenario is that graphics are a designer's responsibility; as the application developer, you are only responsible for writing the program logic itself.

With that in mind, here is a simple script to load the images and display the first digit:
Source Code:

import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;

def images = for(i in [0..9]){Image {url: "{__DIR__}{i}.png"};}

Stage {
scene: Scene {
content: ImageView {image: images[0]}
}
}


Output:

In this version, the for loop returns a sequence containing the image data for all 10 LED digits (0-9). Note how the image to be displayed is currently hardcoded into the GUI: ImageView {image: images[0]}. This hardcoding works fine for static content, but it is not flexible enough to accommodate dynamic GUIs.

You can improve the program by tracking the currently displayed image in its own variable:
Source Code:

import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;

def images = for(i in [0..9]){Image {url: "{__DIR__}{i}.png"};}
var currImg = images[0];

Stage {
scene: Scene {
content: ImageView {image: currImg}
}
}


Output:

The output remains the same, but the overall structure has improved. Now add some interactivity by increasing the value of count with each mouse click. In this version, only the value of count increases with each click. The LED digit itself does not change.
Source Code:

import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;

var count = 0;

def images = for(i in [0..9]){Image {url: "{__DIR__}{i}.png"};}
var currImg = images[count];

Stage {
scene: Scene {
content: ImageView {
image: currImg
onMouseClicked:
function(e: MouseEvent) {
println("Click number {++count} ...");
currImg = images[count];
}
}
}
}



Clicking directly on the image now increases the count and prints its new value. However, the GUI still fails to update because the ImageView object doesn't know that currImg has changed. You can fix this problem by adding in the bind keyword. The ImageView object will now become aware of the mouse clicks, and the LED counter will update as expected:
Source Code:

import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;

var count = 0;
def images = for(i in [0..9]){Image {url: "{__DIR__}{i}.png"};}
var currImg = images[count];

Stage {
scene: Scene {
content: ImageView {
image: bind currImg
onMouseClicked:
function(e: MouseEvent) {
println("Click number {++count} ...");
currImg = images[count];
}
}
}
}


Output:

Click number 1 ...
Click number 2 ...
Click number 3 ...
Click number 4 ...
Click number 5 ...
Click number 6 ...
Click number 7 ...
Click number 8 ...
Click number 9 ...



You can also add a replace trigger to place some constraints on the counter value. A replace trigger is an arbitrary block of code that attaches to a variable and automatically runs whenever that variable's value is changed. Such a trigger is needed in this implementation because the GUI will turn black should the click count ever exceed 9. By placing a trigger on the currImg variable, you can prevent this problem from happening:
Source Code:

import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;

var count = 0;
def images = for(i in [0..9]){Image {url: "{__DIR__}{i}.png"};}
var currImg = images[count] on replace oldValue {
if(count < 10){
if (count == 9){println("Max count ({count}) reached.");}
}else{
println("\tWarning: count has exceeded 9! ");
println("\tThis would cause the screen to go black.");
println("\tI'll roll back the change (which will fire the trigger again)....");
count = 9;
currImg = oldValue;
println("Done. The counter should look OK now.");
}
};

Stage {
scene: Scene {
content: ImageView {
image: bind currImg
onMouseClicked:
function(e: MouseEvent) {
println("Click number {++count} ...");
currImg = images[count];
}
}
}
}


Output:

Click number 1 ...
Click number 2 ...
Click number 3 ...
Click number 4 ...
Click number 5 ...
Click number 6 ...
Click number 7 ...
Click number 8 ...
Click number 9 ...
Max count (9) reached.
Click number 10 ...
Warning: count has exceeded 9!
This would cause the screen to go black.
I'll roll back the change (which will fire the trigger again)....
Max count (9) reached.
Done. The counter should look OK now.



This trigger issues a warning and takes action should count become greater than 9. Note that triggers do not prevent a target variable from being changed. They execute after the change has occurred. In this example, the previous count is stored in the oldVal variable, which is accessed when the count becomes too high.

There is also an on invalidate clause that can be used for improved performance. This is similar to on replace in that it includes a block of code, and can be added to a variable definition. However, an on invalidate clause does not force a bind to be eager as does an on replace clause.

Consider the following:

var y ... on invalidate {code block}

Here, the code block is executed when y might have become invalid, for example, when a new value has been designated for y, which means that the next reference to y must not get the old value of y.

More specifically:

* if y is referenced two times and the second reference to y yields a different value than the first reference to y, then the on invalidate code block will have been executed at least once between the two references.

* if y is explicitly invalidated with the new invalidate statement. For example:

invalidate y;

then the on invalidate code block will be executed at least once between the execution of the invalidate statement and the execution of the next reference to y.

SOURCE:http://download.oracle.com/javafx/1.3/tutorials/core/dataBinding/

0 comments: