June 2005: Introduction to JGoodies Binding

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:

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.

Figure 1: Typical GUI Application

Typical GUI
  1. public class CustomerBean extends Model {
  2. public static final String NAME_PROPERTY = "name";
  3. public static final String PREFERRED_PROPERTY = "preferred";
  4.  
  5. private String name = "";
  6. private boolean preferred;
  7.  
  8. public String getName() {
  9. return name;
  10. }
  11.  
  12. public void setName(String newName) {
  13. String oldValue = name;
  14. name = newName;
  15. firePropertyChange(NAME_PROPERTY, oldValue, newName);
  16. }
  17.  
  18. public boolean isPreferred() {
  19. return preferred;
  20. }
  21.  
  22. public void setPreferred(boolean isPreferred) {
  23. boolean oldValue = preferred;
  24. preferred = isPreferred;
  25. firePropertyChange(PREFERRED_PROPERTY, oldValue, isPreferred);
  26. }
  27.  
  28. }

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.

  1. public class TypicalGUI extends JPanel
  2. implements ActionListener, ItemListener, PropertyChangeListener {
  3. ....
  4.  
  5. public TypicalGUI() {
  6. preferredValueCB.setEnabled(false);
  7.  
  8. nameFld.addActionListener(this);
  9. preferredCB.addItemListener(this);
  10. myCustomerBean.addPropertyChangeListener(this);
  11.  
  12. // set components initial values
  13. modelToView();
  14. }
  15.  
  16. public void actionPerformed(ActionEvent e) {
  17. viewToModel();
  18. }
  19.  
  20. public void itemStateChanged(ItemEvent e) {
  21. viewToModel();
  22. }
  23.  
  24. public void propertyChange(PropertyChangeEvent evt) {
  25. modelToView();
  26. }
  27.  
  28. private void modelToView() {
  29. nameFld.setText(myCustomerBean.getName());
  30. preferredCB.setSelected(myCustomerBean.isPreferred());
  31. }
  32.  
  33. private void viewToModel() {
  34. myCustomerBean.setName(nameFld.getText());
  35. myCustomerBean.setPreferred(preferredCB.isSelected());
  36. }
  37.  
  38. ....
  39. }

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.

Figure 2: Typical GUI Sequence Diagram

Typical GUI Sequence Diagram

 

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.

  1. public interface ValueModel {
  2. Object getValue();
  3.  
  4. void setValue(Object o);
  5.  
  6. void addValueChangeListener(PropertyChangeListener l);
  7.  
  8. void removeValueChangeListener(PropertyChangeListener l);
  9. }

The ValueModel interface abstracts getting and setting an individual value, and provides the ability to observe changes to the value with PropertyChangeListeners. There are three "glue code" classes we can use to bind object properties to UI components:

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.

  1. public class PropertyAdapterExample extends JPanel {
  2. ....
  3.  
  4. private void createComponents() {
  5. myCustomerBean = new CustomerBean();
  6. ValueModel stringValueModel = new PropertyAdapter(
  7. myCustomerBean, CustomerBean.NAME_PROPERTY, true);
  8. ValueModel boolValueModel = new PropertyAdapter(
  9. myCustomerBean, CustomerBean.PREFERRED_PROPERTY, true);
  10.  
  11. nameFld = BasicComponentFactory.createTextField(stringValueModel);
  12. preferredCB = BasicComponentFactory.createCheckBox(boolValueModel,
  13. "Preferred");
  14. }
  15. ...
  16. }

The example above creates PropertyAdapters 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 PropertyChangeEvents). 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.

Figure 3: PropertyAdapter Sequence Diagram

Property Adapter Sequence Diagram

 

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 PropertyChangeEvents 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 PropertyAdapters 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.

  1. public class BeanAdapterExample extends JPanel {
  2. ....
  3.  
  4. private void createComponents() {
  5. myCustomerBean = new CustomerBean();
  6.  
  7. BeanAdapter beanAdapter = new BeanAdapter(myCustomerBean, true);
  8. ValueModel stringValueModel = beanAdapter.getValueModel(
  9. CustomerBean.NAME_PROPERTY);
  10. ValueModel boolValueModel = beanAdapter.getValueModel(
  11. CustomerBean.PREFERRED_PROPERTY);
  12.  
  13. nameFld = BasicComponentFactory.createTextField(stringValueModel);
  14. preferredCB = BasicComponentFactory.createCheckBox(boolValueModel,
  15. "Preferred");
  16. }
  17. ....
  18. }

PresentationModel

The PresentationModel provides binding functionality in the same manner as the BeanAdapterclass 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.

Figure 4: Presentation Model GUI

