Understanding Key Interfaces and Concepts (Properties and Bindings) (JavaFX 2)

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.

Key interfaces of the JavaFX 2.0 properties and bindings framework

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

■ 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.


■ 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 topic. 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 topic, when one of its dependencies fires an invalidation event.

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.

■ 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:

tmpA-110_thumb

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.

■ 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.

Understanding the WritableValue Interface

This may be the simplest subsection in the entire topic, 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:

tmpA-111_thumb

the output would have contained the property name:

tmpA-112_thumb

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:

tmpA-113_thumb

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

BidirectionalBindingExample.java

 

 

 

BidirectionalBindingExample.java

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:

tmpA-116_thumb

■ 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 topic. 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

RectangleAreaExample.java

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:

tmpA-118_thumb

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

■ 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.

Next post:

Previous post: