• Articles
  • /
  • Creating a binder for Spring RCP
 

Creating a custom built binding and binder in Spring RCP

Introduction

For those not familiar with RCP, a quick heads-up. Spring RCP is a Swing application framework, based on the Spring Framework. It utilizes many of Spring's available utilities, the Spring IOC container being the most important one.

Bindings and binders

Spring RCP gives you the possibility to create controls that are bound to certain properties of a given object. These bound controls are called bindings. A binder is a class that creates bindings. A binding factory, if you will.

Standard Spring RCP has some basic binders, but often you will encounter user requirements that require you to build a custom binder. For example, a binder to show a list of objects in a JTable, a binder to show a String in a formatted text field or even a binding to show and select an image. These are not standard in RCP, and you'll need to make those yourself.

Making a binding

For this example, I'll create a binding for a java.io.File property.

Creating a custom control

First of all, you want to know what you want as a control. In my example, I want a non-editable textfield with a button next to it, which shows a dialog in which I can choose a file. Quite straightforward. Unfortunately, no standard control is available in Swing for this, so I made my own. Pay attention, this class has a dependency on JGoodies' FormLayout. If you don't want this, you'll have to refactor out the layoutmanager and add you own manager of choice.

import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

public class FileChooserPanel extends JComponent
{
    private final JTextField fileField;
    private final JButton chooseFileButton;
    private File fileChosen;
    private JFileChooser chooser;
    private Set fileChangeListeners;

    public FileChooserPanel()
    {
        chooser = new JFileChooser();
        chooser.setMultiSelectionEnabled(false);
        chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);

        fileField = new JTextField();
        fileField.setEditable(false);

        chooseFileButton = new JButton("...");
        chooseFileButton.addActionListener(new Handler());

        createControl();

        fileChangeListeners = new HashSet();
    }

    private void createControl()
    {
        setLayout(new FormLayout("fill:1dlu:grow, 2dlu, fill:pref:nogrow", "default"));
        add(fileField, new CellConstraints(1, 1));
        add(chooseFileButton, new CellConstraints(3, 1));
    }

    public File getFileChosen()
    {
        return fileChosen;
    }

    public void setReadOnly(boolean readOnly)
    {
        chooseFileButton.setEnabled(!readOnly);
    }

    public void setEnabled(boolean enabled)
    {
        chooseFileButton.setEnabled(enabled);
        fileField.setEnabled(enabled);
    }

    public void setFileChosen(File fileChosen)
    {
        notifyListeners(this.fileChosen, fileChosen);
        this.fileChosen = fileChosen;
        fileField.setText(fileChosen.getAbsolutePath());
    }

    public void addFileListener(PropertyChangeListener listener)
    {
        fileChangeListeners.add(listener);
    }
    
    public void removeFileListener(PropertyChangeListener listener)
    {
        fileChangeListeners.remove(listener);
    }

    public void notifyListeners(File oldFile, File newFile)
    {
        PropertyChangeEvent event = new PropertyChangeEvent(this, "fileChosen", oldFile, newFile);
        Iterator listenerIterator = fileChangeListeners.iterator();
        while(listenerIterator.hasNext())
        {
            PropertyChangeListener listener = (PropertyChangeListener) listenerIterator.next();
            listener.propertyChange(event);
        }
    }

    class Handler implements ActionListener
    {
        public void actionPerformed(ActionEvent e)
        {
            int result = chooser.showOpenDialog(null);
            if(result == JFileChooser.APPROVE_OPTION)
            {
                setFileChosen(chooser.getSelectedFile());
            }
        }
    }
}

The control seems to have all I need:

  • I can get the chosen file
  • I can attach listeners to notify when I've chosen a file
  • I have enabled and read-only support

Ok, we're set to create our binding.

Creating the binding

To create a custom binding, you'll start off by creating a class and extending CustomBinding. Here, I'll call it FileBinding.

import org.springframework.richclient.form.binding.support.CustomBinding;

public class FileBinding extends CustomBinding
{
   ...
}

First of all, CustomBinding requires me to implement 4 methods:

  • valueModelChanged(Object)

    This is called when the underlying valueModel is changed by the framework. So when the value is changed in RCP, we'll have to show the value in the binding's control through this method.

  • enabledChanged()

    Called when the framework detects that the enabled state of the property is changed. We'll have to update our control to reflect this. The binding's enabled state can be queried by isEnabled()

  • readOnlyChanged()

    Called when the framework detects that the read-only state of the property is changed. We'll have to update our control to reflect this. The binding's enabled state can be queried by isReadOnly()

  • doBindControl()

    This will return the actual control to be shown by this binder.

Our binding will also need the control we just created. So we'll do that first. The constructor of a binding always takes on a FormModel and a property path. You also need to give a type of class this Binding can handle, but since we know it'll be java.io.File , we provide it hard-coded.

