C H A P T E R   3

Properties and Bindings

Heaven acts with vitality and persistence.
In correspondence with this
The superior person keeps himself vital without ceasing.

—I Ching

In Chapters 1 and 2, we introduced you to the JavaFX 2.0 platform. You downloaded the JavaFX 2.0 SDK and the JavaFX plugin for Netbeans. You wrote and ran your first JavaFX 2.0 GUI programs. You learned the fundamental building blocks of JavaFX 2.0: the Stage and Scene classes, and the Nodes that go into the Scene. And you have no doubt noticed the use of user-defined model classes to represent the application state and have that state communicated to the UI through properties and bindings.

In this chapter, we give you a guided tour of the JavaFX 2.0 properties and bindings framework. After recalling a little bit of history and presenting a motivating example that shows various ways that a JavaFX 2.0 Property can be used, we cover key concepts of the framework: Observable, ObservableValue, WritableValue, ReadOnlyProperty, Property, and Binding. We show you the capabilities offered by these fundamental interfaces of the framework. We then show you how Property objects are bound together, how Binding objects are built out of properties and other bindings—using the factory methods in the Bindings utility class, the fluent interface API, or going low-level by directly extending abstract classes that implement the Binding interface—and how they are used to easily propagate changes in one part of a program to other parts of the program without too much coding. We finish this chapter by introducing the JavaFX Beans naming convention, an extension of the original JavaBeans naming convention that makes organizing your data into encapsulated components an orderly affair.

Because the JavaFX 2.0 properties and bindings framework is a nonvisual part of the JavaFX 2.0 platform, the example programs in this chapter are also nonvisual in nature. We deal with Boolean, Integer, Long, Float, Double, String, and Object typed properties and bindings as these are the types in which the JavaFX 2.0 binding framework specializes. Your GUI building fun resumes in the next and further chapters.

Forerunners of JavaFX 2.0 Binding

The need for exposing attributes of Java components directly to client code, allowing them to observe and to manipulate such attributes and to take action when their values change, is recognized early in Java’s life. The JavaBeans framework in Java 1.1 provided support for properties through the now familiar getter and setter convention. It also supported the propagations of property changes through its PropertyChangeEvent and PropertyChangeListener mechanism. Although the JavaBeans framework is used in many Swing applications, its use is quite cumbersome and requires quite a bit of boilerplate code. Several higher-level data binding frameworks were created over the years with various levels of success. The heritage of the JavaBeans in the JavaFX 2.0 properties and bindings framework lies mainly in the JavaFX Beans getter, setter, and property getter naming convention when defining JavaFX 2.0 components. We talk about the JavaFX Beans getter, setter, and property getter naming convention later in this chapter, after we have covered the key concepts and interfaces of the JavaFX 2.0 properties and bindings framework.

Another strand of heritage of the JavaFX 2.0 properties and bindings framework comes from the JavaFX Script language that was part of the JavaFX 1.x platform. Although the JavaFX Script language was deprecated in the JavaFX 2.0 platform in favor of a Java-based API, one of the goals of the transition was to preserve most of the powers of the JavaFX Script’s bind keyword, whose expressive power has delighted many JavaFX enthusiasts. As an example, JavaFX Script supports the binding to complex expressions:

var a = 1;
var b = 10;
var m = 4;
def c = bind for (x in [a..b] where x < m) { x * x };

This code will automatically recalculate the value of c whenever the values of a, b, or m are changed.

Although the JavaFX 2.0 properties and bindings framework does not support all of the binding constructs of JavaFX Script, it supports the binding of many useful expressions. We talk more about constructing compound binding expressions after we cover the key concepts and interfaces of the framework.

A Motivating Example

Let’s start with an example that shows off the capabilities of the Property interface through the use of a couple of instances of the SimpleIntegerProperty class.

Listing 3-1. MotivatingExample.java

import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;

public class MotivatingExample {
    private static IntegerProperty intProperty;

    public static void main(String[] args) {
        createProperty();
        addAndRemoveInvalidationListener();
        addAndRemoveChangeListener();
        bindAndUnbindOnePropertyToAnother();
    }

    private static void createProperty() {
        System.out.println();
        intProperty = new SimpleIntegerProperty(1024);
        System.out.println("intProperty = " + intProperty);
        System.out.println("intProperty.get() = " + intProperty.get());
        System.out.println("intProperty.getValue() = " + intProperty.getValue().intValue());
    }

    private static void addAndRemoveInvalidationListener() {
        System.out.println();
        final InvalidationListener invalidationListener =
                new InvalidationListener() {
                    @Override
                    public void invalidated(Observable observable) {
                        System.out.println("The observable has been invalidated: " +images
 observable + ".");
                    }
                };

        intProperty.addListener(invalidationListener);
        System.out.println("Added invalidation listener.");

        System.out.println("Calling intProperty.set(2048).");
        intProperty.set(2048);

        System.out.println("Calling intProperty.setValue(3072).");
        intProperty.setValue(Integer.valueOf(3072));

        intProperty.removeListener(invalidationListener);
        System.out.println("Removed invalidation listener.");

        System.out.println("Calling intProperty.set(4096).");
        intProperty.set(4096);
    }

    private static void addAndRemoveChangeListener() {
        System.out.println();
        final ChangeListener changeListener = new ChangeListener() {
            @Override
            public void changed(ObservableValue observableValue, Object oldValue, Objectimages
 newValue) {
                System.out.println("The observableValue has changed: oldValue = " +images
 oldValue + ", newValue = " + newValue);
            }
        };

        intProperty.addListener(changeListener);
        System.out.println("Added change listener.");

        System.out.println("Calling intProperty.set(5120).");
        intProperty.set(5120);

        intProperty.removeListener(changeListener);
        System.out.println("Removed change listener.");

        System.out.println("Calling intProperty.set(6144).");
        intProperty.set(6144);
    }

    private static void bindAndUnbindOnePropertyToAnother() {
        System.out.println();
        IntegerProperty otherProperty = new SimpleIntegerProperty(0);
        System.out.println("otherProperty.get() = " + otherProperty.get());

        System.out.println("Binding otherProperty to intProperty.");
        otherProperty.bind(intProperty);
        System.out.println("otherProperty.get() = " + otherProperty.get());

        System.out.println("Calling intProperty.set(7168).");
        intProperty.set(7168);
        System.out.println("otherProperty.get() = " + otherProperty.get());

        System.out.println("Unbinding otherProperty from intProperty.");
        otherProperty.unbind();
        System.out.println("otherProperty.get() = " + otherProperty.get());

        System.out.println("Calling intProperty.set(8192).");
        intProperty.set(8192);
        System.out.println("otherProperty.get() = " + otherProperty.get());
    }
}

In this example we created a SimpleIntegerProperty object called intProperty with an initial value of 1024. We then updated its value through a series of different integers while we added and then removed an InvalidationListener, added and then removed a ChangeListener, and finally created another SimpleIntegerProperty named otherProperty, bound it to and then unbound it from intProperty. The sample program used a generous amount of println calls to show what is happening inside the program.

When we run the program in Listing 3-1, the following output is printed to the console:


intProperty = IntegerProperty [value: 1024]

intProperty.get() = 1024

intProperty.getValue() = 1024

Added invalidation listener.

Calling intProperty.set(2048).

The observable has been invalidated: IntegerProperty [value: 2048].

Calling intProperty.setValue(3072).

The observable has been invalidated: IntegerProperty [value: 3072].

Removed invalidation listener.

Calling intProperty.set(4096).



Added change listener.

Calling intProperty.set(5120).

The observableValue has changed: oldValue = 4096, newValue = 5120

Removed change listener.

Calling intProperty.set(6144).



otherProperty.get() = 0

Binding otherProperty to intProperty.

otherProperty.get() = 6144

Calling intProperty.set(7168).

otherProperty.get() = 7168

Unbinding otherProperty from intProperty.

otherProperty.get() = 7168

Calling intProperty.set(8192).

otherProperty.get() = 7168

By correlating the output lines with the program source code (or by stepping through the code in the debugger of your favorite IDE), we can draw the following conclusions.

  • A SimpleIntegerProperty object such as intProperty and otherProperty holds an int value. The value can be manipulated with the get(), set(), getValue(), and setValue() methods. The get() and set() methods perform their operation with the primitive int type. The getValue() and setValue() methods use the Integer wrapper type.
  • You can add and remove InvalidationListener objects to and from intProperty.
  • You can add and remove ChangeListener objects to and from intProperty.
  • Another Property object such as otherProperty can bind itself to intProperty. When that happens, otherProperty receives the value of intProperty.
  • When a new value is set on intProperty, whatever object that is attached to it is notified. The notification is not sent if the object is removed.
  • When notified, InvalidationListener objects are only informed of which object is sending out the notification and that object is only known as an Observable.
  • When notified, ChangeListener objects are informed on two more pieces of information—the oldValue and the newValue—in addition to the object sending the notification. The sending object is known as an ObservableValue.
  • In the case of a binding property such as otherProperty, we cannot tell from the output when or how it is notified of the change of value in intProperty. However, we can infer that it must have known of the change because when we asked otherProperty for its value we get back the latest value of intProperty.