Presentation Model GUI
  1. public class PresentationModelExample extends JPanel {
  2. ....
  3.  
  4. private void createComponents() {
  5. MyPresentationModel presentationModel =
  6. new MyPresentationModel(new CustomerBean());
  7.  
  8. nameFld = BasicComponentFactory.createTextField(
  9. presentationModel.getBufferedModel(
  10. CustomerBean.NAME_PROPERTY));
  11. preferredCB = BasicComponentFactory.createCheckBox(
  12. presentationModel.getBufferedModel(
  13. CustomerBean.PREFERRED_PROPERTY), "Preferred");
  14.  
  15. applyBtn = new JButton(presentationModel.getApplyAction());
  16. resetBtn = new JButton(presentationModel.getResetAction());
  17. }
  18.  
  19. ...
  20. }
  21.  
  22. public class MyPresentationModel extends PresentationModel {
  23. ....
  24.  
  25. public MyPresentationModel(CustomerBean customerBean) {
  26. super(customerBean);
  27. applyAction = new ApplyAction();
  28. resetAction = new ResetAction();
  29. }
  30.  
  31. public Action getApplyAction() {
  32. return applyAction;
  33. }
  34.  
  35. public Action getResetAction() {
  36. return resetAction;
  37. }
  38.  
  39. private class ApplyAction extends AbstractAction {
  40. public ApplyAction() {
  41. super("Apply");
  42. }
  43.  
  44. public void actionPerformed(ActionEvent e) {
  45. triggerCommit();
  46. }
  47. }
  48.  
  49. private class ResetAction extends AbstractAction {
  50. public ResetAction() {
  51. super("Reset");
  52. }
  53.  
  54. public void actionPerformed(ActionEvent e) {
  55. triggerFlush();
  56. }
  57. }
  58. }

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.

  1. public class MyPresentationModel extends PresentationModel {
  2.  
  3. public MyPresentationModel(CustomerBean customerBean) {
  4. super(customerBean);
  5. ....
  6.  
  7. // Disable the applyAction by default.
  8. applyAction.setEnabled(false);
  9. // We have to use a magic string for the "enabled" property
  10. // because the javax.swing.Action interface does not define a
  11. // constant for it.
  12. PropertyConnector.connect(this, PROPERTYNAME_BUFFERING,
  13. applyAction, "enabled");
  14. }
  15.  
  16. ....
  17. }

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.

Figure 5: PropertyConnector Sequence Diagram


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.

  1. public class WithoutBasicComponentFactoryExample extends JPanel {
  2. ....
  3.  
  4. private void createComponents() {
  5. ....
  6. BufferedValueModel preferredValueModel =
  7. presentationModel.getBufferedModel(
  8. CustomerBean.PREFERRED_PROPERTY);
  9. ButtonModel btnModel = new ToggleButtonAdapter(
  10. preferredValueModel);
  11. preferredCB = new JCheckBox("Preferred");
  12. preferredCB.setModel(btnModel);
  13. ....
  14. }
  15. ....
  16. }

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.

  1. public class RadioButtonExample {
  2. ....
  3.  
  4. private void createComponents() {
  5. ValueModel favoriteTeamModel = new PropertyAdapter(
  6. new FavoriteTeamBean(),
  7. FavoriteTeamBean.FAVORITE_TEAM_PROP,
  8. true);
  9.  
  10. cardsBtn = createRadioBtn(
  11. favoriteTeamModel, "Cardinals", "Cardinals");
  12. ramsBtn = createRadioBtn(
  13. favoriteTeamModel, "Rams", "Rams");
  14. bluesBtn = createRadioBtn(
  15. favoriteTeamModel, "Blues", "Blues");
  16. }
  17.  
  18. private JRadioButton createRadioBtn(
  19. ValueModel valueModel, Object choice, String text) {
  20. JRadioButton radioBtn = new JRadioButton(text);
  21. RadioButtonAdapter rbAdapter = new RadioButtonAdapter(
  22. valueModel, choice);
  23. radioBtn.setModel(rbAdapter);
  24. return radioBtn;
  25. }
  26.  
  27. ....
  28. }

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.

  1. public class SliderExample {
  2. ....
  3.  
  4. private void createComponents() {
  5. ValueModel intValueModel = new PropertyAdapter(
  6. new IntBean(), IntBean.VALUE_PROP, true);
  7. BoundedRangeAdapter boundedRangeAdapter =
  8. new BoundedRangeAdapter(intValueModel, 0, 0, 1000);
  9. slider = new JSlider(boundedRangeAdapter);
  10. }
  11.  
  12. ....
  13. }

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 JLists, JComboBoxes and JTables. 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.

  1. public class ListExample {
  2. ....
  3.  
  4. private java.util.List<String> createData() {
  5. java.util.List<String> data = new ArrayList<String>();
  6. data.add("Cardinals");
  7. data.add("Rams");
  8. data.add("Blues");
  9. return data;
  10. }
  11.  
  12. private void createComponents() {
  13. TeamBean teamBean = new TeamBean();
  14. BeanAdapter beanAdapter = new BeanAdapter(teamBean, true);
  15. ValueModel teamNameValueModel = beanAdapter.getValueModel(
  16. TeamBean.NAME_PROP);
  17. SelectionInList selectionList =
  18. new SelectionInList(createData(), teamNameValueModel);
  19.  
  20. jlist = BasicComponentFactory.createList(selectionList);
  21. textFld = BasicComponentFactory.createTextField(
  22. teamNameValueModel);
  23. }
  24.  
  25. ....
  26. }

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.

  1. public class ComboBoxExample {
  2. ....
  3.  
  4. private void createComponents() {
  5. TeamBean teamBean = new TeamBean();
  6. BeanAdapter beanAdapter = new BeanAdapter(teamBean, true);
  7. ValueModel teamNameValueModel = beanAdapter.getValueModel(
  8. TeamBean.NAME_PROP);
  9.  
  10. ComboBoxModel cbModel = new ComboBoxAdapter(createData(),
  11. teamNameValueModel);
  12. comboBox = new JComboBox(cbModel);
  13. textFld = BasicComponentFactory.createTextField(teamNameValueModel);
  14. }
  15.  
  16. ....
  17. }

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

 

The Software Engineering Tech Trends is a monthly publication featuring emerging trends in software engineering.

Subscribe

© Copyright Object Computing, Inc. 1993, 2018. All rights reserved

secret