(Re-)Binding SWT forms with WindowBuilder

(Re-)Binding SWT forms with WindowBuilder

A very common case in UI applications are forms which are bound to exchangeable model objects. For example, one might want to bind this address form in such a way that you can set a new Address object at any time with the UI reflecting that change:

Example: Address Form

The plain old binding can be created easily using WindowBuilder:

Create Binding in WindowBuilder

The resulting code might look like this:

public class AddressViewPart extends ViewPart {  private DataBindingContext bindingContext; private Address address; private Text textName;  @Override public void createPartControl(Composite parent) { parent.setLayout(new GridLayout(3, false));  Label lblName = new Label(parent, SWT.NONE); lblName.setText(Messages.YetAnotherViewPart_lblName_text);  textName = new Text(parent, SWT.BORDER); textName.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 2, 1));  // ...  bindingContext = initDataBindings(); }  protected DataBindingContext initDataBindings() { DataBindingContext bindingContext = new DataBindingContext(); // IObservableValue textNameValue = WidgetProperties.text(SWT.Modify).observe(textName); IObservableValue addressNameValue = PojoProperties.value("name").observe(address); bindingContext.bindValue(textNameValue, addressNameValue, null, null); // return bindingContext; } }

The tricky part is to bind this in such a way that the model object can be exchanged at any time. There are three possible ways:

1) Dispose bindings

The easiest way is to dispose all bindings and just re-bind whenever a new model object appears:

public class AddressViewPart extends ViewPart {  // ...  protected void setAddress(Address address) { this.address = address; if (bindingContext != null) bindingContext.dispose(); bindingContext = initDataBindings(); }  // ... }

Unfortunately, WindowBuilder insists on putting the initDataBindings call in createPartControl. Also, if you are using ControlDecorationSupport you will stumble upon Bug 341713 - DataBinding ControlDecorationSupport not disposed when DataBindingContext is disposed.

2) Binding to a WritableValue

Another way is to use a detail binding to a WritableValue which can be changed at any time:

public class AddressViewPart extends ViewPart {  private WritableValue addressValue = new WritableValue();  protected void setAddress(Address address) { this.addressValue.setValue(address); }  protected DataBindingContext initDataBindings() { // ... IObservableValue addressNameValue = PojoProperties.value("name").observeDetail(addressValue); // ... } }

Such a Binding can be created in WindowBuilder:

Detail binding in WindowBuilder

3) Bean binding

Yet another way is to make the ViewPart itself a Bean with PropertyChangeSupport:

public class AddressViewPart extends ViewPart {  private PropertyChangeSupport changes = new PropertyChangeSupport(this);  private Address address;  public Address getAddress() { return address; }  public void setAddress(Address address) { changes.firePropertyChange("address", this.address, this.address = address); }  public void addPropertyChangeListener(PropertyChangeListener l) { changes.addPropertyChangeListener(l); }  public void removePropertyChangeListener(PropertyChangeListener l) { changes.removePropertyChangeListener(l); }  protected DataBindingContext initDataBindings() { //... IObservableValue addressNameValue = BeanProperties.value("address.name").observe(this); //... }  }

While I like this way conceptionally, in practice it has the problems of Java’s PropertyChangeSupport being cumbersome. Also, JFace Data Binding doesn’t allow to mix Beans and Pojos, so if the Address object in the example is a Pojo, one will get NoSuchMethodException: Address.addPropertyChangeListener(java.beans.PropertyChangeListener). And there seems to be no way to generate such a binding in WindowBuilder.