Sun Java Solaris Communities My SDN Account Join SDN

Learning the JavaFX Script Programming Language - Tutorial Overview

Lesson 8: Data Binding and Trigger Basics

   
« Previous 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Next »
 
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.
Stay tuned to javafx.com for an upcoming article that discusses how it is programmed.

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.

 
« Previous 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Next »
 
 
Rate This Article
 
Comments
Do you have comments about this article? We welcome your participation in our community. Please keep your comments civil and on point. You may optionally provide your email address to be notified of replies - your information is not used for any other purpose. By submitting a comment, you agree to these Terms of Use.

Oracle is reviewing the Sun product roadmap and will provide guidance to customers in accordance with Oracle's standard product communication policies. Any resulting features and timing of release of such features as determined by Oracle's review of roadmaps, are at the sole discretion of Oracle. All product roadmap information, whether communicated by Sun Microsystems or by Oracle, does not represent a commitment to deliver any material, code, or functionality, and should not be relied upon in making purchasing decisions. It is intended for information purposes only, and may not be incorporated into any contract.