Skip to main content

Hacking JavaFX Binding

June 2, 2009

{cs.r.title}







The JavaFX Script bind operator connects or links variables. This feature is backed by the underlying JavaFX runtime through a small framework based upon so-called locations. This article takes a closer look at its internal structure and demonstrates how JavaFX Binding can be "misused" as a binding framework for Swing.

Introduction

Data binding connects objects and keeps them in sync. While Swing developers depend on additional class libraries, binding is fully integrated into the JavaFX Script programming language. A closer look at its runtime libraries reveals a full-fledged binding framework, whose underlying concepts are quite similar to JGoodies Binding and Beans Binding, which I have compared in my previous article "Binding Beans." JSR-295 (Beans Binding) probably won't be part of Java 7. So developers looking for alternatives might ask themselves if the JavaFX binding framework can be used by plain Java programs, too.

JavaFX is built on top of an ordinary Java SE runtime. To launch the demos in this article, you need to download the appropriate JavaFX SDK for your operating system (at the time of writing, only Windows and Mac OS X are officially supported). Please consult the Resources section for details. The installation takes just a few steps. On Windows machines, the default installation location is C:\Program Files\JavaFX\javafx-sdk1.1. Mac OS X users should look at /Library/Frameworks/JavaFX.framework/Versions/1.1.

JavaFX SDK base directory

Figure 1. JavaFX SDK base directory

Figure 1 shows the JavaFX SDK base directory. The bin directory contains executables to compile and run JavaFX Script programs. We don't use them. The JavaFX (Script) documentation

is in docs. The file src.zip includes the sources of parts of the JavaFX runtime. If you unpack it, you may notice files ending in .stg and .st. I will turn to them shortly. Finally, lib and its subdirectories contain library .jars. The demos in this article depend on some of them.

Locations

lib/shared/javafxrt.jar contains the package com.sun.javafx.runtime.location. Its classes and interfaces form the basic building blocks of JavaFX Binding. For example, the Location interface represents a value, which may be mutable or immutable, valid or invalid, and null or non-null. Such states can be queried by corresponding getters; for example, isMutable(). If the value of a Location is invalid, it will be updated either when the update() method is called, or when the value is retrieved.

The type of a Location is determined by subinterfaces; for example, IntLocation. If you search for IntLocation.java in src.zip, you should not see it. That is because its source is generated from the two files XxxLocation.st and XxxTemplate.stg. Each subinterface adds getAsXYZ() and setAsXYZ() for the type XYZ it represents. There are also DoubleLocation, FloatLocation, ShortLocation, CharLocation, LongLocation, BooleanLocation, ByteLocation, and ObjectLocation.

Other objects may express a dependency on a Location. Also, change listeners can be registered to be notified whenever the value associated with the location changes. Finally, Locations may be lazy: though change listeners will be notified when the value is invalidated, the new value will not be recomputed until it is needed. So far I have only talked about interfaces. There are, of course, ready-to-use implementations of Locations for the types I mentioned above.

public static void main(String[] args) {

final IntVariable i1 = IntVariable.make(42);

i1.addChangeListener(new ChangeListener() {
@Override

public boolean onChange() {

System.out.println("onChange(): " + i1.get());

return false;

}
});

System.out.println(i1.get() +

", isMutable(): " + i1.isMutable());
IntLocation i2 = IntConstant.make(24);

System.out.println(i2.get() +

", isMutable(): " + i2.isMutable());
i1.set(i2.get());

}

To compile and run LocationDemo1, please append lib/shared/javafxrt.jar to your class path. This demo introduces the classes IntVariable and IntConstant. Both implement the IntLocation interface, and hence are Locations. Instances are created using the static method make(). Current values are queried using get(). As you can see in Figure 2, setting a value after initialization triggers a notification. It is handled by children of the abstract class ChangeListener.

output of LocationDemo1

Figure 2. Output of LocationDemo1

Its onChange() method is called when the contents of a location may have changed. The method returns a Boolean value that indicates whether the listener is still valid. Returning false will cause the listener to be removed from the listener list. The Javadoc suggests that listeners doing their own weak reference management should return false when the relevant weak references have been reported as cleared.

Just like the ValueModel of JGoodies Binding and the Property of Beans Binding, Locations provide a means for reading and writing typed values. They can also inform registered listeners about value changes. Finally, as you will see in the following section, they can be used to establish bindings.

Establishing Bindings

As with Beans Binding and JGoodies Binding, the JavaFX runtime contains a helper class for establishing bindings: com.sun.javafx.runtime.location.Bindings. It is used to set up bijective relationships between two Locations. This means that if one value is updated, its counterpart will be updated, too. After the Locations have been instantiated, they are passed to bijectiveBind().

public class BindingDemo1 {
private static IntLocation i1, i2;
public static void main(String[] args) {

i1 = IntVariable.make();

i2 = IntVariable.make();

Bindings.bijectiveBind(i2, i1);

showValues();
i1.setAsInt(10);

showValues();
i2.setAsInt(100);

showValues();
}
private static void showValues() {

System.out.println("i1: " + i1.get());

System.out.println("i2: " + i2.get());

}
}
bijectiveBind(i2, i1) establishes a two-way dependency between i1 and i2. If either one is updated, for example by calling setAsInt(), the value of the other changes, too. To achieve this, the implementation attaches two listeners that share state among the involved Locations.

The convenience method makeBijectiveBind() creates a new Location and binds it to an existing one. This is done as follows:

i1 = IntVariable.make();

i2 = Bindings.makeBijectiveBind(i1);
BindingDemo2 shows how to use it. It is included in /today/2009/06/02/sources.zip. Please consult the Resources section for details. Figure 3 shows the output it generates.