images Note Even though this motivating example uses an Integer property, similar examples can be made to use properties based on the Boolean, Long, Float, Double, String, and Object types. In the JavaFX 2.0 properties and bindings framework, when interfaces are extended or implemented for concrete types, they are always done for the Boolean, Integer, Long, Float, Double, String, and Object types.

This example brings to our attention some of the key interfaces and concepts of the JavaFX 2.0 properties and bindings framework: including the Observable and the associated InvalidationListener interfaces, the ObservableValue and the associated ChangeListener interfaces, the get(), set(), getValue(), and setValue() methods that allow us to manipulate the values of a SimpleIntegerProperty object directly, and the bind() method that allows us to relinquish direct manipulation of the value of a SimpleIntegerProperty object by subordinating it to another SimpleIntegerProperty object.

In the next section we show you these and some other key interfaces and concepts of the JavaFX 2.0 properties and bindings framework in more detail.

Understanding Key Interfaces and Concepts

Figure 3-1 is an UML diagram showing the key interfaces of the JavaFX 2.0 properties and bindings framework. It includes some interfaces that you have seen in the last section, and some that you haven’t seen.

images

Figure 3-1. Key interfaces of the JavaFX 2.0 properties and bindings framework

images Note We did not show you the fully qualified names of the interfaces in the UML diagram. These interfaces are spread out in four packages: javafx.beans, javafx.beans.binding, javafx.beans.property, and javafx.beans.value. You can easily figure out which interface belongs to which package by examining the JavaFX API documentation or by the “find class” feature of your favorite IDE.

Understanding the Observable Interface

At the root of the hierarchy is the Observable interface. You can register InvalidationListener objects to an Observable object to receive invalidation events. You have already seen invalidation events fired from one kind of Observable object, the SimpleIntegerProperty object intProperty in the motivating example in the last section. It is fired when the set() or setValue() methods are called to change the underlying value from one int to a different int.

images Note An invalidation event is fired only once by any of the implementations of the Property interface in the JavaFX 2.0 properties and bindings framework if you call the setter with the same value several times in a row.

Another place where invalidation events are fired is from Binding objects. You haven’t seen an example of a Binding object yet. But there are plenty of Binding objects in the second half of this chapter. For now we just note that Binding objects may become invalid, for example, when its invalidate() method is called or, as we show later in this chapter, when one of its dependencies fires an invalidation event.

images Note An invalidation event is fired only once by any of the implementations of the Binding interface in the JavaFX 2.0 properties and bindings framework if it becomes invalid several times in a row.

Understanding the ObservableValue Interface

Next up in the hierarchy is the ObservableValue interface. It’s simply an Observable that has a value. Its getValue() method returns its value. The getValue() method that we called on the SimpleIntegerProperty objects in the motivating example can be considered to have come from this interface. You can register ChangeListener objects to an ObservableValue object to receive change events.

You have seen change events being fired in the motivating example in the last section. When the change event fires, the ChangeListener receives two more pieces of information: the old value and the new value of the ObservableValue object.

images Note A change event is fired only once by any of the implementations of the ObservableValue interface in the JavaFX 2.0 properties and bindings framework if you call the setter with the same value several times in a row.

The distinction between an invalidation event and a change event is made so that the JavaFX 2.0 properties and bindings framework may support lazy evaluations. We show an example of this by looking at three lines of code from the motivating example:

        otherProperty.bind(intProperty);
        intProperty.set(7168);
        System.out.println("otherProperty.get() = " + otherProperty.get());

When intProperty.set(7168) is called, it fires an invalidation event to otherProperty. Upon receiving this invalidation event, otherProperty simply makes a note of the fact that its value is no longer valid. It does not immediately perform a recalculation of its value by querying intProperty for its value. The recalculation is performed later when otherProperty.get() is called. Imagine if instead of calling intProperty.set() only once as in the above code we call intProperty.set() multiple times; otherProperty still recalculates its value only once.

images Note The ObservableValue interface is not the only direct subinterface of Observable. There are two other direct subinterfaces of Observable that live in the javafx.collections package: ObservableList and ObservableMap with corresponding ListChangeListener and MapChangeListener as callback mechanisms. These JavaFX 2.0 observable collections are covered in Chapter 6, “Collections and Concurrency.”

Understanding the WritableValue Interface

This may be the simplest subsection in the entire chapter, for the WritableValue interface is truly as simple as it looks. Its purpose is to inject the getValue() and setValue() methods into implementations of this interface. All implementation classes of WritableValue in the JavaFX 2.0 properties and bindings framework also implement ObservableValue, therefore you can make an argument that the value of WritableValue is only to provide the setValue() method.

You have seen the setValue() method at work in the motivating example.

Understanding the ReadOnlyProperty Interface

The ReadOnlyProperty interface injects two methods into its implementations. The getBean() method should return the Object that contains the ReadOnlyRroperty or null if it is not contained in an Object. The getName() method should return the name of the ReadOnlyProperty or the empty string if the ReadOnlyProperty does not have a name.

The containing object and the name provide contextual information about a ReadOnlyProperty. The contextual information of a property does not play any direct role in the propagation of invalidation events or the recalculation of values. However, if provided, it will be taken into account in some peripheral calculations.

In our motivating example, the intProperty is constructed without any contextual information. Had we used the full constructor to supply it a name:

        intProperty = new SimpleIntegerProperty(null, "intProperty", 1024);

the output would have contained the property name:

intProperty = IntegerProperty [name: intProperty, value: 1024]

Understanding the Property Interface

Now we come to the bottom of our key interfaces hierarchy. The Property interface has as its superinterfaces all four interfaces we have examined thus far: Observable, ObservableValue, ReadOnlyProperty, and WritableValue. Therefore it inherits all the methods from these interfaces. It also provides five methods of its own:

void bind(ObservableValue<? extends T> observableValue);
void unbind();
boolean isBound();
void bindBidirectional(Property<T> tProperty);
void unbindBidirectional(Property<T> tProperty);

You have seen two of the methods at work in the motivating example in the last section: bind() and unbind().

Calling bind() creates a unidirectional binding or a dependency between the Property object and the ObservableValue argument. Once they enter this relationship, calling the set() or setValue() methods on the Property object will cause a RuntimeException to be thrown. Calling the get() or getValue() methods on the Property object will return the value of the ObservableValue object. And, of course, changing the value of the ObservableValue object will invalidate the Property object. Calling unbind() releases any existing unidirectional binding the Property object may have. If a unidirectional binding is in effect, the isBound() method returns true; otherwise it returns false.

Calling bindBidirectional() creates a bidirectional binding between the Property caller and the Property argument. Notice that unlike the bind() method, which takes an ObservableValue argument, the bindBidirectional() method takes a Property argument. Only two Property objects can be bound together bidirectionally. Once they enter this relationship, calling the set() or setValue() methods on either Property object will cause both objects’ values to be updated. Calling unbindBidirectional() releases any existing bidirectional binding the caller and the argument may have. The program in Listing 3-2 shows a simple bidirectional binding at work.

Listing 3-2. BidirectionalBindingExample.java

import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;

public class BidirectionalBindingExample {
    public static void main(String[] args) {
        System.out.println("Constructing two StringProperty objects.");
        StringProperty prop1 = new SimpleStringProperty("");
        StringProperty prop2 = new SimpleStringProperty("");

        System.out.println("Calling bindBidirectional.");
        prop2.bindBidirectional(prop1);

        System.out.println("prop1.isBound() = " + prop1.isBound());
        System.out.println("prop2.isBound() = " + prop2.isBound());

        System.out.println("Calling prop1.set("prop1 says: Hi!")");
        prop1.set("prop1 says: Hi!");
        System.out.println("prop2.get() returned:");
        System.out.println(prop2.get());

        System.out.println("Calling prop2.set(prop2.get() + "\nprop2 says: Bye!")");
        prop2.set(prop2.get() + " prop2 says: Bye!");
        System.out.println("prop1.get() returned:");
        System.out.println(prop1.get());
    }
}

In this example we created two SimpleStringProperty objects called prop1 and prop2, created a bidirectional binding between them, and then called set() and get() on both properties.

When we run the program in Listing 3-2, the following output is printed to the console:


Constructing two StringProperty objects.

Calling bindBidirectional.

prop1.isBound() = false

prop2.isBound() = false

Calling prop1.set("prop1 says: Hi!")

prop2.get() returned:

prop1 says: Hi!

Calling prop2.set(prop2.get() + " prop2 says: Bye!")

prop1.get() returned:

prop1 says: Hi!

prop2 says: Bye!

images  Caution Each Property object may have at most one active unidirectional binding at a time. It may have as many bidirectional bindings as you want. The isBound() method pertains only to unidirectional bindings. Calling bind() a second time with a different ObservableValue argument while a unidirectional binding is already in effect will unbind the existing one and replace it with the new one.

