Creating Bindings (Properties and Bindings) (JavaFX 2)

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

tmpA-127_thumb


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().

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

tmpA-128_thumb

Listing 3-5. TriangleAreaExample.java

TriangleAreaExample.java

 

 

 

TriangleAreaExample.java

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:

tmpA-131_thumb

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

tmpA-132_thumb

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

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.

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

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:

tmpA-133

• Common for all numeric properties and bindings

• Common for all numeric properties and bindings

 

 

• Common for all numeric properties and bindings

 

 

tmpA-136

• ForIntegerProperty and IntegerBinding

• ForIntegerProperty and IntegerBinding

• For LongProperty and LongBinding

• For LongProperty and LongBinding

 

 

• For LongProperty and LongBinding

• For FloatProperty and FloatBinding

• For FloatProperty and FloatBinding

 

 

 

 

• For FloatProperty and FloatBinding

• For DoubleProperty and DoubleBinding

• For DoubleProperty and DoubleBinding

 

 

 

• For DoubleProperty and DoubleBinding

• For StringProperty and StringBinding

• For StringProperty and StringBinding

• For ObjectProperty and ObjectBinding

• For ObjectProperty and ObjectBinding

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

TriangleAreaFluentExample.java

 

 

 

 

TriangleAreaFluentExample.java

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:

tmpA-148_thumb

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:

tmpA-149_thumb

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:

tmpA-150_thumb

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.

• ForWhen.NumberConditionBuilder

• ForWhen.NumberConditionBuilder

• For When.BooleanConditionBuilder

• For When.BooleanConditionBuilder

• ForWhen.StringConditionBuilder

• ForWhen.StringConditionBuilder

• For When.ObjectConditionBuilder

• For When.ObjectConditionBuilder

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

tmpA-155_thumb

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.

tmpA-156_thumb

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

tmpA-157_thumb

where s is the semiperimeter:

tmpA-158_thumb

Listing3-7. HeronsFormulaExample.java

HeronsFormulaExample.java

 

 

HeronsFormulaExample.java

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.

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

tmpA-161_thumb

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

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

Listing3-8. HeronsFormulaDirectExtensionExample.java

HeronsFormulaDirectExtensionExample.java

 

 

 

 

HeronsFormulaDirectExtensionExample.java

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.

Next post:

Previous post: