Skip to main content

Binding Beans

May 27, 2008

{cs.r.title}



When talking about data binding in Java, people usually think of mapping the contents of XML files to Java objects, as such frameworks have become very popular through the years. However, JAXB or Castor just implement a more general concept. Data binding means wiring attributes or properties of different data structures and optionally keeping them in sync. These can also be JavaBeans.

This article takes a look at two frameworks for binding beans. JGoodies Binding has been around for several years. It is heavily inspired by the Presentation Model pattern by Martin Fowler. The Java Specification Request 295: Beans Binding and its reference implementation gained widespread recognition in 2007. The JSR is considered for inclusion in Java 7. The article uses both libraries to implement separate versions of a program called VolumeControl. You can find these examples in the packages com.thomaskuenneth.articles.binding.beansbinding and com.thomaskuenneth.articles.binding.jgoodiesbinding. Please consult the Resources section for details. The archive also contains a classic implementation, which can be found in com.thomaskuenneth.articles.binding.classic.

Installing the Frameworks

The JGoodies Binding class library is released under the terms of the BSD open source license. It can be downloaded from the project home page here at java.net. The binding-2_0_2.zip archive contains the sources, a ready-to-use .jar, the documentation, and a tutorial. To get started, unzip the archive and create an empty Java project using your favorite IDE. Add binding-2.0.2.jar and lib\forms-1.2.0b2.jar to the classpath and copy the contents of src\tutorial into the sources folder of your newly created project. You should now be able to launch the demos in com.jgoodies.binding.tutorial and its subpackages. Figure 1 shows the AlbumManagerExample program.

AlbumManagerExample
Figure 1. AlbumManagerExample

You can build the library on your own, too. The source files are located in src\core and src\extras. The latter contains experimental classes that are not present in the the precompiled archive. Please be sure to put lib\forms-1.2.0b2.jar on the classpath.

The second framework we are looking at, the reference implementation of JSR 295: Beans Binding, is available for download from its project home page. You can choose from a precompiled archive, the sources, and its Javadoc documentation. I suggest getting the source distribution (beansbinding-1.2.1-src.zip) and compiling it on your own. Just create an empty Java project in your favorite IDE and copy the contents of the src folder into the sources directory of your project.

By now you should have a project for each of the frameworks. As a final step, please create a third project (Examples) and put the contents of sources.zip there (please consult the Resources section for details). Make sure to reference the other projects.

JavaBeans Revisited

The JavaBeans specification defines a model for reusable software components. Among many other things, it specifies how components can expose their states and how other objects interested in state changes will get informed when they occur. The underlying idea was to build an application by visually arranging and modifying those beans. Consequently, Swing components, being one of the building blocks of such a program, try to behave as proper beans.

As we know today, the creation of sophisticated design tools took much longer than originally anticipated. As a result, both the wiring of those of components as well as the creation of application-specific beans had to be done mostly by hand.

Beans expose their state by means of Getters and Setters. The state is a set of properties. For example, the property backgroundColor can be accessed with getBackgroundColor() and setBackgroundColor(). Properties are said to be bound if they can inform interested objects about state changes. Writing such a bean requires:

  • The implementation of all Getters and Setters.
  • Allowing others to be notified of state changes.
  • Firing appropriate events when they occur.

Establishing a link between beans requires:

  • Implementing appropriate listeners.
  • Registering them with the bean.

Although the JavaBeans architecture delivers the technical infrastructure for inter-bean communication, there is no immediate built-in support for synchronizing properties. Binding frameworks fill that void.

The following sections show how to bind a simple application-specific bean called com.thomaskuenneth.articles.binding.Volume to three Swing components using either JGoodies Binding or Beans Binding. The sources.zip archive also contains a traditional implementation, which makes no use of additional frameworks. The three versions of the main program, VolumeControl, reside in different subpackages, so you can compare them easily.

Representing Properties

Volume has two bound properties: volume and mute. As you can see in Figure 2, 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.

VolumeControl
Figure 2. VolumeControl

The relationship between the involved beans 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.

com.thomaskuenneth.articles.binding.Volume shows the standard approach of defining bean properties. Bound properties usually fire events through the firePropertyChange() method of PropertyChangeSupport instances. JGoodies Binding has a convenience class, com.jgoodies.binding.beans.Model, which simplifies the task of implementing beans. It already contains much of that boilerplate code.

Bean properties used to be bound by registering PropertyChangeListeners and hard-coding their bindings into propertyChange() methods. Both JGoodies Binding and Beans Binding ease that pain by establishing inter-property relationships through simple method calls. Internally, they utilize the JavaBeans infrastructure, but expose bean properties by means of accessor objects.

Beans Binding introduces the abstract class Property. It defines a uniform way to access the value of a property. An implementation, BeanProperty, is used to address JavaBeans properties of source objects. For example, Property propertyVolume = BeanProperty.create("volume"); creates an object that can be used to get or set the volume. This is done as follows:


System.out.println(propertyVolume.getValue(volume));
propertyVolume.setValue(volume, 99);
System.out.println(propertyVolume.getValue(volume));

Usually, a Property instance can be used to get and set values of arbitrary beans, which are passed to the corresponding methods. However, there may be subclasses that by themselves store a property value. In this case, they probably ignore the source object.

JGoodies Binding is based upon the com.jgoodies.binding.value.ValueModel interface. Such models provide a generic access to a single value, which can be accessed using the getValue() and setValue() methods. The framework offers several implementations of the interface; for example, ValueHolder and ComponentValueModel. The latter provides bound properties for the JComponent states enabled and visible, as well as the JTextComponent state editable. You will see shortly how to make use of that.

ValueHolder, on the other hand, is a simple ValueModel implementation that holds a generic value. If the value changes, a PropertyChangeEvent is fired. Unlike BeanProperty in Beans Binding, it denotes no path to a bean property. You can do this using com.jgoodies.binding.beans.PropertyAdapter. This class converts a single bean property into the generic ValueModel interface. BeanAdapter can do that with multiple properties of the same bean.

Property (Beans Binding) and ValueModel (JGoodies Binding) provide means for reading and writing values. They can also inform registered listeners about value changes. Finally, as you will see in the following section, both are used to establish bindings.

Establishing Bindings

In Beans Binding, the abstract class org.jdesktop.beansbinding.Binding represents the concept of a binding between two properties. The class contains methods for syncing them upon explicit requests. Automatic updates, according to some specified strategy, are implemented by subclasses. Bindings can be obtained from the factory class org.jdesktop.beansbinding.Bindings.

For example, AutoBinding syncs its source and target objects according to one of the following update strategies:

  • AutoBinding.UpdateStrategy.READ_ONCE
  • AutoBinding.UpdateStrategy.READ
  • AutoBinding.UpdateStrategy.READ_WRITE

Connecting the volume bean property to a slider is achieved by:


Property propertyVolume = BeanProperty.create("volume");
Property propertySliderValue = BeanProperty.create("value");
Binding bindingVolumeSlider = Bindings.createAutoBinding(
    AutoBinding.UpdateStrategy.READ_WRITE, 
        volume, propertyVolume,
    sliderVolume, propertySliderValue);
bindingVolumeSlider.bind();

The bean property volume is an instance of com.thomaskuenneth.articles.binding.Volume, whereas sliderVolume is a JSlider. Moving the slider updates the bean, and invoking setVolume() changes the slider location, because the update strategy is set to UpdateStrategy.READ_WRITE. VolumeControl contains a label that prints the current value of the bean property volume. If the update strategy of that binding was set to READ_WRITE as well, invoking labelInfo.setText() would change the property. This is not intended, so the binding is created as follows:


Property propertyVolume = BeanProperty.create("volume");
Property propertyLabel = BeanProperty.create("text");
Binding bindingVolumeLabel = 
    Bindings.createAutoBinding(AutoBinding.UpdateStrategy.READ, 
        volume, propertyVolume,
                labelInfo, propertyLabel);

This way the target (the label) is kept in sync with the source, but not vice versa.

JGoodies Binding works in a similar fashion. It offers two helper classes for establishing a binding. com.jgoodies.binding.adapter.Bindings binds components that have been created before. It wraps ValueModels with the adapters from the package com.jgoodies.binding.adapter. This approach is showcased in com.thomaskuenneth.articles.binding.jgoodiesbinding.VolmeControl. Alternatively, you can use com.jgoodies.binding.adapter.BasicComponentFactory. This class creates Swing components that are then bound using the Bindings class. The sample VolumeControl2 shows you how to do that.