Understanding the Binding Interface

The Binding interface defines four methods that reveal the intentions of the interface. A Binding object is an ObservableValue whose validity can be queried with the isValid() method and set with the invalidate() method. It has a list of dependencies that can be obtained with the getDependencies() method. And finally a dispose() method signals that the binding will not be used anymore and resources used by it can be cleaned up.

From this brief description of the Binding interface, we can infer that it represents a unidirectional binding with multiple dependencies. Each dependency, we imagine, could be an ObservableValue to which the Binding is registered to receive invalidation events. When the get() or getValue() method is called, if the binding is invalidated, its value is recalculated.

The JavaFX 2.0 properties and bindings framework does not provide any concrete classes that implement the Binding interface. However, it provides multiple ways to create your own Binding objects easily: you can extend the abstract base classes in the framework; you can use a set of static methods in the utility class Bindings to create new bindings out of existing regular Java values (i.e., unobservable values), properties, and bindings; you can also use a set of methods that are provided in the various properties and bindings classes and form a fluent interface API to create new bindings. We go through the utility methods and the fluent interface API in the Creating Bindings section later in this chapter. For now, we show you the first example of a binding by extending the DoubleBinding abstract class. The program in Listing 3-3 uses a binding to calculate the area of a rectangle.

Listing 3-3. RectangleAreaExample.java

import javafx.beans.binding.DoubleBinding;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;

public class RectangleAreaExample {
    public static void main(String[] args) {
        System.out.println("Constructing x with initial value of 2.0.");
        final DoubleProperty x = new SimpleDoubleProperty(null, "x", 2.0);
        System.out.println("Constructing y with initial value of 3.0.");
        final DoubleProperty y = new SimpleDoubleProperty(null, "y", 3.0);
        System.out.println("Creating binding area with dependencies x and y.");
        DoubleBinding area = new DoubleBinding() {
            private double value;

            {
                super.bind(x, y);
            }

            @Override
            protected double computeValue() {
                System.out.println("computeValue() is called.");
                return x.get() * y.get();
            }
        };
        System.out.println("area.get() = " + area.get());
        System.out.println("area.get() = " + area.get());
        System.out.println("Setting x to 5");
        x.set(5);
        System.out.println("Setting y to 7");
        y.set(7);
        System.out.println("area.get() = " + area.get());
    }
}

In the anonymous inner class, we called the protected bind() method in the superclass DoubleBinding, informing the superclass that we would like to listen to invalidation events from the DoubleProperty objects x and y. We finally implemented the protected abstract computeValue() method in the superclass DoubleBinding to do the actual calculation when a recalculation is needed.

When we run the program in Listing 3-3, the following output is printed to the console:


Constructing x with initial value of 2.0.

Constructing y with initial value of 3.0.

Creating binding area with dependencies x and y.

computeValue() is called.

area.get() = 6.0

area.get() = 6.0

Setting x to 5

Setting y to 7

computeValue() is called.

area.get() = 35.0

Notice that computeValue() is called only once when we call area.get() twice in a row.

images  Caution The DoubleBinding abstract class contains a default implementation of dispose() that is empty and a default implementation of getDependencies() that returns an empty list. To make this example a correct Binding implementation we should override these two methods to behave correctly.

Now that you have a firm grasp of the key interfaces and concepts of the JavaFX 2.0 properties and bindings framework, we show you how these generic interfaces are specialized to type-specific interfaces and implemented in type-specific abstract and concrete classes.

Type-Specific Specializations of Key Interfaces

We did not emphasize this fact in the last section because we believe its omission does not hurt the explanations there, but except for Observable and InvalidationListener, the rest of the interfaces are generic interfaces with a type parameter <T>. In this section we examine how these generic interfaces are specialized to the specific types of interest: Boolean, Integer, Long, Float, Double, String, and Object. We also examine some of the abstract and concrete classes of the framework and explore typical usage scenarios of each class.

A Common Theme for Type-Specific Interfaces

Although the generic interfaces are not all specialized in exactly the same way, a common theme exists:

  • The Boolean type is specialized directly.
  • The Integer, Long, Float, and Double types are specialized through the Number supertype.
  • The String type is specialized through the Object type.

This theme exists in the type-specific specializations of all the key interfaces. As an example, we examine the subinterfaces of the ObservableValue<T> interface:

  • ObservableBooleanValue extends ObservableValue<Boolean>, and it offers one additional method.
    • boolean get();
  • ObservableNumberValue extends ObservableValue<Number>, and it offers four additional methods.
    • int intValue();
    • long longValue();
    • float floatValue();
    • double doubleValue();
  • ObservableObjectValue<T> extends ObservableValue<t>, and it offers one additional method.
    • T get();
  • ObservableIntegerValue, ObservableLongValue, ObservableFloatValue, and ObservableDoubleValue extend ObservableNumberValue and each offers an additional get() method that returns the appropriate primitive type value.
  • ObservableStringValue extends ObservableObjectValue<String> and inherits its get() method that returns String.

Notice that the get() method that we have been using in the examples is defined in the type-specific ObservableValue subinterfaces. A similar examination reveals that the set() method that we have been using in the examples is defined in the type-specific WritableValue subinterfaces.

A practical consequence of this derivation hierarchy is that any numerical property can call bind() on any other numerical property or binding. Indeed, the signature of the bind() method on any numerical property is and any numerical property and binding is assignable to the generic parameter type. The program in Listing 3-2 shows that any numerical properties of different specific types can be bound to each other.

void bind(ObservableValue<? extends Number>  observable);

Listing 3-4. NumericPropertiesExample.java

import javafx.beans.property.DoubleProperty;
import javafx.beans.property.FloatProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.LongProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleFloatProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleLongProperty;

public class NumericPropertiesExample {
    public static void main(String[] args) {
        IntegerProperty i = new SimpleIntegerProperty(null, "i", 1024);
        LongProperty l = new SimpleLongProperty(null, "l", 0L);
        FloatProperty f = new SimpleFloatProperty(null, "f", 0.0F);
        DoubleProperty d = new SimpleDoubleProperty(null, "d", 0.0);
        System.out.println("Constructed numerical properties i, l, f, d.");

        System.out.println("i.get() = " + i.get());
        System.out.println("l.get() = " + l.get());
        System.out.println("f.get() = " + f.get());
        System.out.println("d.get() = " + d.get());

        l.bind(i);
        f.bind(l);
        d.bind(f);
        System.out.println("Bound l to i, f to l, d to f.");

        System.out.println("i.get() = " + i.get());
        System.out.println("l.get() = " + l.get());
        System.out.println("f.get() = " + f.get());
        System.out.println("d.get() = " + d.get());

        System.out.println("Calling i.set(2048).");
        i.set(2048);

        System.out.println("i.get() = " + i.get());
        System.out.println("l.get() = " + l.get());
        System.out.println("f.get() = " + f.get());
        System.out.println("d.get() = " + d.get());

        d.unbind();
        f.unbind();
        l.unbind();
        System.out.println("Unbound l to i, f to l, d to f.");

        f.bind(d);
        l.bind(f);
        i.bind(l);
        System.out.println("Bound f to d, l to f, i to l.");

        System.out.println("Calling d.set(10000000000L).");
        d.set(10000000000L);

        System.out.println("d.get() = " + d.get());
        System.out.println("f.get() = " + f.get());
        System.out.println("l.get() = " + l.get());
        System.out.println("i.get() = " + i.get());
    }
}

In this example we created four numeric properties and bound them into a chain in decreasing size to demonstrate that the bindings work as expected. We then reversed the order of the chain and set the double property’s value to a number that would overflow the integer property to highlight the fact that even though you can bind different sizes of numeric properties together, when the value of the dependent property is outside the range of the binding property, normal Java numeric conversion applies.

When we run the program in Listing 3-4, the following is printed to the console:


Constructed numerical properties i, l, f, d.

i.get() = 1024

l.get() = 0

f.get() = 0.0

d.get() = 0.0

Bound l to i, f to l, d to f.

i.get() = 1024

l.get() = 1024

f.get() = 1024.0

d.get() = 1024.0

Calling i.set(2048).

i.get() = 2048

l.get() = 2048

f.get() = 2048.0

d.get() = 2048.0

Unbound l to i, f to l, d to f.

Bound f to d, l to f, i to l.

Calling d.set(10000000000L).

d.get() = 1.0E10

f.get() = 1.0E10

l.get() = 10000000000

i.get() = 1410065408

Commonly Used Classes

