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 "http://jcp.org/en/jsr/detail?id=295">Java Specification Request
295: Beans Binding
and its "https://beansbinding.dev.java.net/">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 "https://binding.dev.java.net/servlets/ProjectDocumentList">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.

<br "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 "https://beansbinding.dev.java.net/servlets/ProjectDocumentList">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 "http://java.sun.com/javase/technologies/desktop/javabeans/docs/spec.html">
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&lt;Volume&gt; adapterMute = 
    new PropertyAdapter&lt;Volume&gt;(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&lt;Volume&gt; adapterVolume = 
    new PropertyAdapter&lt;Volume&gt;(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&lt;Volume&gt; adapterMute = 
    new PropertyAdapter&lt;Volume&gt;(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 "http://martinfowler.com/eaaDev/PresentationModel.html">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


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 >> GUI   |   Swing   |