Output of BindingDemo2

Figure 3. Output of BindingDemo2

Please note that only compatible types can be bound using bijectiveBind(). The following lines of code are taken from BindingDemo3.java (which is included in sources.zip). They may look all right at first sight. Still, they throw a ClassCastException during execution. What is happening here?

ObjectLocation loc1 = IntVariable.make();

ObjectLocation loc2 = BooleanVariable.make();

Bindings.bijectiveBind(loc1, loc2);

During the creation of the binding, the result of loc2.get() is passed to the set() method of loc1. This does not work for Boolean and Integer. To avoid such issues, just properly parameterize the generic type ObjectLocation. So far, we have seen how two variables can be synchronized. The following section takes a look at how Swing components can be bound.

Binding Swing Components

Almost every JavaFX Script tutorial starts with a small program that shows a window, a button, and a label. JavaFX uses Swing to build and display these components. Hence, we can fairly assume that Swing is somehow integrated into the JavaFX runtime. As you will see shortly, this applies to binding as well.

The file lib/desktop/javafx-swing.jar contains the package javafx.ext.swing. Its classes wrap the most common Swing components. If you examine them, you will notice that they expose fields starting with $. Their type is ObjectVariable, which implements ObjectLocation.

SwingLabel in the Members view of Eclipse

Figure 4. SwingLabel in the Members view of Eclipse

Taking into account that this interface belongs to the package com.sun.javafx.runtime.location, it is safe to assume that such Locations can be bound to other variables. The following program shows you how to do that. To compile and run the demo, please append lib/shared/javafxrt.jar, lib/desktop/javafx-swing.jar, lib/desktop/Scenario.jar, and lib/desktop/javafxgui.jar to your class path.

public class SwingDemo1 {
public static void main(String[] args) {

JFrame f = new JFrame();

f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

JPanel p = new JPanel(new BorderLayout());

f.setContentPane(p);
SwingLabel label = new SwingLabel();

ObjectLocation text =

Bindings.makeBijectiveBind(label.$text);
p.add(label.getJComponent(), BorderLayout.CENTER);

f.pack();

f.setVisible(true);



text.set("Hello, JavaFX!");

}

}

Besides the usual Swing code for creating and showing a window, three important things are done:

  1. A SwingLabel is instantiated and assigned to label.
  2. A Location is assigned to test and bound to $text of label.
  3. The label is added to the component hierarchy.

Please note that you cannot directly add the SwingLabel to a container. Instead, its getJComponent() method is used to obtain an instance of JComponent.

Though this simple example has shown how a Swing component can be bound to a variable, it probably fails to illustrate the benefits of using JavaFX binding. In my previous article, "Binding Beans," I demonstrated how to implement a simple volume control using JGoodies Binding and Beans Binding. The following section shows you how to build such a program using JavaFX binding.

The VolumeControl Example

The volume control is based on a simple application-specific POJO called Volume. It has two fields: volume and mute. As you can see in Figure 5, they are manipulated by a checkbox and a slider. A label shows the current value of volume. Additionally, mute controls whether the volume can be adjusted.

The volume control example

Figure 5. The volume control example

The relationship between the involved Swing components and the fields of the POJO is as follows:

  • The checkbox sets mute.
  • The slider sets volume.
  • mute selects or deselects the checkbox.
  • volume sets the position of the slider.
  • mute enables or disables the slider.
  • volume sets the text of the label.

The complete source is included in sources.zip. Please consult the Resources section for details. Its structure closely resembles the versions in my previous article, so it is easy to compare the different versions. To compile and run VolumeControl, please append lib/shared/javafxrt.jar, lib/desktop/javafx-swing.jar, lib/desktop/Scenario.jar, and lib/desktop/javafxgui.jar to your class path.

First, all relevant components are initialized. This takes place in initComponents(). For example, a vertical slider is created and set up as follows:

sliderVolume = new SwingSlider();

sliderVolume.$vertical.set(true);

Bindings are established in initEventHandling(). For example, the checkbox is linked to mute with this command: Bindings.bijectiveBind(checkboxMute.$selected, volume.mute);. Disabling the slider when the checkbox is selected is achieved by adding a listener to mute.

volume.mute.addChangeListener(new ChangeListener() {

@Override

public boolean onChange() {

sliderVolume.$enabled.set(! volume.mute.get());

return true;

}

});
onChange() is called each time the value of volume.mute changes. If it turns true, the slider is disabled. If it turns false, it is enabled again. The state is set with sliderVolume.$enabled.set().

A similar approach is needed to set the text of the label. The value of the slider is represented as an Integer that, as we have seen earlier, cannot be bound to Strings. The conversion is done as follows:

public boolean onChange() {

labelInfo.$text.set(volume.volume.get().toString());

return true;

}

The result of get() is turned into a String using toString() and then passed to labelInfo.$text.set().

Conclusion

It is surprising to see how easily JavaFX binding can be used in Swing applications. Although the binding framework surely has not been designed for this, it does quite a decent job. Still, this article deliberately ignores several question or issues:

  • Does Sun allow the usage and possibly the redistribution of parts of the JavaFX runtime for non-JavaFX applications?
  • How safe is the use of internal classes? The packages described here have not been designed as public APIs.
  • Why bother anyway, as there are other, matured binding frameworks for Swing developers?

Even if JavaFX binding can or should not be used in production environments, digging into its internals is both fun and inspiring. Keep hacking.

Resources


width="1" height="1" border="0" alt=" " />
Thomas Kunneth works as a software architect at the German authorities, specializing in Java-based rich clients.
Related Topics >> Programming   |   Swing   |