We now give a survey of the content of the four packages javafx.beans, javafx.beans.binding, javafx.beans.property, and javafx.beans.value. In this section, “the SimpleIntegerProperty series of classes” refers to the classes extrapolated over the Boolean, Integer, Long, Float, Double, String, and Object types. Therefore what is said also applies to SimpleBooleanProperty, and so on.

  • The most often used classes in the JavaFX 2.0 properties and bindings framework are the SimpleIntegerProperty series of classes. They provide all the functionalities of the Property interface including lazy evaluation. They are used in all the examples of this chapter up to this point.
  • Another set of concrete classes in the JavaFX 2.0 properties and bindings framework is the ReadOnlyIntegerWrapper series of classes. These classes implement the Property interface but also have a getReadOnlyProperty() method that returns a ReadOnlyProperty that is synchronized with the main Property. They are very handy to use when you need a full-blown Property for the implementation of a component but you only want to hand out a ReadOnlyProperty to the client of the component.
  • The IntegerPropertyBase series of abstract classes can be extended to provide implementations of full Property classes, although in practice the SimpleIntegerProperty series of classes is easier to use. The only abstract methods in the IntegerPropertyBase series of classes are getBean() and getName().
  • The ReadOnlyIntegerPropertyBase series of abstract classes can be extended to provide implementations of ReadOnlyProperty classes. This is rarely necessary. The only abstract methods in the ReadOnlyIntegerPropertyBase series of classes are get(), getBean(), and getName().
  • The WeakInvalidationListener and WeakChangeListener classes can be used to wrap InvalidationListener and ChangeListener instances before addListener() is called. They hold weak references of the wrapped listener instances. As long as you hold a reference to the wrapped listener on your side, the weak references will be kept alive and you will receive events. When you are done with the wrapped listener and have unreferenced it from your side, the weak references will be eligible for garbage collection and later garbage collected. All the JavaFX 2.0 properties and bindings framework Observable objects know how to clean up a weak listener after its weak reference has been garbage collected. This prevents memory leaks when the listeners are not removed after use.

That covers all the JavaFX 2.0 properties and bindings API that reside in the javafx.beans, javafx.beans.property, and javafx.beans.value packages and some but not all of the APIs in the javafx.beans.binding package. The remaining classes of the javafx.beans.binding package are APIs that help you to create new bindings out of existing properties and bindings. That is the focus of the next section.

Creating Bindings

We now turn our focus to the creation of new bindings out of existing properties and bindings. You learned in the “Understanding Key Interfaces and Concepts” section earlier in this chapter that a binding is an observable value that has a list of dependencies which are also observable values.

The JavaFX 2.0 properties and bindings framework offers three ways of creating new bindings:

  • Extending the IntegerBinding series of abstract classes
  • Using the bindings creating static methods in the utilities class Bindings
  • Using the fluent interface API provided by the IntegerExpression series of abstract classes

You saw the direct extension approach in the “Understanding the Binding Interface” section earlier in this chapter. We explore the Bindings utility class next.

Understanding the Bindings Utility Class

The Bindings class contains 163 factory methods that make new bindings out of existing observable values and regular values. Most of the methods are overloaded to take into account that both observable values and regular Java (unobservable) values can be used to build new bindings. At least one of the parameters must be an observable value. Here are the signatures of the nine overloaded add() methods:

public static NumberBinding add(ObservableNumberValue n1, ObservableNumberValue n2)
public static DoubleBinding add(ObservableNumberValue n, double d)
public static DoubleBinding add(double d, ObservableNumberValue n)
public static NumberBinding add(ObservableNumberValue n, float f)
public static NumberBinding add(float f, ObservableNumberValue n)
public static NumberBinding add(ObservableNumberValue n, long l)
public static NumberBinding add(long l, ObservableNumberValue n)
public static NumberBinding add(ObservableNumberValue n, int i)
public static NumberBinding add(int i, ObservableNumberValue n)

When the add() method is called, it returns a NumberBinding whose dependencies include all the observable value parameters, and whose value is the sum of the value of its two parameters. Similarly overloaded methods exist for subtract(), multiply(), and divide().

images Note Recall from the last section that ObservableIntegerValue, ObservableLongValue, ObservableFloatValue, and ObservableDoubleValue are subclasses of ObservableNumberValue. Therefore the four arithmetic methods mentioned above can take any combinations of these observable numeric values as well as any unobservable values.

The program in Listing 3-5 uses the arithmetic methods in Bindings to calculate the area of a triangle in the Cartesian plane with vertices (x1, y1), (x2, y2), (x3, y3) using this formula:

Area = (x1*y2 + x2*y3 + x3*y1 – x1*y3 – x2*y1 – x3*y2) / 2

Listing 3-5. TriangleAreaExample.java

import javafx.beans.binding.Bindings;
import javafx.beans.binding.NumberBinding;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;

public class TriangleAreaExample {
    public static void main(String[] args) {
        IntegerProperty x1 = new SimpleIntegerProperty(0);
        IntegerProperty y1 = new SimpleIntegerProperty(0);
        IntegerProperty x2 = new SimpleIntegerProperty(0);
        IntegerProperty y2 = new SimpleIntegerProperty(0);
        IntegerProperty x3 = new SimpleIntegerProperty(0);
        IntegerProperty y3 = new SimpleIntegerProperty(0);

        final NumberBinding x1y2 = Bindings.multiply(x1, y2);
        final NumberBinding x2y3 = Bindings.multiply(x2, y3);
        final NumberBinding x3y1 = Bindings.multiply(x3, y1);
        final NumberBinding x1y3 = Bindings.multiply(x1, y3);
        final NumberBinding x2y1 = Bindings.multiply(x2, y1);
        final NumberBinding x3y2 = Bindings.multiply(x3, y2);

        final NumberBinding sum1 = Bindings.add(x1y2, x2y3);
        final NumberBinding sum2 = Bindings.add(sum1, x3y1);
        final NumberBinding sum3 = Bindings.add(sum2, x3y1);
        final NumberBinding diff1 = Bindings.subtract(sum3, x1y3);
        final NumberBinding diff2 = Bindings.subtract(diff1, x2y1);
        final NumberBinding determinant = Bindings.subtract(diff2, x3y2);
        final NumberBinding area = Bindings.divide(determinant, 2.0D);

        x1.set(0); y1.set(0);
        x2.set(6); y2.set(0);
        x3.set(4); y3.set(3);

        printResult(x1, y1, x2, y2, x3, y3, area);

        x1.set(1); y1.set(0);
        x2.set(2); y2.set(2);
        x3.set(0); y3.set(1);

        printResult(x1, y1, x2, y2, x3, y3, area);
    }

    private static void printResult(IntegerProperty x1, IntegerProperty y1,
                                    IntegerProperty x2, IntegerProperty y2,
                                    IntegerProperty x3, IntegerProperty y3,
                                    NumberBinding area) {
        System.out.println("For A(" +
                x1.get() + "," + y1.get() + "), B(" +
                x2.get() + "," + y2.get() + "), C(" +
                x3.get() + "," + y3.get() + "), the area of triangle ABC is " + area.getValue());
    }
}

We used IntegerProperty to represent the co-ordinates. The building up of the NumberBinding area uses all four arithmetic factory methods of Bindings. Because we started with IntegerProperty objects, even though the return type from the arithmetic factory methods of Bindings are NumberBinding, the actual object that is returned, up to determinant, are IntegerBinding objects. We used 2.0D rather than a mere 2 in the divide() call to force the division to be done as a double division, not as int division. All the properties and bindings that we build up form a tree structure with area as the root, the intermediate bindings as internal nodes, and the properties x1, y1, x2, y2, x3, y3 as leaves. This tree is similar to the parse tree we will get if we parse the mathematical expression for the area formula using grammar for the regular arithmetic expressions.

When we run the program in Listing 3-5, the following output is printed to the console:


For A(0,0), B(6,0), C(4,3), the area of triangle ABC is 9.0

For A(1,0), B(2,2), C(0,1), the area of triangle ABC is 1.5

Aside from the arithmetic methods, the Bindings class also has the following factory methods.

  • Logical operators: and, or, not
  • Numeric operators: min, max, negate
  • Object operators: isNull, isNotNull
  • Relational operators:
    • equal
    • equalIgnoreCase
    • greaterThan
    • greaterThanOrEqual
    • lessThan
    • lessThanOrEqual
    • notEqual
    • notEqualIgnoreCase
  • Selection operators:
    • select
    • selectBoolean
    • selectInteger
    • selectLong
    • selectFloat
    • selectDouble
    • selectString

Except for the selection operators, the preceding operators all do what you think they will do. The object operators are meaningful only for observable string values and observable object values. All relational operators except for the IgnoreCase ones apply to numeric values. There are versions of the equal and notEqual operators for numeric values that have a third double parameter for the tolerance when comparing float or double values. The equal and notEqual operators also apply to boolean, string, and object values. For string and object values, the equal and notEqual operator compares their values using the equals() method.

The selection operators operate on what are called JavaFX beans, Java classes constructed according to the JavaFX Beans specification. We talk about JavaFX Beans in the “Understanding JavaFX Beans” section later in this chapter.

That covers all methods in Bindings that return a binding object. There are seven methods in Bindings that do not return a binding object. The bindBidirectional() and unbindBidirectional() methods create bidirectional bindings. As a matter of fact, the bindBidirectional() and unbindBidirectional() methods in the various properties classes simply call the corresponding ones in the Bindings class. Four of the other five methods, convert(), concat(), and a pair of overloaded format(), return StringExpression objects. And finally the when() method returns a When object.