import org.springframework.richclient.form.binding.support.CustomBinding;
import java.io.File;

public class FileBinding extends CustomBinding
{
    private final FileChooserPanel fileChooser;
        
        public FileBinding(FormModel formModel, String formPropertyPath)
        {
            super(formModel, formPropertyPath, File.class)
        }
        
        ...
}

Now, let's create the valueModelChanged method. The argument of this method reflects the new value in the valueModel of the binding's property, so we just need to show this value in out control. We know the binding only accepts java.io.File , so we can safely cast to that. And we've got a method on our control to set a file, so all in all, it'll be quite simple.

...
protected void valueModelChanged(Object newValue)
{
        fileChooser.setFileChosen((File) newValue);
}
...

That was easy. Equally easy are the readOnlyChanged and enabledChanged methods.

...
protected void readOnlyChanged()
{
        fileChooser.setReadOnly(isReadOnly());
}

protected void enabledChanged()
{
        fileChooser.setEnabled(isEnabled());
}
...

Now we'll do the doBindControl method. It returns the control for the binding.

...
protected JComponent doBindControl()
{
        return fileChooser;
}
...

We're done, right? Uhm... let's see. Read-only... check. Enabled... check. Update control when valuemodel changed... check. Update valuemodel when control changed... darn. Ok, we forgot that one.

When the control's value is changed, the underlying valuemodel still needs to be informed of the change. The method to do just that is controlValueChanged(Object) . Logical, isn't it. So we'll add a PropertyChangeListener, which coincidentally is provided through our control.

...
protected JComponent doBindControl()
{
        fileChooser.addFileListener(new PropertyChangeListener()
        {
                public void propertyChange(PropertyChangeEvent evt)
                {
                        controlValueChanged(evt.getNewValue());
                }
        });
        return fileChooser;
}
...

Ok, the binding's done. The complete binding looks something like this.

import org.springframework.binding.form.FormModel;
import org.springframework.richclient.form.binding.support.CustomBinding;

import javax.swing.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;

public class FileBinding extends CustomBinding
{
    private final FileChooserPanel fileChooser;
    
    public FileBinding(FormModel formModel, String formPropertyPath)
    {
        super(formModel, formPropertyPath, File.class);
        fileChooser = new FileChooserPanel();
    }

    protected JComponent doBindControl()
    {
        fileChooser.addFileListener(new PropertyChangeListener()
        {
            public void propertyChange(PropertyChangeEvent evt)
            {
                controlValueChanged(evt.getNewValue());
            }
        });
        return fileChooser;
    }

    protected void readOnlyChanged()
    {
        fileChooser.setReadOnly(isReadOnly());
    }

    protected void enabledChanged()
    {
        fileChooser.setEnabled(isEnabled());
    }

    protected void valueModelChanged(Object newValue)
    {
        fileChooser.setFileChosen((File) newValue);
    }
}

Creating the binder

Now, create the binder. This one's really easy. Just implement Binder.

import org.springframework.richclient.form.binding.Binder;
import org.springframework.richclient.form.binding.Binding;
import org.springframework.binding.form.FormModel;

import javax.swing.*;
import java.util.Map;

public class FileBinder implements Binder
{
    public Binding bind(FormModel formModel, String formPropertyPath, Map context)
    {
        return new FileBinding(formModel, formPropertyPath);
    }

    public Binding bind(JComponent control, FormModel formModel, String formPropertyPath, Map context)
    {
        throw new UnsupportedOperationException("The binder needs a custom created component and will take care of this" +
                " itself");
    }
}

Binder has 2 methods to implement. One that supplies a control, one that doesn't. I've been lazy here, I want my Binding to handle control creation, not me. So I don't support binding creation with a control provided. If you want that, you'll have to change the Binding to accept a control too (you'll probably only want to accept FileChooserPanel, so you'll have to do a check there).

Using the binding

I'll show you a quick example on how to use the example. Assume we have an object with a single property myFileField of type java.io.File . Creating a form for it with our binding would be:

class ExampleForm extends AbstractForm
{
        public ExampleForm()
        {
                super(FormModelHelper.createFormModel(new ExampleObject(), "exampleForm"));
        }
        
        protected JComponent createFormControl()
        {
                TableFormBuilder builder = new TableFormBuilder(getBindingFactory());
                FileBinding binding = new FileBinding(getFormModel(), "myFileField");
                builder.add(binding);
                return builder.getForm();
        }
}

The binder

The binder you can plug into the Spring RCP BinderSelectionStrategy to always use it when you show a java.io.File property. That way Spring RCP will handle binding creation, and you don't need to think about it.

That's it

This concludes my simple tutorial on creating a binder and binding. Be warned, there might be a syntax error here and there, and if you find one, don't hesitate to inform me . You can also contact me through MSN on that address.

 
2009 © Lieven Doclo