Introduction to JGoodies Binding
By Rob Smith, OCI Senior Software Engineer
June 2005
Introduction
JGoodies Binding is an open-source framework for binding Java Beans to UI components. The JGoodies Binding API:
- Simplifies binding Java Bean properties to UI components
- Reduces the lines of code needed to build Java UIs
- Encourages separation of domain and presentation layers
Installation
This article uses version 1.0 of the JGoodies Binding API. It is available for download at the JGoodies Binding home page. Just follow the links to locate version 1.0, then download the zip file.
Simply expand the zip file and add binding-1.0.jar to your classpath. It requires JDK 1.4 or later and the tutorials also require JGoodies Forms.
The Problem
Binding values to and from your domain model to UI components can be tedious. Let's look at a typical Swing application that binds properties from a simple Java bean to UI components.
- public class CustomerBean extends Model {
- public static final String NAME_PROPERTY = "name";
- public static final String PREFERRED_PROPERTY = "preferred";
-
- private String name = "";
- private boolean preferred;
-
- public String getName() {
- return name;
- }
-
- public void setName(String newName) {
- String oldValue = name;
- name = newName;
- firePropertyChange(NAME_PROPERTY, oldValue, newName);
- }
-
- public boolean isPreferred() {
- return preferred;
- }
-
- public void setPreferred(boolean isPreferred) {
- boolean oldValue = preferred;
- preferred = isPreferred;
- firePropertyChange(PREFERRED_PROPERTY, oldValue, isPreferred);
- }
-
- }
The CustomerBean
class extends the Model
class provided by the Binding API. The Model
class simplifies adding change support for bound and constrained Bean properties without having to duplicate the addPropertyChangeListener
, removePropertyChangeListener
and firePropertyChange
methods in all of our Bean classes. The drawback of using the Model
class is the single inheritance limitation.
In order for the Binding framework to be able to update UI components when domain object properties change, the CustomerBean
properties must be bound properties. Changes to bound properties cause a PropertyChangeEvent
to be fired when the property value changes.
The CustomerBean
class provides constants for the name
and preferred
properties. This is a good convention to follow to avoid using "magic" strings to refer to the property names of the CustomerBean
Bean throughout our code.
- public class TypicalGUI extends JPanel
- implements ActionListener, ItemListener, PropertyChangeListener {
- ....
-
- public TypicalGUI() {
- preferredValueCB.setEnabled(false);
-
- nameFld.addActionListener(this);
- preferredCB.addItemListener(this);
- myCustomerBean.addPropertyChangeListener(this);
-
- // set components initial values
- modelToView();
- }
-
- public void actionPerformed(ActionEvent e) {
- viewToModel();
- }
-
- public void itemStateChanged(ItemEvent e) {
- viewToModel();
- }
-
- public void propertyChange(PropertyChangeEvent evt) {
- modelToView();
- }
-
- private void modelToView() {
- nameFld.setText(myCustomerBean.getName());
- preferredCB.setSelected(myCustomerBean.isPreferred());
- }
-
- private void viewToModel() {
- myCustomerBean.setName(nameFld.getText());
- myCustomerBean.setPreferred(preferredCB.isSelected());
- }
-
- ....
- }
In this example the name
and preferred
properties of the CustomerBean
are bound to a JTextField
and JCheckBox
. In order to update the CustomerBean
with UI component state changes, component specific event listeners were added to each component. In order to update the components with state changes from the CustomerBean
, a PropertyChangeListener
was added to the CustomerBean
object.
Here's a sequence diagram of what occurs when the JTextField
's contents are modified.
This approach to writing applications is easy to implement and understand but it leads to code bloat and it is error prone to implement the same boilerplate code through out an application.
JGoodies Binding
The Binding API is centered around the ValueModel
interface.
- public interface ValueModel {
- Object getValue();
-
- void setValue(Object o);
-
- void addValueChangeListener(PropertyChangeListener l);
-
- void removeValueChangeListener(PropertyChangeListener l);
- }
The ValueModel
interface abstracts getting and setting an individual value, and provides the ability to observe changes to the value with PropertyChangeListener
s. There are three "glue code" classes we can use to bind object properties to UI components:
PropertyAdapter
BeanAdapter
PresentationModel
PropertyAdapter
The PropertyAdapter
converts a single Java Bean property into a ValueModel
implementation. Here's an example using the PropertyAdapter
class to bind properties from the CustomerBean
class to UI components.
- public class PropertyAdapterExample extends JPanel {
- ....
-
- private void createComponents() {
- myCustomerBean = new CustomerBean();
- ValueModel stringValueModel = new PropertyAdapter(
- myCustomerBean, CustomerBean.NAME_PROPERTY, true);
- ValueModel boolValueModel = new PropertyAdapter(
- myCustomerBean, CustomerBean.PREFERRED_PROPERTY, true);
-
- nameFld = BasicComponentFactory.createTextField(stringValueModel);
- preferredCB = BasicComponentFactory.createCheckBox(boolValueModel,
- "Preferred");
- }
- ...
- }
The example above creates PropertyAdapter
s that bind the CustomerBean
name
and preferred
properties to a JTextField
and JCheckBox
, respectively. The property adaptor observes changes made in the bound UI component and changes made to the bean property (because the setter methods in the CustomerBean fire PropertyChangeEvent
s). If a user types a new value into the text field, the name
property value changes. If the programmer sets the value of the name
property, the view updates.
Here's a sequence diagram of what occurs when the JTextField
's contents are modified.
The BasicComponentFactory
is a convenience class provided by the framework to create components bound to a property value. It creates and returns standard Swing components. I will discuss the BasicComponentFactory
class in more detail later in the article and also show how to manually bind properties to UI components.
The boolean
parameter passed to the PropertyAdapter
constructor instructs it to register itself as a PropertyChangeListener
for the property to which it is bound. The PropertyAdapter
class utilizes standard JavaBean introspection to invoke the bean's getter and setter methods for the property specified in the constructor. It has overloaded constructors for specifying getter and setter method names for properties that do not adhere to JavaBean conventions.
This example does not contain any code to explicitly bind the bean properties to the UI components. The framework handles the binding, making our code easier to read and maintain. Using the Binding framework makes debugging somewhat more difficult. As evident by the sequence diagram above, it is much harder to visualize what is happening in the code, due to all of the PropertyChangeEvent
s being fired back and forth. The benefits of using the Binding framework such as code reduction and easier maintenance, more than make up for this one drawback.
BeanAdapter
Creating PropertyAdapter
s for each bound property is a lot work. The BeanAdapter
class provides ValueModel
adapters for multiple Java Bean properties. Here's the previous example modified to use the BeanAdapter
class.
- public class BeanAdapterExample extends JPanel {
- ....
-
- private void createComponents() {
- myCustomerBean = new CustomerBean();
-
- BeanAdapter beanAdapter = new BeanAdapter(myCustomerBean, true);
- ValueModel stringValueModel = beanAdapter.getValueModel(
- CustomerBean.NAME_PROPERTY);
- ValueModel boolValueModel = beanAdapter.getValueModel(
- CustomerBean.PREFERRED_PROPERTY);
-
- nameFld = BasicComponentFactory.createTextField(stringValueModel);
- preferredCB = BasicComponentFactory.createCheckBox(boolValueModel,
- "Preferred");
- }
- ....
- }
PresentationModel
The PresentationModel
provides binding functionality in the same manner as the BeanAdapter
class but it also can be used to implement the Presentation Model pattern. The Presentation Model is described in the upcoming addition to Martin Fowler's "Patterns of Enterprise Application Architecture". Fowler's web site states that the purpose of the Presentation Model pattern is to "Represent the state and behavior of the presentation independently of the GUI controls used in the interface. Presentation Model pulls the state and behavior of the view out into a model class that is part of the presentation. The Presentation Model coordinates with the domain layer and provides an interface to the view that minimizes decision-making in the view. The view either stores all its state in the Presentation Model or synchronizes its state with Presentation Model frequently".
Discussion of the Presentation Model pattern itself is enough to take up an entire article. This article will focus on how the PresentationModel
class provides support for buffering changes to its underlying bean. The following example shows how the PresentationModel
can be used to add buffering support to our previous example.
- public class PresentationModelExample extends JPanel {
- ....
-
- private void createComponents() {
- MyPresentationModel presentationModel =
- new MyPresentationModel(new CustomerBean());
-
- nameFld = BasicComponentFactory.createTextField(
- presentationModel.getBufferedModel(
- CustomerBean.NAME_PROPERTY));
- preferredCB = BasicComponentFactory.createCheckBox(
- presentationModel.getBufferedModel(
- CustomerBean.PREFERRED_PROPERTY), "Preferred");
-
- applyBtn = new JButton(presentationModel.getApplyAction());
- resetBtn = new JButton(presentationModel.getResetAction());
- }
-
- ...
- }
-
- public class MyPresentationModel extends PresentationModel {
- ....
-
- public MyPresentationModel(CustomerBean customerBean) {
- super(customerBean);
- applyAction = new ApplyAction();
- resetAction = new ResetAction();
- }
-
- public Action getApplyAction() {
- return applyAction;
- }
-
- public Action getResetAction() {
- return resetAction;
- }
-
- private class ApplyAction extends AbstractAction {
- public ApplyAction() {
- super("Apply");
- }
-
- public void actionPerformed(ActionEvent e) {
- triggerCommit();
- }
- }
-
- private class ResetAction extends AbstractAction {
- public ResetAction() {
- super("Reset");
- }
-
- public void actionPerformed(ActionEvent e) {
- triggerFlush();
- }
- }
- }
This example creates a MyPresentationModel
that provides the capability to buffer changes to the CustomerBean
by calling the getBufferedModel
method. This will return a ValueModel
that will buffer changes until the triggerCommit
method is invoked. The triggerCommit
method will be invoked when the user clicks the "Apply" button. The components can have their state restored to the properties original values by invoking the triggerFlush
method on the PresentationModel
. The triggerFlush
method will be invoked when the user clicks the "Reset" button.
PropertyConnector
The PropertyConnector
class keeps two bound JavaBean properties synchronized. The previous example could be improved by enabling the Apply button only when changes have occurred. We can easily implement this behavior using a PropertyConnector
in the MyPresentationModel
class.
- public class MyPresentationModel extends PresentationModel {
-
- public MyPresentationModel(CustomerBean customerBean) {
- super(customerBean);
- ....
-
- // Disable the applyAction by default.
- applyAction.setEnabled(false);
- // We have to use a magic string for the "enabled" property
- // because the javax.swing.Action interface does not define a
- // constant for it.
- PropertyConnector.connect(this, PROPERTYNAME_BUFFERING,
- applyAction, "enabled");
- }
-
- ....
- }
The PropertyConnector
class is simple and powerful. With one line of code we have synchronized the buffering
property of the PresentationModel
with the enabled
property of the JButton
. This causes the Apply button to enable when changes are made to the underlying bean.
Here's a sequence diagram of what occurs when the user enters a new name value for the Customer.
Binding Components
So far the examples used the BasicComponentFactory
class to create all bound components. It is important to understand what the BasicComponentFactory
is doing behind the scenes. The Binding API contains adapter classes for adapting the ValueModel
interface to Swing model classes. For instance, Binding uses the ToggleButtonAdapter
class for adapting the ValueModel
interface to the JToggleButton.ToggleButtonModel
interface required for the JCheckBox
's model. The following example shows how to bind a JCheckBox
to a boolean property without using the BasicComponentFactory
class.
- public class WithoutBasicComponentFactoryExample extends JPanel {
- ....
-
- private void createComponents() {
- ....
- BufferedValueModel preferredValueModel =
- presentationModel.getBufferedModel(
- CustomerBean.PREFERRED_PROPERTY);
- ButtonModel btnModel = new ToggleButtonAdapter(
- preferredValueModel);
- preferredCB = new JCheckBox("Preferred");
- preferredCB.setModel(btnModel);
- ....
- }
- ....
- }
Seeing this example makes us appreciate how much work the BasicComponentFactory
saves us. It is worth mentioning that the BasicComponentFactory
class uses the Bindings
class to setup the bindings for the components it creates. You may need to use the Bindings
class yourself to perform binding for custom components subclassed from the standard Swing components.
The Binding API contains adapters for many other Swing components. The following table lists Swing components with their associated Binding adapters.
Component | Adapter |
JCheckBox | ToggleButtonAdapter |
JCheckBoxMenuItem | ToggleButtonAdapter |
JFormattedTextField | PropertyConnector |
JLabel | PropertyConnector |
JPasswordField | DocumentAdapter |
JSlider | BoundedRangeAdapter |
JTextArea | DocumentAdapter |
JTextField | DocumentAdapter |
JToggleButton | ToggleButtonAdapter |
JRadioButton | RadioButtonAdapter |
JRadioButtonMenuItem | RadioButtonAdapter |
Working with the various adapters is straightforward and is done similarly to our previous examples. The two adapters that are a little different and worth a closer look are the RadioButtonAdapter
and BoundedRangeAdapter
.
RadioButtonAdapter
The Binding API provides the RadioButtonAdapter
class to bind values to a JRadioButton
.
- public class RadioButtonExample {
- ....
-
- private void createComponents() {
- ValueModel favoriteTeamModel = new PropertyAdapter(
- new FavoriteTeamBean(),
- FavoriteTeamBean.FAVORITE_TEAM_PROP,
- true);
-
- cardsBtn = createRadioBtn(
- favoriteTeamModel, "Cardinals", "Cardinals");
- ramsBtn = createRadioBtn(
- favoriteTeamModel, "Rams", "Rams");
- bluesBtn = createRadioBtn(
- favoriteTeamModel, "Blues", "Blues");
- }
-
- private JRadioButton createRadioBtn(
- ValueModel valueModel, Object choice, String text) {
- JRadioButton radioBtn = new JRadioButton(text);
- RadioButtonAdapter rbAdapter = new RadioButtonAdapter(
- valueModel, choice);
- radioBtn.setModel(rbAdapter);
- return radioBtn;
- }
-
- ....
- }
The RadioButtonAdapter
binds a JRadioButton
to the ValueModel
for the favoriteTeam
property. The RadioButtonAdapter
also contains an associated choice Object
. When a radio button is selected, the value of its adapter's choice Object
is set to the favoriteTeam
property of the bean. For instance, if the cardsBtn
radio button is selected the favoriteTeam
property of the FavoriteTeamBean
object will be set to "Cardinals".
BoundedRangeAdapter
The Binding API provides the BoundedRangeAdapter
class to bind values to a JSlider
.
- public class SliderExample {
- ....
-
- private void createComponents() {
- ValueModel intValueModel = new PropertyAdapter(
- new IntBean(), IntBean.VALUE_PROP, true);
- BoundedRangeAdapter boundedRangeAdapter =
- new BoundedRangeAdapter(intValueModel, 0, 0, 1000);
- slider = new JSlider(boundedRangeAdapter);
- }
-
- ....
- }
The BoundedRangeAdapter
binds a JSlider
to the value property of the IntBean
. It is provided an extent, minimum and maximum values for the range just as you would provide to a DefaultBoundedRangeModel
. As the JSlider is moved on the screen, the value property of the IntBean
will be kept in sync with the JSlider model's value.
Binding Lists of Objects
The SelectionInList
class provides the capability to bind lists of objects to UI components. Lists of objects can be bound to JList
s, JComboBox
es and JTable
s. The SelectionInList
implements the ValueModel
interface for a list of objects, the selected object in the list, and the selected index of the list. Here's an example of the SelectionInList
class in action.
- public class ListExample {
- ....
-
- private java.util.List<String> createData() {
- java.util.List<String> data = new ArrayList<String>();
- data.add("Cardinals");
- data.add("Rams");
- data.add("Blues");
- return data;
- }
-
- private void createComponents() {
- TeamBean teamBean = new TeamBean();
- BeanAdapter beanAdapter = new BeanAdapter(teamBean, true);
- ValueModel teamNameValueModel = beanAdapter.getValueModel(
- TeamBean.NAME_PROP);
- SelectionInList selectionList =
- new SelectionInList(createData(), teamNameValueModel);
-
- jlist = BasicComponentFactory.createList(selectionList);
- textFld = BasicComponentFactory.createTextField(
- teamNameValueModel);
- }
-
- ....
- }
This example provides a list of team names to select as your favorite team. The selected name will be bound to the name property of a TeamBean
instance. The selected name will be displayed in the JTextField
. The SelectionInList
class implements the ListModel
interface and can be set as the data model for the JList
. Also by using the BasicComponentFactory.createList
method (which in turn uses Binding.bind
) a SelectionModel
for the JList
is created that is bound to the selected index of the SelectionInList
.
Here's the same example using a JComboBox
instead of a JList
.
- public class ComboBoxExample {
- ....
-
- private void createComponents() {
- TeamBean teamBean = new TeamBean();
- BeanAdapter beanAdapter = new BeanAdapter(teamBean, true);
- ValueModel teamNameValueModel = beanAdapter.getValueModel(
- TeamBean.NAME_PROP);
-
- ComboBoxModel cbModel = new ComboBoxAdapter(createData(),
- teamNameValueModel);
- comboBox = new JComboBox(cbModel);
- textFld = BasicComponentFactory.createTextField(teamNameValueModel);
- }
-
- ....
- }
Summary
JGoodies Binding makes binding domain object properties to UI components a breeze. This article has demonstrated how using the Binding API can make your code easier to implement and maintain. If you find the Binding API useful you should consider licensing the JGoodies Swing Suite. The Suite is built on top of the various open source JGoodies libraries and provides a strong foundation and quick start for building rich client applications in Swing.
References
- [1] Swing Data Binding Presentation
http://jgoodies.com/articles/binding.pdf - [2] JGoodies Binding Home Page
https://binding.dev.java.net - [3] JGoodies Forms Home Page
https://forms.dev.java.net - [4] Intro to JGoodies Forms
- [5] Presentation Model
http://martinfowler.com/eaaDev/PresentationModel.html - [6] JGoodies: Understanding Binding - Part 1
http://www.javalobby.org/java/forums/t17672 - [7] JGoodies: Understanding Binding - Part 2
http://www.javalobby.org/java/forums/t17707 - [8] JGoodies: Understanding Binding - Part 3
http://www.javalobby.org/java/forums/t17728 - [9] Desktop Java Live
http://www.sourcebeat.com/TitleAction.do?id=10
Software Engineering Tech Trends (SETT) is a regular publication featuring emerging trends in software engineering.