The When and the StringExpression classes are part of the fluent interface API for creating bindings, which we cover in the next subsection.

Understanding the Fluent Interface API

If you asked the question: “Why would anybody name a method when()? And what kind of information would the When class encapsulate?” Welcome to the club. While you were not looking, the object-oriented programming community invented a brand new way of API design that totally disregards the decades-old principles of object-oriented practices. Instead of encapsulating data and distributing business logic into relevant domain objects, this new methodology produces a style of API that encourages method chaining and uses the return type of one method to determine what methods are available for the next car of the choo-choo train. Method names are chosen not to convey complete meaning but to make the entire method chain read like a fluent sentence. This style of APIs is called fluent interface APIs.

images Note You can find a more through exposition of fluent interfaces on Martin Fowler’s web site, referenced at the end of this chapter.

The fluent interface APIs for creating bindings are defined in the IntegerExpression series of classes. IntegerExpression is a superclass of both IntegerProperty and IntegerBinding, making the methods of IntegerExpression also available in the IntegerProperty and IntegerBinding classes. The four numeric expression classes share a common superinterface NumberExpression, where all the methods are defined. The type-specific expression classes override some of the methods that yield a NumberBinding to return a more appropriate type of binding.

The methods thus made available for the seven kinds of properties and bindings are listed here:

  • For BooleanProperty and BooleanBinding
    • BooleanBinding and(ObservableBooleanValue b)
    • BooleanBinding or(ObservableBooleanValue b)
    • BooleanBinding not()
    • BooleanBinding isEqualTo(ObservableBooleanValue b)
    • BooleanBinding isNotEqualTo(ObservableBooleanValue b)
    • StringBinding asString()
  • Common for all numeric properties and bindings
    • BooleanBinding isEqualTo(ObservableNumberValue m)
    • BooleanBinding isEqualTo(ObservableNumberValue m, double err)
    • BooleanBinding isEqualTo(double d, double err)
    • BooleanBinding isEqualTo(float f, double err)
    • BooleanBinding isEqualTo(long l)
    • BooleanBinding isEqualTo(long l, double err)
    • BooleanBinding isEqualTo(int i)
    • BooleanBinding isEqualTo(int i, double err)
    • BooleanBinding isNotEqualTo(ObservableNumberValue m)
    • BooleanBinding isNotEqualTo(ObservableNumberValue m, double err)
    • BooleanBinding isNotEqualTo(double d, double err)
    • BooleanBinding isNotEqualTo(float f, double err)
    • BooleanBinding isNotEqualTo(long l)
    • BooleanBinding isNotEqualTo(long l, double err)
    • BooleanBinding isNotEqualTo(int i)
    • BooleanBinding isNotEqualTo(int i, double err)
    • BooleanBinding greaterThan(ObservableNumberValue m)
    • BooleanBinding greaterThan(double d)
    • BooleanBinding greaterThan(float f)
    • BooleanBinding greaterThan(long l)
    • BooleanBinding greaterThan(int i)
    • BooleanBinding lessThan(ObservableNumberValue m)
    • BooleanBinding lessThan(double d)
    • BooleanBinding lessThan(float f)
    • BooleanBinding lessThan(long l)
    • BooleanBinding lessThan(int i)
    • BooleanBinding greaterThanOrEqualTo(ObservableNumberValue m)
    • BooleanBinding greaterThanOrEqualTo(double d)
    • BooleanBinding greaterThanOrEqualTo(float f)
    • BooleanBinding greaterThanOrEqualTo(long l)
    • BooleanBinding greaterThanOrEqualTo(int i)
    • BooleanBinding lessThanOrEqualTo(ObservableNumberValue m)
    • BooleanBinding lessThanOrEqualTo(double d)
    • BooleanBinding lessThanOrEqualTo(float f)
    • BooleanBinding lessThanOrEqualTo(long l)
    • BooleanBinding lessThanOrEqualTo(int i)
    • StringBinding asString()
    • StringBinding asString(String str)
    • StringBinding asString(Locale locale, String str)
  • For IntegerProperty and IntegerBinding
    • IntegerBinding negate()
    • NumberBinding add(ObservableNumberValue n)
    • DoubleBinding add(double d)
    • FloatBinding add(float f)
    • LongBinding add(long l)
    • IntegerBinding add(int i)
    • NumberBinding subtract(ObservableNumberValue n)
    • DoubleBinding subtract(double d)
    • FloatBinding subtract(float f)
    • LongBinding subtract(long l)
    • IntegerBinding subtract(int i)
    • NumberBinding multiply(ObservableNumberValue n)
    • DoubleBinding multiply(double d)
    • FloatBinding multiply(float f)
    • LongBinding multiply(long l)
    • IntegerBinding multiply(int i)
    • NumberBinding divide(ObservableNumberValue n)
    • DoubleBinding divide(double d)
    • FloatBinding divide(float f)
    • LongBinding divide(long l)
    • IntegerBinding divide(int i)
  • For LongProperty and LongBinding
    • LongBinding negate()
    • NumberBinding add(ObservableNumberValue n)
    • DoubleBinding add(double d)
    • FloatBinding add(float f)
    • LongBinding add(long l)
    • LongBinding add(int i)
    • NumberBinding subtract(ObservableNumberValue n)
    • DoubleBinding subtract(double d)
    • FloatBinding subtract(float f)
    • LongBinding subtract(long l)
    • LongBinding subtract(int i)
    • NumberBinding multiply(ObservableNumberValue n)
    • DoubleBinding multiply(double d)
    • FloatBinding multiply(float f)
    • LongBinding multiply(long l)
    • LongBinding multiply(int i)
    • NumberBinding divide(ObservableNumberValue n)
    • DoubleBinding divide(double d)
    • FloatBinding divide(float f)
    • LongBinding divide(long l)
    • LongBinding divide(int i)
  • For FloatProperty and FloatBinding
    • FloatBinding negate()
    • NumberBinding add(ObservableNumberValue n)
    • DoubleBinding add(double d)
    • FloatBinding add(float g)
    • FloatBinding add(long l)
    • FloatBinding add(int i)
    • NumberBinding subtract(ObservableNumberValue n)
    • DoubleBinding subtract(double d)
    • FloatBinding subtract(float g)
    • FloatBinding subtract(long l)
    • FloatBinding subtract(int i)
    • NumberBinding multiply(ObservableNumberValue n)
    • DoubleBinding multiply(double d)
    • FloatBinding multiply(float g)
    • FloatBinding multiply(long l)
    • FloatBinding multiply(int i)
    • NumberBinding divide(ObservableNumberValue n)
    • DoubleBinding divide(double d)
    • FloatBinding divide(float g)
    • FloatBinding divide(long l)
    • FloatBinding divide(int i)
  • For DoubleProperty and DoubleBinding
    • DoubleBinding negate()
    • DoubleBinding add(ObservableNumberValue n)
    • DoubleBinding add(double d)
    • DoubleBinding add(float f)
    • DoubleBinding add(long l)
    • DoubleBinding add(int i)
    • DoubleBinding subtract(ObservableNumberValue n)
    • DoubleBinding subtract(double d)
    • DoubleBinding subtract(float f)
    • DoubleBinding subtract(long l)
    • DoubleBinding subtract(int i)
    • DoubleBinding multiply(ObservableNumberValue n)
    • DoubleBinding multiply(double d)
    • DoubleBinding multiply(float f)
    • DoubleBinding multiply(long l)
    • DoubleBinding multiply(int i)
    • DoubleBinding divide(ObservableNumberValue n)
    • DoubleBinding divide(double d)
    • DoubleBinding divide(float f)
    • DoubleBinding divide(long l)
    • DoubleBinding divide(int i)
  • For StringProperty and StringBinding
    • StringExpression concat(Object obj)
    • BooleanBinding isEqualTo(ObservableStringValue str)
    • BooleanBinding isEqualTo(String str)
    • BooleanBinding isNotEqualTo(ObservableStringValue str)
    • BooleanBinding isNotEqualTo(String str)
    • BooleanBinding isEqualToIgnoreCase(ObservableStringValue str)
    • BooleanBinding isEqualToIgnoreCase(String str)
    • BooleanBinding isNotEqualToIgnoreCase(ObservableStringValue str)
    • BooleanBinding isNotEqualToIgnoreCase(String str)
    • BooleanBinding greaterThan(ObservableStringValue str)
    • BooleanBinding greaterThan(String str)
    • BooleanBinding lessThan(ObservableStringValue str)
    • BooleanBinding lessThan(String str)
    • BooleanBinding greaterThanOrEqualTo(ObservableStringValue str)
    • BooleanBinding greaterThanOrEqualTo(String str)
    • BooleanBinding lessThanOrEqualTo(ObservableStringValue str)
    • BooleanBinding lessThanOrEqualTo(String str)
    • BooleanBinding isNull()
    • BooleanBinding isNotNull()
  • For ObjectProperty and ObjectBinding
    • BooleanBinding isEqualTo(ObservableObjectValue<?> obj)
    • BooleanBinding isEqualTo(Object obj)
    • BooleanBinding isNotEqualTo(ObservableObjectValue<?> obj)
    • BooleanBinding isNotEqualTo(Object obj)
    • BooleanBinding isNull()
    • BooleanBinding isNotNull()