To bind an already-created checkbox to the bean property mute you have to:


PropertyAdapter<Volume> adapterMute = 
    new PropertyAdapter<Volume>(volume, "mute", true);
Bindings.bind(checkboxMute, adapterMute);

Invoking checkboxMute.setSelected() will modify the mute bean property and vice versa. Binding volume to a label is almost as easy. The following example also shows you how to convert between different formats. You will see shortly why this may be necessary.


PropertyAdapter<Volume> adapterVolume = 
    new PropertyAdapter<Volume>(volume, "volume", true);
ValueModel labelModel = 
    ConverterFactory.createStringConverter(adapterVolume,
                     NumberFormat.getIntegerInstance());
Bindings.bind(labelInfo, labelModel);

The label is bound to a ValueModel, which has been derived from the original PropertyAdapter by invoking ConverterFactory.createStringConverter(). It converts Integers to Strings. This is necessary to avoid PropertyAccessExceptions, which would occur because the data types of the source and target beans are different.

Beans Binding knows converters, too. Once a binding is created, you can invoke setConverter().


Property propertyMute = BeanProperty.create("mute");
Property propertySliderEnabled = BeanProperty.create("enabled");
Binding bindingMuteSlider = Bindings.createAutoBinding(
    AutoBinding.UpdateStrategy.READ, 
    volume, propertyMute,
    sliderVolume, propertySliderEnabled);
bindingMuteSlider.setConverter(new Converter() {
    @Override
    public Object convertForward(Object value) {
        return !((Boolean) value);
    }

    @Override
    public Object convertReverse(Object value) {
        return convertForward(value);
    }
});
bindingMuteSlider.bind();

In this example, the bean property mute is bound to the enabled state of a slider. As the slider should be movable only if mute is false, its state needs to be negated before it is passed to the component. Converter classes extend org.jdesktop.beansbinding.Converter and override its methods convertForward() and convertReverse().

To achieve the same using JGoodies Binding, you can use com.jgoodies.binding.beans.PropertyConnector to keep two bean properties in sync. The framework also offers a converter that can negate Booleans. Here is how you can wire things up:


PropertyAdapter<Volume> adapterMute = 
    new PropertyAdapter<Volume>(volume, "mute", true);
ValueModel negator = 
    ConverterFactory.createBooleanNegator(adapterMute);
PropertyConnector connector = 
    PropertyConnector.connect(negator, 
                              "value", 
                              sliderVolume, "enabled");

The idea is to connect the slider to a converter, which in turn gets its data from a PropertyAdapter.

Structuring Applications

All versions of VolumeControl have a very similar structure. In particular, the JGoodies Binding and Beans Binding implementations resemble each other closely, besides, of course, the actual binding activities in initEventHandling(). Still, they intentionally share the same weakness: enabling or disabling the slider is woven into the binding process.

The Presentation Model pattern by Martin Fowler transfers the state and behavior of a view to a model class, which communicates both with the domain layer and the view. JGoodies Binding nicely incorporates this pattern. It provides an implementation, com.jgoodies.binding.PresentationModel, that can be used either directly or as a subclass (as in com.thomaskuenneth.articles.binding.jgoodiesbinding.VolumeControlPresentationModel). My implementation contains the code for enabling or disabling the slider. It utilizes ComponentValueModel, which adds several component states to ValueModels. This is done as follows:


getComponentModel(Volume.PROPERTY_VOLUME).setEnabled(
    !getModel(Volume.PROPERTY_MUTE).booleanValue());

Conclusion

Both JGoodies Binding and Beans Binding are powerful frameworks that significantly ease the development of Swing applications. The incorporation of the Presentation Model pattern helps structuring a program, making it more readable and maintainable. Having been in the market for quite a while now, JGoodies Binding has become very mature. Still, Beans Binding makes binding beans a breeze, too. In the long run, it might become the framework of choice, especially if it is included in a future Java version and an application must rely exclusively on core libraries.

Resources

Thomas K&#252;nneth works as a software architect at the German authorities, specializing in Java-based rich clients.
Related Topics >> GUI   |   Swing   |