With these methods, you can create an infinite variety of bindings by starting with a property and calling one of the methods that is appropriate for the type of the property to get a binding, and calling one of the methods that is appropriate for the type of the binding to get another binding, and so on. One fact that is worth pointing out here is that all the methods for the type-specific numeric expressions are defined in the NumberExpression base interface with a return type of NumberBinding, and are overridden in the type-specific expression classes with an identical parameter signature but a more specific return type. This way of overriding a method in a subclass with an identical parameter signature but a more specific return type is called covariant return-type overriding, and has been a Java language feature since Java 5. One of the consequences of this fact is that numeric bindings built with the fluent interface API have more specific types than those built with factory methods in the Bindings class.

The program in Listing 3-6 is a modification of the triangle area example in Listing 3-5 that uses the fluent interface API instead of calling factory methods in the Bindings class.

Listing 3-6. TriangleAreaFluentExample.java

import javafx.beans.binding.Bindings;
import javafx.beans.binding.NumberBinding;
import javafx.beans.binding.StringExpression;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;

public class TriangleAreaFluentExample {
    public static void main(String[] args) {
        IntegerProperty x1 = new SimpleIntegerProperty(0);
        IntegerProperty y1 = new SimpleIntegerProperty(0);
        IntegerProperty x2 = new SimpleIntegerProperty(0);
        IntegerProperty y2 = new SimpleIntegerProperty(0);
        IntegerProperty x3 = new SimpleIntegerProperty(0);
        IntegerProperty y3 = new SimpleIntegerProperty(0);

        final NumberBinding area = x1.multiply(y2)
                .add(x2.multiply(y3))
                .add(x3.multiply(y1))
                .subtract(x1.multiply(y3))
                .subtract(x2.multiply(y1))
                .subtract(x3.multiply(y2))
                .divide(2.0D);

        StringExpression output = Bindings.format(
                "For A(%d,%d), B(%d,%d), C(%d,%d), the area of triangle ABC is %3.1f",
                x1, y1, x2, y2, x3, y3, area);

        x1.set(0); y1.set(0);
        x2.set(6); y2.set(0);
        x3.set(4); y3.set(3);

        System.out.println(output.get());

        x1.set(1); y1.set(0);
        x2.set(2); y2.set(2);
        x3.set(0); y3.set(1);

        System.out.println(output.get());
    }
}

Notice how the 13 lines of code and 12 intermediate variables used in Listing 3-5 to build up the area binding are reduced to the 7 lines of code with no intermediate variables used in Listing 3-6. We also used the Bindings.format() method to build up a StringExpression object called output. There are two overloaded Bindings.format() methods with signatures:

StringExpression format(Locale locale, String format, Object... args)
StringExpression format(String format, Object... args)

They work similarly to the corresponding String.format() methods in that they format the values args according to the format specification format and the Locale locale, or the default Locale. If any of the args is an ObservableValue, its change is reflected in the StringExpression.

When we run the program in Listing 3-6, the following output is printed to the console:


For A(0,0), B(6,0), C(4,3), the area of triangle ABC is 9.0

For A(1,0), B(2,2), C(0,1), the area of triangle ABC is 1.5

Next we unravel the mystery of the When class and the role it plays in constructing bindings that are essentially if/then/else expressions. The When class has a constructor that takes an ObservableBooleanValue argument:

public When(ObservableBooleanValue b)

It has the following 11 overloaded then() methods.

When.NumberConditionBuilder then(ObservableNumberValue n)
When.NumberConditionBuilder then(double d)
When.NumberConditionBuilder then(float f)
When.NumberConditionBuilder then(long l)
When.NumberConditionBuilder then(int i)
When.BooleanConditionBuilder then(ObservableBooleanValue b)
When.BooleanConditionBuilder then(boolean b)
When.StringConditionBuilder then(ObservableStringValue str)
When.StringConditionBuilder then(String str)
When.ObjectConditionBuilder<T> then(ObservableObjectValue<T> obj)
When.ObjectConditionBuilder<T> then(T obj)

The type of object returned from the then() method depends on the type of the argument. If the argument is a numeric type, either observable or unobservable, the return type is a nested class When.NumberConditionBuilder. Similarly, for Boolean arguments, the return type is When.BooleanConditionBuilder; for string arguments, When.StringConditionBuilder; and for object arguments, When.ObjectConditionBuilder.

These condition builders in turn have the following otherwise() methods.

  • For When.NumberConditionBuilder
    • NumberBinding otherwise(ObservableNumberValue n)
    • DoubleBinding otherwise(double d)
    • NumberBinding otherwise(float f)
    • NumberBinding otherwise(long l)
    • NumberBinding otherwise(int i)
  • For When.BooleanConditionBuilder
    • BooleanBinding otherwise(ObservableBooleanValue b)
    • BooleanBinding otherwise(boolean b)
  • For When.StringConditionBuilder
    • StringBinding otherwise(ObservableStringValue str)
    • StringBinding otherwise(String str)
  • For When.ObjectConditionBuilder
    • ObjectBinding<T> otherwise(ObservableObjectValue<T>  obj)
    • ObjectBinding<T> otherwise(T obj)

The net effect of these method signatures is that you can build up a binding that resembles an if/then/else expression this way:

new When(b).then(x).otherwise(y)

where b is an ObservableBooleanValue, and x and y are of similar types and can be either observable or unobservable. The resulting binding will be of a type similar to that of x and y.

The program in Listing 3-7 uses the fluent interface API from the When class to calculate the area of a triangle with given sides a, b, and c. Recall that to form a triangle, the three sides must satisfy the following conditions.

a + b > c, b + c > a, c + a > b,

When the preceding conditions are satisfied, the area of the triangle can be calculated using Heron’s formula:

Area = sqrt(s * (s – a) * (s – b) * (s – c))

where s is the semiperimeter:

s = (a + b + c) / 2.

Listing 3-7. HeronsFormulaExample.java

import javafx.beans.binding.DoubleBinding;
import javafx.beans.binding.When;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
public class HeronsFormulaExample {
    public static void main(String[] args) {
        DoubleProperty a = new SimpleDoubleProperty(0);
        DoubleProperty b = new SimpleDoubleProperty(0);
        DoubleProperty c = new SimpleDoubleProperty(0);

        DoubleBinding s = a.add(b).add(c).divide(2.0D);

        final DoubleBinding areaSquared = new When(
                        a.add(b).greaterThan(c)
                        .and(b.add(c).greaterThan(a))
                        .and(c.add(a).greaterThan(b)))
                .then(s.multiply(s.subtract(a))
                        .multiply(s.subtract(b))
                        .multiply(s.subtract(c)))
                .otherwise(0.0D);

        a.set(3);
        b.set(4);
        c.set(5);
        System.out.printf("Given sides a = %1.0f, b = %1.0f, and c = %1.0f," +
                " the area of the triangle is %3.2f ", a.get(), b.get(), c.get(),
                Math.sqrt(areaSquared.get()));

        a.set(2);
        b.set(2);
        c.set(2);
        System.out.printf("Given sides a = %1.0f, b = %1.0f, and c = %1.0f," +
                " the area of the triangle is %3.2f ", a.get(), b.get(), c.get(),
                Math.sqrt(areaSquared.get()));
    }
}

Inasmuch as there is no ready-made binding method in DoubleExpression that calculates the square root, we create a DoubleBinding for areaSquared instead. The constructor argument for When() is a BooleanBinding built out of the three conditions on a, b, and c. The argument for the then() method is a DoubleBinding that calculates the square of the area of the triangle. And because the then() argument is numeric, the otherwise() argument also has to be numeric. We choose to use 0.0D to signal that an invalid triangle is encountered.

images Note Instead of using the When() constructor, you can also use the factory method when() in the Bindings utility class to create the When object.

When we run the program in Listing 3-7, the following output is printed to the console:


Given sides a = 3, b = 4, and c = 5, the area of the triangle is 6.00.

Given sides a = 2, b = 2, and c = 2, the area of the triangle is 1.73.

If the binding defined in Listing 3-7 makes your head spin a little, you are not alone. We choose this example simply to illustrate the use of the fluent interface API offered by the When class. As a matter of fact, this example may be better served with a direct subclassing approach we first introduced in the “Understanding the Binding Interface” section earlier in this chapter.

The program in Listing 3-8 solves the same problem as Listing 3-7 by using the direct extension method.

Listing 3-8. HeronsFormulaDirectExtensionExample.java

import javafx.beans.binding.DoubleBinding;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;

public class HeronsFormulaDirectExtensionExample {
    public static void main(String[] args) {
        final DoubleProperty a = new SimpleDoubleProperty(0);
        final DoubleProperty b = new SimpleDoubleProperty(0);
        final DoubleProperty c = new SimpleDoubleProperty(0);

        DoubleBinding area = new DoubleBinding() {
            {
                super.bind(a, b, c);
            }
            @Override
            protected double computeValue() {
                double a0 = a.get();
                double b0 = b.get();
                double c0 = c.get();

                if ((a0 + b0 > c0) && (b0 + c0 > a0) && (c0 + a0 > b0)) {
                    double s = (a0 + b0 + c0) / 2.0D;
                    return Math.sqrt(s * (s - a0) * (s - b0) * (s - c0));
                } else {
                    return 0.0D;
                }
            }
        };

        a.set(3);
        b.set(4);
        c.set(5);
        System.out.printf("Given sides a = %1.0f, b = %1.0f, and c = %1.0f," +
                " the area of the triangle is %3.2f ", a.get(), b.get(), c.get(),
                area.get());

        a.set(2);
        b.set(2);
        c.set(2);
        System.out.printf("Given sides a = %1.0f, b = %1.0f, and c = %1.0f," +
                " the area of the triangle is %3.2f ", a.get(), b.get(), c.get(),
                area.get());
    }
}

The direct extension method is preferred for complicated expressions and for expressions that go beyond the available operators.

Now that you have mastered all the APIs in the javafx.beans, javafx.beans.binding, javafx.beans.property, and javafx.beans.value packages, you are ready step beyond the details of the JavaFX 2.0 properties and bindings framework and learn how these properties are organized into bigger components called JavaFX Beans.

Understanding the JavaFX Beans Convention

JavaFX 2.0 introduces the concept of JavaFX Beans, a set of conventions that provide properties support for Java objects. In this section, we talk about the naming conventions for specifying JavaFX Beans properties, several ways of implementing JavaFX Beans properties, and finally the use of selection bindings.

The JavaFX Beans Specification

For many years Java has used the JavaBeans API to represent a property of an object. A JavaBeans property is represented by a pair of getter and setter methods. Property changes are propagated to property change listeners through the firing of property change events in the setter code.

JavaFX 2.0 introduces the JavaFX Beans specification that adds properties support to Java objects through the help of the properties classes from the JavaFX 2.0 properties and bindings framework.

images  Caution The word property is used here with two distinct meanings. When we say JavaFX Beans properties, it should be understood to mean a higher-level concept similar to JavaBeans properties. When we say JavaFX 2.0 properties and bindings framework properties, it should be understood to mean the various implementations of the Property or ReadOnlyProperty interfaces, such as IntegerProperty, StringProperty, and so on. JavaFX Beans properties are specified using JavaFX 2.0 properties and bindings framework properties.

Like its JavaBeans counterparts, JavaFX Beans properties are specified by a set of methods in a Java class. To define a JavaFX Beans property in a Java class, you provide three methods: the getter, the setter, and the property getter. For a property named height of type double, the three methods are:

public final double getHeight();
public final void setHeight(double h);
public DoubleProperty heightProperty();

The names of the getter and setter methods follow the JavaBeans convention. They are obtained by concatenating “get” and “set” with the name of the property with the first character capitalized. For boolean type properties, the getter name can also start with “is”. The name of the property getter is obtained by concatenating the name of the property with “Property”. To define a read only JavaFX Beans property, you can either remove the setter method or change it to a private method and change the return type of the property getter to be a ReadOnlyProperty.

This specification speaks only about the interface of JavaFX Beans properties and does not impose any implementation constraints. Depending on the number of properties a JavaFX Bean may have, and the usage patterns of these properties, there are several implementation strategies. Not surprisingly, all of them use the JavaFX 2.0 properties and bindings framework properties as the backing store for the values of the JavaFX Beans properties. We show you these strategies in the next two subsections.

Understanding the Eagerly Instantiated Properties Strategy

The eagerly instantiated properties strategy is the simplest way to implement JavaFX Beans properties. For every JavaFX Beans property you want to define in an object, you introduce a private field in the class that is of the appropriate JavaFX 2.0 properties and bindings framework property type. These private fields are instantiated at bean construction time. The getter and setter methods simply call the private field’s get() and set() methods. The property getter simply returns the private field itself.

The program in Listing 3-9 defines a JavaFX Bean with an int property i, a String property str, and a Color property color.

Listing 3-9. JavaFXBeanModelExample.java

import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.paint.Color;

public class JavaFXBeanModelExample {
    private IntegerProperty i = new SimpleIntegerProperty(this, "i", 0);
    private StringProperty str = new SimpleStringProperty(this, "str", "Hello");
    private ObjectProperty<Color> color = new SimpleObjectProperty<Color>(this, "color",images
 Color.BLACK);

    public final int getI() {
        return i.get();
    }

    public final void setI(int i) {
        this.i.set(i);
    }

    public IntegerProperty iProperty() {
        return i;
    }

    public final String getStr() {
        return str.get();
    }

    public final void setStr(String str) {
        this.str.set(str);
    }

    public StringProperty strProperty() {
        return str;
    }

    public final Color getColor() {
        return color.get();
    }

    public final void setColor(Color color) {
        this.color.set(color);
    }

    public ObjectProperty<Color> colorProperty() {
        return color;
    }
}

This is a straightforward Java class. There are only two things we want to point out in this implementation. First, the getter and setter methods are declared final by convention. Secondly, when the private fields are initialized, we called the simple properties constructors with the full context information, supplying them with this as the first parameter. In all of our previous examples in this chapter, we used null as the first parameter for the simple properties constructors because those properties are not part of a higher-level JavaFX Bean object.

The program in Listing 3-10 defines a view class that watches over an instance of the JavaFX Bean defined in Listing 3-9. It observes changes to the i, str, and color properties of the bean by hooking up change listeners that print out any changes to the console.

Listing 3-10. JavaFXBeanViewExample.java

import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.paint.Color;

public class JavaFXBeanViewExample {
    private JavaFXBeanModelExample model;

    public JavaFXBeanViewExample(JavaFXBeanModelExample model) {
        this.model = model;
        hookupChangeListeners();
    }

    private void hookupChangeListeners() {
        model.iProperty().addListener(new ChangeListener<Number>() {
            @Override
            public void changed(ObservableValue<? extends Number> observableValue, Numberimages
 oldValue, Number newValue) {
                System.out.println("Property i changed: old value = " + oldValue + ", newimages
 value = " + newValue);
            }
        });

        model.strProperty().addListener(new ChangeListener<String>() {
            @Override
            public void changed(ObservableValue<? extends String> observableValue, Stringimages
 oldValue, String newValue) {
                System.out.println("Property str changed: old value = " + oldValue + ", newimages
 value = " + newValue);
            }
        });

        model.colorProperty().addListener(new ChangeListener<Color>() {
            @Override
            public void changed(ObservableValue<? extends Color> observableValue, Colorimages
 oldValue, Color newValue) {
                System.out.println("Property color changed: old value = " + oldValue + ",images
 new value = " + newValue);
            }
        });
    }
}

The program in Listing 3-11 defines a controller that can modify a model object.

Listing 3-11. JavaFXBeanControllerExample.java

import javafx.scene.paint.Color;

public class JavaFXBeanControllerExample {
    private JavaFXBeanModelExample model;
    private JavaFXBeanViewExample view;

    public JavaFXBeanControllerExample(JavaFXBeanModelExample model, JavaFXBeanViewExampleimages
 view) {
        this.model = model;
        this.view = view;
    }

    public void incrementIPropertyOnModel() {
        model.setI(model.getI() + 1);
    }

    public void changeStrPropertyOnModel() {
        final String str = model.getStr();
        if (str.equals("Hello")) {
            model.setStr("World");
        } else {
            model.setStr("Hello");
        }
    }

    public void switchColorPropertyOnModel() {
        final Color color = model.getColor();
        if (color.equals(Color.BLACK)) {
            model.setColor(Color.WHITE);
        } else {
            model.setColor(Color.BLACK);
        }
    }
}

Notice that this is not a full-blown controller and does not do anything with its reference to the view object. The program in Listing 3-12 provides a main program that assembles and test drives the classes in Listings 3-9 to 3-11 in a typical model–view–controller pattern.

Listing 3-12. JavaFXbeanMainExample.java

public class JavaFXBeanMainExample {
    public static void main(String[] args) {
        JavaFXBeanModelExample model = new JavaFXBeanModelExample();
        JavaFXBeanViewExample view = new JavaFXBeanViewExample(model);
        JavaFXBeanControllerExample controller = new JavaFXBeanControllerExample(model, view);

        controller.incrementIPropertyOnModel();
        controller.changeStrPropertyOnModel();
        controller.switchColorPropertyOnModel();
        controller.incrementIPropertyOnModel();
        controller.changeStrPropertyOnModel();
        controller.switchColorPropertyOnModel();
    }
}

When we run the program in Listings 3-9 to 3-12, the following output is printed to the console:


Property i changed: old value = 0, new value = 1

Property str changed: old value = Hello, new value = World

Property color changed: old value = Color[red=0,green=0,blue=0,opacity=1.0], new value =
Color[red=255,green=255,blue=255,opacity=1.0]

Property i changed: old value = 1, new value = 2

Property str changed: old value = World, new value = Hello

Property color changed: old value = Color[red=255,green=255,blue=255,opacity=1.0], new value
= Color[red=0,green=0,blue=0,opacity=1.0]

Understanding the Lazily Instantiated Properties Strategy

If your JavaFX Bean has many properties, instantiating all the properties objects up front at bean creation time may be too heavy an approach. The memory for all the properties objects is truly wasted if only a few of the properties are actually used. In such situations, you can use one of several lazily instantiated properties strategies. Two typical such strategies are the half-lazy instantiation strategy and the full-lazy instantiation strategy.

In the half-lazy strategy, the property object is instantiated only if the setter is called with a value that is different from the default value, or if the property getter is called. The program in Listing 3-13 illustrates how this strategy is implemented.

Listing 3-13. JavaFXBeanModelHalfLazyExample.java

import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;

public class JavaFXBeanModelHalfLazyExample {
    private static final String DEFAULT_STR = "Hello";
    private StringProperty str;

    public final String getStr() {
        if (str != null) {
            return str.get();
        } else {
            return DEFAULT_STR;
        }
    }

    public final void setStr(String str) {
        if ((this.str != null) || !(str.equals(DEFAULT_STR))) {
            strProperty().set(str);
        }
    }

    public StringProperty strProperty() {
        if (str == null) {
            str = new SimpleStringProperty(this, "str", DEFAULT_STR);
        }
        return str;
    }
}

In this strategy, the client code can call the getter many times without the property object being instantiated. If the property object is null, the getter simply returns the default value. As soon as the setter is called with a value that is different from the default value, it will call the property getter which lazily instantiates the property object. The property object is also instantiated if the client code calls the property getter directly.

In the full-lazy strategy, the property object is instantiated only if the property getter is called. The getter and setter go through the property object only if it is already instantiated. Otherwise they go through a separate field.

The program in Listing 3-14 shows an example of a full-lazy property.

Listing 3-14. JavaFXBeanModelFullLazyExample.java

import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;

public class JavaFXBeanModelFullLazyExample {
    private static final String DEFAULT_STR = "Hello";
    private StringProperty str;
    private String _str = DEFAULT_STR;

    public final String getStr() {
        if (str != null) {
            return str.get();
        } else {
            return _str;
        }
    }

    public final void setStr(String str) {
        if (this.str != null) {
            this.str.set(str);
        } else {
            _str = str;
        }
    }

    public StringProperty strProperty() {
        if (str == null) {
            str = new SimpleStringProperty(this, "str", DEFAULT_STR);
        }
        return str;
    }
}

images  Caution The full-lazy instantiation strategy incurs the cost of an extra field in order to stave off the need for property instantiation a little longer. Similarly, both the half-lazy and the full-lazy instantiation strategies incur costs of implementation complexity and runtime performance to gain the benefit of a potentially reduced runtime memory footprint. This is a classical trade-off situation in software engineering. Which strategy you choose will depend on the circumstance of your application. Our advice is to introduce optimization only if there is a need.

Using Selection Bindings

As you have seen in the "Understanding the Bindings Utility Class" subsection of the "Creating Bindings" section, the Bindings utility class contains seven selection operators. The method signatures of these operators are:

  • select(ObservableValue<?> root, String... steps)
  • selectBoolean(ObservableValue<?> root, String... steps)
  • selectDouble(ObservableValue<?> root, String... steps)
  • selectFloat(ObservableValue<?> root, String... steps)
  • selectInteger(ObservableValue<?> root, String... steps)
  • selectLong(ObservableValue<?> root, String... steps)
  • selectString(ObservableValue<?> root, String... steps)

These selection operators allow you to create bindings that observe deeply nested JavaFX Beans properties. Suppose that you have a JavaFX bean that has a property, whose type is a JavaFX bean that has a property, whose type is a JavaFX bean that has a property, and so on. Suppose also that you are observing the root of this properties chain through an ObjectProperty. Then you can create a binding that observes the deeply nested JavaFX Beans property by calling one of the select methods whose type matches the type of the deeply nested JavaFX Beans property with the ObjectProperty as the root, and the successive JavaFX Beans property names that reach into the deeply nested JavaFX Beans property as the rest of the arguments.

In the following example, we use a few classes from the javafx.scene.effect package—Lighting and Light—to illustrate how the selection operator works. We teach you how to apply lighting to a JavaFX scene graph in a later chapter of the book. For now, our interest lies in the fact that Lighting is a JavaFX bean that has a property named light whose type is Light, and that Light is also a JavaFX bean that has a property named color whose type is Color (in javafx.scene.paint).

The program in Listing 3-15 illustrates how to observe the color of the light of the lighting.

Listing 3-15. SelectBindingExample.java

import javafx.beans.binding.Bindings;
import javafx.beans.binding.ObjectBinding;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.effect.Light;
import javafx.scene.effect.Lighting;
import javafx.scene.paint.Color;

public class SelectBindingExample {
    public static void main(String[] args) {
        ObjectProperty<Lighting> root = new SimpleObjectProperty<>();
        final ObjectBinding<Color> selectBinding = Bindings.select(root, "light", "color");
        selectBinding.addListener(new ChangeListener<Color>() {
            @Override
            public void changed(ObservableValue<? extends Color> observableValue, Colorimages
 oldValue, Color newValue) {
                System.out.println(" The color changed: old color = " +
                        oldValue + ", new color = " + newValue);
            }
        });

        System.out.println("firstLight is black.");
        Light firstLight = new Light.Point();
        firstLight.setColor(Color.BLACK);

        System.out.println("secondLight is white.");
        Light secondLight = new Light.Point();
        secondLight.setColor(Color.WHITE);

        System.out.println("firstLighting has firstLight.");
        Lighting firstLighting = new Lighting();
        firstLighting.setLight(firstLight);

        System.out.println("secondLighting has secondLight.");
        Lighting secondLighting = new Lighting();
        secondLighting.setLight(secondLight);

        System.out.println("Making root observe firstLighting.");
        root.set(firstLighting);

        System.out.println("Making root observe secondLighting.");
        root.set(secondLighting);

        System.out.println("Changing secondLighting's light to firstLight");
        secondLighting.setLight(firstLight);

        System.out.println("Changing firstLight's color to red");
        firstLight.setColor(Color.RED);
    }
}

In this example, the root is an ObjectProperty that observes Lighting objects. The binding colorBinding observes the color property of the light property of the Lighting object that is the value of root. We then created some Light and Lighting objects and changed their configuration in various ways.

When we run the program in Listing 3-15, the following output is printed to the console:


firstLight is black.

secondLight is white.

firstLighting has firstLight.

secondLighting has secondLight.

Making root observe firstLighting.

        The color changed:

                old color = null,

                new color = Color[red=0,green=0,blue=0,opacity=1.0]

Making root observe secondLighting.

        The color changed:

                old color = Color[red=0,green=0,blue=0,opacity=1.0],

                new color = Color[red=255,green=255,blue=255,opacity=1.0]

Changing secondLighting's light to firstLight

        The color changed:

                old color = Color[red=255,green=255,blue=255,opacity=1.0],

                new color = Color[red=0,green=0,blue=0,opacity=1.0]


Changing firstLight's color to red

        The color changed:

                old color = Color[red=0,green=0,blue=0,opacity=1.0],

                new color = Color[red=255,green=0,blue=0,opacity=1.0]

As expected, a change event is fired for every change in the configuration of the object being observed by root; and the value of colorBinding always reflects the color of the light of the current Lighting object in root.

images  Caution The JavaFX 2.0 properties and bindings framework does not issue any warnings if the supplied property names do not match any property names in a JavaFX bean. It will simply have the default value for the type: null for object type, zero for numeric types, false for boolean type, and the empty string for string type.

Summary

In this chapter, you learned the fundamentals of the JavaFX 2.0 properties and bindings framework and the JavaFX Beans specification. You now understand the following important principles.

  • JavaFX 2.0 properties and bindings framework properties and bindings are the fundamental workhorses of the framework.
  • They conform to the key interfaces of the framework.
  • They fire two kinds of events: invalidation event and change event.
  • All properties and bindings provided by the JavaFX 2.0 properties and bindings framework recalculate their values lazily, only when a value is requested. To force them into eager re-evaluation, attach a ChangeListener to them.
  • New bindings are created out of existing properties and bindings in one of three ways: using the factory methods of the Bindings utility class, using the fluent interface API, or directly extending the IntegerBinding series of abstract classes.
  • The JavaFX Beans specification uses three methods to define a property: the getter, the setter, and the property getter.
  • JavaFX Beans properties can be implemented through the eager, half-lazy, and full-lazy strategies.

Resources

Here are some useful resources for understanding properties and bindings:

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.224.51.55