Support free tutorials



vogella training Training Books

JFace Data Binding - Tutorial

Lars Vogel

Simon Scholz

Version 4.0

01.04.2015

JFace Data binding

This tutorial explains Eclipse JFace Data Binding which can be used to synchronize data between different objects. This tutorial is based on Eclipse 4.4 and Java 1.7.


Table of Contents

1. Data Binding with JFace
1.1. What are Data Binding frameworks?
1.2. JFace Data Binding
2. JFace Data Binding Plug-ins
3. Listening to changes
3.1. Ability to listen to changes in UI components
3.2. Ability to listen to changes in the domain model
3.3. Property change support
3.4. Data Binding and Java objects without change notification
4. Create bindings
4.1. Observing properties with the IObservableValue interface
4.2. Creating instances of the IObservableValue
4.3. Connecting properties with the DataBindingContext
4.4. Example: how to observe properties
4.5. Observing nested properties
5. Update strategy, converters and validators
5.1. UpdateValueStrategy
5.2. Converter
5.3. Validator
6. More on bindings
6.1. ControlDecorators
6.2. Placeholder binding with WritableValue
6.3. Binding values of a radio button group
6.4. Listening to all changes in the binding
6.5. More information on Data Binding
7. Data Binding for JFace Viewers
7.1. Binding Viewers
7.2. Observing list details
7.3. ViewerSupport
7.4. Master Detail binding
7.5. Chaining properties
8. Extending data binding with custom observables
8.1. Developing custom observables
8.2. Directly implement IObservable
8.3. Implement an IProperty rather than IObservable directly
8.4. Delegates for common properties of different objects
9. Example: DateTime Widget and Java 8 time API
10. Prerequisites for this tutorial
11. Data Binding with SWT controls
11.1. First example
11.2. More Customer Validations and ControlDecoration
12. Tutorial: WritableValue
13. Tutorial: Data Binding for a JFace Viewer
14. Using ObservableListContentProvider and ObservableMapLabelProvider
15. About this website
16. Links and Literature
16.1. Eclipse Data Binding resources
16.2. vogella GmbH training and consulting support

1. Data Binding with JFace

1.1. What are Data Binding frameworks?

A Data Binding framework connects properties of objects. It is typically used to synchronize properties of user interface widgets with properties of other Java objects. These Java objects are typically called the data model or the domain model.

Data Binding frameworks synchronize changes of these properties. They typically support validation and conversion during the synchronization process. This synchronization is depicted in the following graphic.

For example you could bind the String property called firstName of a Java object to a text property of the SWT Text widget. If the user changes the text in the user interface, the corresponding property in the Java object is updated.

1.2. JFace Data Binding

JFace Data Binding is an Eclipse framework for data binding and is frequently used in Eclipse based applications.

2. JFace Data Binding Plug-ins

The following plug-ins are required to use JFace Data Binding.

  • org.eclipse.core.databinding

  • org.eclipse.core.databinding.beans

  • org.eclipse.core.databinding.property

  • org.eclipse.jface.databinding

3. Listening to changes

3.1. Ability to listen to changes in UI components

To observe changes in an attribute of a Java object, a data binding framework needs to be able to register itself as a listener to this attribute.

The SWT and JFace widgets support this, therefore it is possible to use JFace data binding to update SWT and JFace user interface components.

3.2. Ability to listen to changes in the domain model

JFace Data Binding can be used to observe attributes of a domain model. It can register listeners for these Java objects and gets notified if a change in the model happens. This change notification from the domain model requires that the model objects provide PropertyChangeSupport according to the Java Bean specification.

3.3. Property change support

The data model is typically described as a Plain Old Java Object (POJO) model or a Java Bean model.

The term POJO is used to describe a Java object which does not follow any of the major Java object models, conventions, or frameworks, i.e. a Java object which does not have to fulfill any specific requirements. For example the following is a POJO.

package com.vogella.databinding.example;

public class Person  {
  private String name;

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }
} 

A Java Bean is a Java object which follows the Java Bean specification. This specification requires that the class implements getter and setter methods for all its attributes. It must also implement property change support via the PropertyChangeSupport class and propagate changes to registered listeners.

A Java class which provides PropertyChangeSupport looks like the following example.

package com.vogella.databinding.example;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;

public class ModelObject {
  private final PropertyChangeSupport changeSupport = 
      new PropertyChangeSupport(this);

  public void addPropertyChangeListener(PropertyChangeListener 
      listener) {
    changeSupport.addPropertyChangeListener(listener);
  }

  public void removePropertyChangeListener(PropertyChangeListener 
      listener) {
    changeSupport.removePropertyChangeListener(listener);
  }

  protected void firePropertyChange(String propertyName, Object oldValue,
      Object newValue) {
    changeSupport.firePropertyChange(propertyName, oldValue, newValue);
  }
} 

Other domain classes could extend this class. The following example demonstrates that.

package com.vogella.databinding.example;

public class Person extends ModelObject {
  private String name;

  public String getName() {
    return name;
  }

  public void setName(String name) {
    firePropertyChange("name", this.name, this.name = name);
  }
} 

3.4. Data Binding and Java objects without change notification

If the Java object (POJO) does not support change notification, you can still use Data Binding to connect fields of other objects to fields of this object.

For example you can connect the text property of a SWT Text field to the summary field of a Todo object. While updates in fields of the Todo object will not update the Text widget, relevant change in the user interface will update the domain model (Todo).

4. Create bindings

4.1. Observing properties with the IObservableValue interface

The IObservableValue interface is used to observe properties of objects. On widgets you typically observe the text property but you can also observe other values. For example, you could bind the enabled property to a boolean value of the data model.

4.2. Creating instances of the IObservableValue

JFace Data Binding provides the Properties API for creating IObservableValue instances. The Properties API provides factories to create IObservableValue objects.

The main factories are PojoProperties, BeanProperties, WidgetProperties and ViewerProperties.

Table 1. Factories

Factory Description
PojoProperties Used to create IObservableValues for Java objects.
BeanProperties Used to create IObservableValues objects for Java Beans.
WidgetProperties Used to create IObservableValues for properties of SWT widgets.
ViewerProperties Used to create IObservableValues for properties of JFace Viewer.
Properties Used to create IObservables for properties of any type like Objects, Collections or Maps.
Observables Used to create IObservables for properties of special Objects, Collections, Maps and Entries of an IObservableMap.

4.3. Connecting properties with the DataBindingContext

The DataBindingContext class provides the functionality to connect IObservableValues objects.

Via the DataBindingContext.bindValue() method two IObservableValues objects are connected. The first parameter is the target and the second is the model. During the initial binding the value from the model is copied to the target.

// create new Context
DataBindingContext ctx = new DataBindingContext();

// define the IObservables
IObservableValue target = WidgetProperties.text(SWT.Modify).
  observe(firstName);
IObservableValue model= BeanProperties.
  value(Person.class,"firstName").observe(person);

// connect them
ctx.bindValue(target, model); 

Note

The initial copying from model to target is useful for the initial synchronization. For example if you have an attribute of a Person p object and the text attribute of a Text txtName widget, you typically want to copy the value from p to txtName at the beginning.

4.4. Example: how to observe properties

The following code demonstrates how to create an IObservableValue object for the firstName property of a Java object called person.

// if person is a POJO
IObservableValue myModel = PojoProperties.value("firstName").
  observe(person)
  
// prefer using beans if your data model provides property change support  
IObservableValue myModel = BeansProperties.value("firstName").
  observe(person) 

The next example demonstrates how to create an IObservableValue for the text property of an SWT Text widget called firstNameText.

IObservableValue target = WidgetProperties.text(SWT.Modify).
  observe(firstNameText); 

In case you do not want to manipulate the object properties directly you can place those attributes into an IObservableMap and observe the MapEntries with the Observables.observeMapEntry() method.

import javax.annotation.PostConstruct;
import javax.inject.Inject;
import javax.inject.Named;

import org.eclipse.core.databinding.DataBindingContext;
import org.eclipse.core.databinding.observable.Observables;
import org.eclipse.core.databinding.observable.map.IObservableMap;
import org.eclipse.core.databinding.observable.map.WritableMap;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.e4.core.di.annotations.Optional;
import org.eclipse.e4.ui.di.Persist;
import org.eclipse.e4.ui.services.IServiceConstants;
import org.eclipse.jface.databinding.swt.ISWTObservableValue;
import org.eclipse.jface.databinding.swt.WidgetProperties;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Text;

import com.example.e4.rcp.todo.model.Todo;

public class ObservableMapEntry {

  private static final String SECOND_ATTRIBUTE = "secondAttribute";
  private static final String FIRST_ATTRIBUTE = "firstAttribute";

  private IObservableMap attributesMap = new WritableMap();
  private DataBindingContext dbc;
  private Todo todo;

  @PostConstruct
  public void createUI(Composite parent) {
    dbc = new DataBindingContext();

    Text txtFirstAttribute = new Text(parent, SWT.BORDER);
    Text txtSecondAttribute = new Text(parent, SWT.BORDER);

    // create observables for the Text controls
    ISWTObservableValue txtFirstAttributeObservable = 
        WidgetProperties.text(SWT.Modify).observe(txtFirstAttribute);
    ISWTObservableValue txtSecondAttributeObservable = WidgetProperties.text(SWT.Modify)
        .observe(txtSecondAttribute);

    // create observables for the Map entries
    IObservableValue firstAttributeObservable = 
        Observables.observeMapEntry(attributesMap, FIRST_ATTRIBUTE);
    IObservableValue secondAttributeObservable = 
        Observables.observeMapEntry(attributesMap, SECOND_ATTRIBUTE);

    dbc.bindValue(txtFirstAttributeObservable, firstAttributeObservable);
    dbc.bindValue(txtSecondAttributeObservable, secondAttributeObservable);
  }

  @Inject
  @Optional
  public void setModel(@Named(IServiceConstants.ACTIVE_SELECTION) Todo todo) {
    if (todo != null) {
      this.todo = todo;
      // Set new values for the map entries from a model object
      attributesMap.put(FIRST_ATTRIBUTE, todo.getSummary());
      attributesMap.put(SECOND_ATTRIBUTE, todo.getDescription());
    }
  }

  @Persist
  public void save() {
    if (todo != null) {
      // only store the actual values on save and not directly
      todo.setSummary((String) attributesMap.get(FIRST_ATTRIBUTE));
      todo.setDescription((String) attributesMap.get(SECOND_ATTRIBUTE));
    }
  }
} 

4.5. Observing nested properties

You can also observe nested model properties, e.g., attributes of classes which are contained in another class.

The following code demonstrates how to access the country property in the address field of the object person.

IObservable model = PojoProperties.value(Person.class, 
    "address.country").observe(person); 

5. Update strategy, converters and validators

5.1. UpdateValueStrategy

The bindValue() method allows you to specify UpdateValueStrategy objects as third and fourth parameters. These objects allow you to control how and when the values are updated. The following values are permitted:

Table 2. UpdateValueStrategy

Value Description
UpdateValueStrategy.POLICY_NEVER Policy constant denoting that the source observable's state should not be tracked and that the destination observable's value should never be updated.
UpdateValueStrategy.POLICY_ON_REQUEST Policy constant denoting that the source observable's state should not be tracked, but that validation, conversion and updating the destination observable's value should be performed when explicitly requested. You can call DataBindingContext.updateModels() or DataBindingContext.updateTargets() to update all bindings at once. Or you can call Binding.updateTargetToModel() or Binding.updateModelToTarget() to update a single binding.
UpdateValueStrategy.POLICY_CONVERT Policy constant denoting that the source observable's state should be tracked, including validating changes except for validateBeforeSet(Object), but that the destination observable's value should only be updated on request.
UpdateValueStrategy.POLICY_UPDATE Policy constant denoting that the source observable's state should be tracked, and that validation, conversion and updating the destination observable's value should be performed automatically on every change of the source observable value.


If no UpdateValueStrategy is specified, the UpdateValueStrategy.POLICY_UPDATE is used by default.

You can register converters and validators in the UpdateValueStrategy object.

5.2. Converter

Converters allow to convert the values between the model and the target. Converters are defined based on the IConverter interface.

The most common example for this is the requirement to map String values of a Text widget to your own model attribute classes.

So let's make our Person model smarter and add a programmingSkills field/property to it.

public class Person extends ModelObject {
  private String name;
  private String[] programmingSkills;

  public String getName() {
    return name;
  }

  public void setName(String name) {
    changes.firePropertyChange("name", this.name, this.name = name);
  }
  
  public String[] getProgrammingSkills() {
    return programmingSkills;
  }

  public void setProgrammingSkills(String[] programmingSkills) {
    firePropertyChange("programmingSkills", this.programmingSkills,
        this.programmingSkills = programmingSkills);
  }
} 

Instead of having a list with programming skills of the Person object, the programming skills should be shown commaseparated in a Text widget. Therefore a conversion from a String[] array to a commaseparated String and the other way round is necessary.

For the String to String[] array conversion we use the CommaSeparatedStringToStringArrayConverter class.

package de.vogella.databinding.example.converter;

import org.eclipse.core.databinding.conversion.Converter;

public class CommaSeparatedStringToStringArrayConverter extends Converter {

  public CommaSeparatedStringToStringArrayConverter() {
    // Ensure that the fromType is a String and the toType is a String[] array
    super(String.class, String[].class);
  }

  @Override
  public Object convert(Object fromObject) {
    if(fromObject instanceof String){
      return ((String) fromObject).split(",");
    }
    return null;
  }

} 

For the String[] array to commaseparated String conversion we use the StringArrayToCommaSeparatedStringConverter class.

package de.vogella.databinding.example.converter;

import org.eclipse.core.databinding.conversion.Converter;

public class StringArrayToCommaSeparatedStringConverter extends Converter {

  public StringArrayToCommaSeparatedStringConverter() {
    // Ensure that the fromType is a String[] array and the toType is a String
    super(String[].class, String.class);
  }

  @Override
  public Object convert(Object fromObject) {
    if(fromObject instanceof String[]) {
      String[] stringArray = (String[]) fromObject;
      StringBuilder sb = new StringBuilder();
      int length = stringArray.length;
      for (int i = 0; i < length; i++) {
        String string = stringArray[i];
        sb.append(string);
        if(i + 1 < length) {
          sb.append(",");
        }
      }
      return sb.toString();
    }
    return null;
  }
} 

In order to apply these converters an UpdateValueStrategy for the the binding needs to be defined and applied.

@PostConstruct
public void createPartControl(Composite parent) {
  // create the Person model with programming skills
  Person person = new Person();
  person.setName("John");
  person.setProgrammingSkills(new String[] { "Java", "JavaScript", "Groovy" });

  GridLayoutFactory.swtDefaults().numColumns(2).applyTo(parent);

  Label programmingSkillsLabel = new Label(parent, SWT.NONE);
  programmingSkillsLabel.setText("Programming Skills");
  GridDataFactory.swtDefaults().applyTo(programmingSkillsLabel);

  Text programmingSkillsText = new Text(parent, SWT.BORDER);
  GridDataFactory.fillDefaults().grab(true, false).applyTo(programmingSkillsText);

  // Do the actual binding and conversion
  DataBindingContext dbc = new DataBindingContext();
  
  UpdateValueStrategy programmingSkillsTargetStrategy = new UpdateValueStrategy();
  // apply converter from Text widget to programmingSkills in the Person model
  programmingSkillsTargetStrategy.setConverter(new CommaSeparatedStringToStringArrayConverter());

  UpdateValueStrategy programmingSkillsModelStrategy = new UpdateValueStrategy();
  // apply converter from programmingSkills in the Person model to the Text widget
  programmingSkillsModelStrategy.setConverter(new StringArrayToCommaSeparatedStringConverter());

  // Create the observables, which should be bound
  ISWTObservableValue programmingSkillsTarget = WidgetProperties.text(SWT.Modify).observe(programmingSkillsText);
  IObservableValue programmingSkillsModel = BeanProperties.value("programmingSkills").observe(person);

  // Bind observables together with the appropriate UpdateValueStrategies
  dbc.bindValue(programmingSkillsTarget, programmingSkillsModel, programmingSkillsTargetStrategy,
      programmingSkillsModelStrategy);

} 

It is also possible to combine the CommaSeparatedStringToStringArrayConverter and StringArrayToCommaSeparatedStringConverter into one, which would not ensure the type safety as before, because no fromType and toType are used for the Converter.

Therefore at least an error should be thrown by yourself in case of unsuitable types.

package de.vogella.databinding.example.converter;

import org.eclipse.core.databinding.conversion.Converter;

public class CommaSeparatedStringStringArrayConverter extends Converter {

  public CommaSeparatedStringStringArrayConverter() {
    // pass null for undefined fromType and toType
    super(null, null);
  }

  @Override
  public Object convert(Object fromObject) {
    if (fromObject instanceof String) {
      return ((String) fromObject).split(";");
    } else if (fromObject instanceof String[]) {
      String[] stringArray = (String[]) fromObject;
      StringBuilder sb = new StringBuilder();
      int length = stringArray.length;
      for (int i = 0; i < length; i++) {
        String string = stringArray[i];
        sb.append(string);
        if (i + 1 < length) {
          sb.append(",");
        }
      }
      return sb.toString();
    }
    throw new IllegalArgumentException(fromObject.getClass() + " type cannot be converted by " + getClass());
  }

} 

5.3. Validator

Validators allow you to implement validation of the data before it is propagated to the other connected property. A class which wants to provide this functionality must implement the org.eclipse.core.databinding.validation.IValidator interface.

// define a validator to check that only numbers are entered
 IValidator validator = new IValidator() {
    private final Pattern numbersOnly = Pattern.compile("\\d*");
    @Override
    public IStatus validate(Object value) {
      if (value != null && numbersOnly.matcher(value.toString()).matches()) {
         return ValidationStatus.ok();
      }
      return ValidationStatus.error(value + " is not a number");
    }
};


// create an UpdateValueStrategy and assign it to the binding
UpdateValueStrategy strategy = new UpdateValueStrategy();
strategy.setBeforeSetValidator(validator);

Binding bindValue = 
  ctx.bindValue(widgetValue, modelValue, strategy, null); 

Tip

The WizardPageSupport class provides support to connect the result from the given data binding context to the given wizard page, updating the wizard page's completion state and its error message accordingly.

6. More on bindings

6.1. ControlDecorators

JFace Data Binding allows you to use icon decorators in the user interface which reflect the status of the field validation. This allows you to provide immediate feedback to the user. For the creation of the control decoration you use the return object from the bindvalue() method of DataBindingContext object.

// The following code assumes that a Validator is already defined
Binding bindValue = 
  ctx.bindValue(widgetValue, modelValue, strategy, null);

// add some decorations to the control
ControlDecorationSupport.create(bindValue, SWT.TOP | SWT.LEFT); 

The result might look like the following screenshot.

ControlDecoration with databinding

6.2. Placeholder binding with WritableValue

You can create bindings to a WritableValue object. A WritableValue object can hold a reference to another object.

You can exchange this reference in WritableValue and the databinding will use the new (reference) object for its binding. This way you can create the binding once and still exchange the object which is bound by databinding.

To bind to a WritableValue you use the observeDetail() method, to inform the framework that you would like to observe the contained object.

WritableValue value = new WritableValue();

// create the binding
DataBindingContext ctx = new DataBindingContext();
IObservableValue target = WidgetProperties.
  text(SWT.Modify).observe(text);
IObservableValue model = BeanProperties.value("firstName").
    observeDetail(value);

ctx.bindValue(target, model);
    
// create a Person object called p
Person p = new Person();

// make the binding valid for this new object
value.setValue(p); 

6.3. Binding values of a radio button group

When using radio buttons each button usually represents a certain value. In order to bind the value according to the selected state in a radio button group, the SelectObservableValue class is the right choice.

The following example shows how to bind the currently selected radio value to a Label.

Group group = new Group(shell, SWT.NONE);
group.setText("Radio Group with Names");
GridLayoutFactory.fillDefaults().applyTo(group);
GridDataFactory.fillDefaults().grab(true, true).applyTo(group);

// Options for the radio buttons
String[] names = new String[] { "Matthew Hall", "Christoph Zauner", "Tom Schindl", "Wim Jongman", "Dirk Fauth", "Lars Vogel",
    "Simon Scholz" };

SelectObservableValue selectedRadioButtonObservable = new SelectObservableValue();
for (String name : names) {
  Button button = new Button(group, SWT.RADIO);
  button.setText(name);
  // Add name as option value in case the appropriate button is selected
  selectedRadioButtonObservable.addOption("Selected: " + name, WidgetProperties.selection().observe(button));
}

Label label = new Label(shell, SWT.NONE);
GridDataFactory.fillDefaults().applyTo(label);
ISWTObservableValue labelTextObservable = WidgetProperties.text().observe(label);

DataBindingContext dbc = new DataBindingContext();
// bind label text to currently selected option
dbc.bindValue(selectedRadioButtonObservable, labelTextObservable); 

The result of this snippet, should look like this:

6.4. Listening to all changes in the binding

You can register a listener to all bindings of the DataBindingContext class. Your listener will be called when something has changed.

For example this can be used to determine the status of a part which behaves like an editor. If its data model changes, this editor marks itself as dirty.

// define your change listener
// dirty holds the state for the changed status of the editor
IChangeListener listener = new IChangeListener() {
  @Override
  public void handleChange(ChangeEvent event) {
    // Ensure dirty is not null
    if (dirty!=null){
      dirty.setDirty(true);
    }
};


private void updateUserInterface(Todo todo) {
  
  // check that the user interface is available
  if (txtSummary != null && !txtSummary.isDisposed()) {
  
    
    // Deregister change listener to the old binding
    IObservableList providers = ctx.getValidationStatusProviders();
    for (Object o : providers) {
      Binding b = (Binding) o;
      b.getTarget().removeChangeListener(listener);
    }
  
    // dispose the binding
    ctx.dispose();
  
    // NOTE
    // HERE WOULD BE THE DATABINDING CODE
    // INTENTIALLY LEFT OUT FOR BREVITY
  
  
    
    // get the validation status provides
    IObservableList bindings = 
      ctx.getValidationStatusProviders();

    // mot all validation status providers 
    // are bindings, e.g. MultiValidator
    // otherwise you could use
    // context.getBindings()

    // register the listener to all bindings
    for (Object o : bindings) {
      Binding b = (Binding) o;
      b.getTarget().addChangeListener(listener);
    }
  }
} 

6.5. More information on Data Binding

Data Binding provides lots of examples for other use cases via its version control system. See Wiki on JFace Data Binding for instructions how to access this information.

7. Data Binding for JFace Viewers

7.1. Binding Viewers

JFace Data Binding provides functionality to bind the data of JFace viewers.

Data binding for these viewers distinguish between changes in the collection and changes in the individual object.

In the case that Data Binding observes a collection, it requires a content provider which notifies the viewer, once the data in the collection changes.

The ObservableListContentProvider class is a content provider which requires a list implementing the IObservableList interface. The Properties class allows you to wrap another list with its selfList() method into an IObservableList.

The following snippet demonstrates the usage:

// use ObservableListContentProvider
viewer.setContentProvider(new ObservableListContentProvider());

// create sample data
List<Person> persons = createExampleData();

// wrap the input into a writable list
IObservableList input = 
   Properties.selfList(Person.class).observe(persons);

// set the IObservableList as input for the viewer
viewer.setInput(input); 

7.2. Observing list details

You can also use the ObservableMapLabelProvider class to observe changes of the list elements.

ObservableListContentProvider contentProvider = 
  new ObservableListContentProvider();
  
// create the label provider which includes monitoring
// of changes to update the labels

IObservableSet knownElements = contentProvider.getKnownElements();

final IObservableMap firstNames = BeanProperties.value(Person.class,
  "firstName").observeDetail(knownElements);
final IObservableMap lastNames = BeanProperties.value(Person.class,
  "lastName").observeDetail(knownElements);
  
IObservableMap[] labelMaps = { firstNames, lastNames };

ILabelProvider labelProvider = 
  new ObservableMapLabelProvider(labelMaps) {
  public String getText(Object element) {
    return firstNames.get(element) + " " + lastNames.get(element);
    }
}; 

7.3. ViewerSupport

ViewerSupport simplifies the setup for JFace viewers in cases where selected columns should be displayed. It registers changes listeners on the collection as well as on the individual elements.

ViewerSupport creates via the bind() method the LabelProvider and ContentProvider for a viewer automatically.

// the MyModel.getPersons() method call returns a List<Person> object
// the WritableList object wraps this object in an IObservableList

input = new WritableList(MyModel.getPersons(), Person.class);

// The following  creates and binds the data 
// for the Table based on the provided input
// no additional label provider / 
// content provider / setInput required

ViewerSupport.bind(viewer, input, 
    BeanProperties.
    values(new String[] { "firstName", "lastName", "married" })); 

7.4. Master Detail binding

The ViewerProperties class allows you to create IObservableValues for properties of the viewer. For example you can track the current selection, e.g., which data object is currently selected. This binding is called Master Detail binding as you track the selection of a master.

To access fields in the selection you can use the PojoProperties or the BeanProperties class. Both provide the value().observeDetail() method chain, which allows you to observe a detailed value of an IObservableValue object.

For example the following will map the summary property of the Todo domain object to a Label based on the selection of a ComboViewer.

// assume we have Todo domain objects
// todos is a of type: List<Todo>
final ComboViewer viewer = new ComboViewer(parent, SWT.DROP_DOWN);
viewer.setContentProvider(ArrayContentProvider.getInstance());
viewer.setLabelProvider(new LabelProvider() {
  public String getText(Object element) {
    Todo todo = (Todo) element;
    return todo.getSummary();
  };
});
viewer.setInput(todos);

// create a Label to map to
Label label = new Label(parent, SWT.BORDER);
// parent has a GridLayout assigned
label.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));


DataBindingContext dbc = new DataBindingContext();

// for binding to the label
IObservableValue target = WidgetProperties.text().observe(label);

// observe the selection
IViewerObservableValue selectedTodo = ViewerProperties
    .singleSelection().observe(viewer);
// observe the summary attribute of the selection
IObservableValue detailValue = 
  PojoProperties
    .value("summary", String.class)
    .observeDetail(selectedTodo)

dbc.bindValue(target, detailValue); 

7.5. Chaining properties

You can chain properties together to simplify observing nested properties. The following examples demonstrate this usage.

IObservableValue viewerSelectionSummaryObservable = 
  ViewerProperties.singleSelection()
    .value(BeanProperties.value("summary", String.class))
    .observe(viewer); 

IListProperty siblingNames = BeanProperties.
  value("parent").list("children").values("name");
IObservableList siblingNamesObservable = 
  siblingNames.observe(node); 

8. Extending data binding with custom observables

8.1. Developing custom observables

Sometimes the observables, which are provided by the factories (see Table 1, “Factories”), are not sufficient and you might want to create a custom IObservable.

This implementation can extend the AbstractObservable class or one of it's subclasses, like the AbstractObservableValue class.

For example the following custom widget allows to set a text.

public class CustomWidget extends Canvas {
  
  private String text;
  private Point textExtent;

  public CustomWidget(Composite parent) {
    super(parent, SWT.NONE);
    addPaintListener(new PaintListener() {
      

      @Override
      public void paintControl(PaintEvent e) {
        GC gc = e.gc;
        
        // draw the text, which can be set
        String textValue = getText() != null ? getText() : "Good Default";
        gc.drawText(textValue, 5, 5);
        textExtent = gc.textExtent(textValue);
      }
    });
  }
  
  @Override
  public Point computeSize(int wHint, int hHint, boolean changed) {
    checkWidget();
    return textExtent != null ? textExtent : new Point(30, 12);
  }

  public String getText() {
    checkWidget();
    return text;
  }

  public void setText(String text) {
    checkWidget();
    this.text = text;
    redraw();
  }

} 

Note

Since there is no predefined IObservable for custom widgets, you have to implement a custom one. While is is possible to use PojoProperties class to create an IObservableValue by using PojoProperties.value("text").observe(customWidget) this would not result in an ISWTObservableValue. Only by using an ISWTObservable classes like ControlDecorationSupport would work, because only those offer a public Widget getWidget(); method, so that the widget may be decorated.

8.2. Directly implement IObservable

The CustomWidgetObservableValue extends AbstractObservableValue and also implements the ISWTObservableValue interface.

public class CustomWidgetObservableValue extends AbstractObservableValue implements ISWTObservableValue {

  private CustomWidget customWidget;

  public CustomWidgetObservableValue(CustomWidget customWidget) {
    this(customWidget, Realm.getDefault());
  }

  public CustomWidgetObservableValue(CustomWidget customWidget ,Realm realm) {
    super(realm);
    this.customWidget = customWidget;
  }

  @Override
  public Object getValueType() {
    return String.class;
  }

  @Override
  protected Object doGetValue() {
    return customWidget.getText();
  }
  
  @Override
  protected void doSetValue(Object value) {
    customWidget.setText(value.toString());
  }

  @Override
  public Widget getWidget() {
    // implement the ISWTObservableValue interface to enable ControlDecorationSupport
    return customWidget;
  }
} 

This observable can then be used like this:

@PostConstruct
public void createComposite(Composite parent) {
  parent.setLayout(new GridLayout(1, false));

  DataBindingContext dbc = new DataBindingContext();
  
  CustomWidget widget = new CustomWidget(parent);
  CustomWidgetObservableValue customWidgetObservableValue = new CustomWidgetObservableValue(widget);
  
  Todo todo = //...
  IObservableValue todoSummaryObservable = PojoProperties.value("summary").observe(todo);
  
  dbc.bindValue(customWidgetObservableValue, todoSummaryObservable);
} 

8.3. Implement an IProperty rather than IObservable directly

A better approach is to implement the IProperty interface, like IValueProperty, IWidgetValueProperty and others.

So let's implement the solution of the previous section with an IProperty implementation.

public class CustomWidgetProperty extends WidgetValueProperty {

  @Override
  public Object getValueType() {
    return String.class;
  }

  @Override
  protected Object doGetValue(Object source) {
    if(source instanceof CustomWidget) {
      return ((CustomWidget) source).getText();
    }
    
    return "";
  }

  @Override
  protected void doSetValue(Object source, Object value) {
    if(source instanceof CustomWidget && value instanceof String) {
      ((CustomWidget) source).setText((String) value);
    }
  }
} 

This WidgetValueProperty can be used like this:

@PostConstruct
public void createComposite(Composite parent) {
  parent.setLayout(new GridLayout(1, false));

  DataBindingContext dbc = new DataBindingContext();
  
  CustomWidget widget = new CustomWidget(parent);
  
  // Create the property and then observe the widget afterwards
  CustomWidgetProperty customWidgetProperty = new CustomWidgetProperty();
  ISWTObservableValue customWidgetObservableValue = customWidgetProperty.observe(widget);
  
  Todo todo = //...
  IObservableValue todoSummaryObservable = PojoProperties.value("summary").observe(todo);
  
  dbc.bindValue(customWidgetObservableValue, todoSummaryObservable);
} 

8.4. Delegates for common properties of different objects

Delegating properties act a bit like a factory for IProperty objects, where the IProperty is not created directly, but at the moment, when the observe method is called. For instance a DelegatingValueProperty creates a IValueProperty according to the object, which should be observed.

So you can decision, which IValueProperty is used, is made at the moment, when the observe method is actually called.

Imagine, we want such a factory for the IValueProperty classes we created in the former sections. Therefore we can derive from DelegatingValueProperty.

public class VogellaDelegatingValueProperty extends DelegatingValueProperty {

  public VogellaDelegatingValueProperty() {
    this(null);
  }

  public VogellaDelegatingValueProperty(Object valueType) {
    super(valueType);
  }

  @Override
  protected IValueProperty doGetDelegate(Object source) {
    // return appropriate IValueProperty according to the given source,
    // which is passed, when the observe method is called
    if(source instanceof CustomWidget) {
      return new CustomWidgetProperty();
    }else if (source instanceof DateTime) {
      return new DateTimeSelectionProperty();
    }
    
    return null;
  }
} 

In order to have a factory like the WidgetProperties class, we could create a VogellaProperties class.

package com.vogella.operationhistory.databinding;

import org.eclipse.core.databinding.property.value.IValueProperty;

public class VogellaProperties {

  private VogellaProperties() {
  }

  public static IValueProperty vogellaProperty() {
    // return a vogella property, which can handle different vogella
    // properties
    return new VogellaDelegatingValueProperty();
  }
} 

You might want to have a look at the WidgetProperties, which contains several methods with more sense for those delegates.

Here is the code how to use the factory for "vogella" properties and some samples, which fit to the intention of the delegates.

public class VogellaPropertiesPart {

  @PostConstruct
  public void createPartControl(Composite parent) {
    CustomWidget customWidget = new CustomWidget(parent);
    
    // Observable for the CustomWidget by using the delegate
    IObservableValue customWidgetObservable = VogellaProperties.vogellaProperty().observe(customWidget);
    
    DateTime dateTime = new DateTime(parent, SWT.DROP_DOWN);
    // Observable for the DateTime by using the delegate
    IObservableValue dateTimeJava8Observable = VogellaProperties.vogellaProperty().observe(dateTime);
    
    // ### Real common properties for different kinds of objects ###
    
    Button button = new Button(parent, SWT.CHECK);
    CCombo combo = new CCombo(parent, SWT.READ_ONLY);
    
    // see WidgetSelectionProperty for more details
    ISWTObservableValue oldDateTimeObservable = WidgetProperties.selection().observe(dateTime);
    ISWTObservableValue buttonSelectionObservable = WidgetProperties.selection().observe(button);
    ISWTObservableValue comboSelectionObservable = WidgetProperties.selection().observe(combo);
    
    // and much more
  }
} 

9. Example: DateTime Widget and Java 8 time API

With Java 8, you have a redesigned Data and Time API. JFace data binding does currently not support not based on Java 8 therefore this Java 8 API cannot be used out of the box with JFace data binding.

To use it, you have to implement your custom WidgetValueProperty.

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.ChronoField;
import java.time.temporal.TemporalAccessor;
import java.util.Calendar;
import java.util.Date;

import org.eclipse.jface.databinding.swt.WidgetValueProperty;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.DateTime;

public class DateTimeSelectionProperty extends WidgetValueProperty {

  // this constant is used to correct the differences concerning the month counting
  private static final int MONTH_MAPPING_VALUE = 1;

  public DateTimeSelectionProperty() {
    super(SWT.Selection);
  }

  @Override
  public Object getValueType() {
    // use TemporalAccessor here, because it is the common interface for
    // LocalDate, LocalTime and LocalDateTime.
    return TemporalAccessor.class;
  }

  @Override
  protected Object doGetValue(Object source) {
    DateTime dateTime = (DateTime) source;

    // create LocalTime instance, if SWT.TIME is used,...
    if ((dateTime.getStyle() & SWT.TIME) != 0) {
      return LocalTime.of(dateTime.getHours(), dateTime.getMinutes(),
          dateTime.getSeconds());
    }

    // ... otherwise LocalDate
    return LocalDate.of(dateTime.getYear(), dateTime.getMonth()
        + MONTH_MAPPING_VALUE, dateTime.getDay());
  }

  @Override
  protected void doSetValue(Object source, Object value) {
    DateTime dateTime = (DateTime) source;

    if (value == null)
      throw new IllegalArgumentException("Cannot set null selection on DateTime"); 

    TemporalAccessor temporalAccessor = getTemporalAccessor(value);

    if (temporalAccessor == null)
      throw new IllegalArgumentException("Cannot find TemporalAccessor for the given value"); 

    // set only hours, minutes and seconds in case the SWT.TIME flag is
    // set,...
    if ((dateTime.getStyle() & SWT.TIME) != 0) {
      dateTime.setTime(temporalAccessor.get(ChronoField.HOUR_OF_DAY),
          temporalAccessor.get(ChronoField.MINUTE_OF_HOUR),
          temporalAccessor.get(ChronoField.SECOND_OF_MINUTE));
    } else {
      // ... otherwise set year, month and day.
      dateTime.setDate(temporalAccessor.get(ChronoField.YEAR),
          temporalAccessor.get(ChronoField.MONTH_OF_YEAR)
              - MONTH_MAPPING_VALUE,
          temporalAccessor.get(ChronoField.DAY_OF_MONTH));
    }
  }

  // get TemporalAccessor from a Date, Calendar or TemporalAccessor object
  private TemporalAccessor getTemporalAccessor(Object value) {
    TemporalAccessor temporalAccessor = null;

    if (value instanceof Date) {
      temporalAccessor = LocalDateTime.from(((Date) value).toInstant());
    } else if (value instanceof TemporalAccessor) {
      temporalAccessor = (TemporalAccessor) value;
    } else if (value instanceof Calendar) {
      temporalAccessor = LocalDateTime.from(((Calendar) value)
          .toInstant());
    }
    return temporalAccessor;
  }

} 

Here we implement a binding between the DateTime SWT widget and a TemporalAccessor, which for instance can be a java.time.LocalDate or java.time.LocalTime.

The usage of the DateTimeSelectionProperty is demonstrated by the following listing.

@PostConstruct
public void createComposite(Composite parent) {
  parent.setLayout(new GridLayout(1, false));

  DataBindingContext dbc = new DataBindingContext();

  DateTime dateTime = new DateTime(parent, SWT.DROP_DOWN);

  // use the previously created DateTimeSelectionProperty to be able to
  // create a binding with a Java 8 TemporalAccessor
  DateTimeSelectionProperty dateTimeSelectionProperty = new DateTimeSelectionProperty();
  dateTimeObservableValue = dateTimeSelectionProperty.observe(dateTime);

  // create and initialize a placeholder observable, which can be changed
  // later on
  writableDateTimeModel = new WritableValue();
  writableDateTimeModel.setValue(LocalDate.now());

  // bind DateTime widget to a Java 8 TemporalAccessor observable
  dbc.bindValue(dateTimeObservableValue, writableDateTimeModel);

  Label label = new Label(parent, SWT.BORDER);
  GridDataFactory.fillDefaults().grab(true, false).applyTo(label);

  ISWTObservableValue dateTimeLabelObservable = WidgetProperties.text().observe(label);

  // bind a Label to the DateTime Widget, in order to see the changes,
  // which are made in the DateTime widget
  dbc.bindValue(dateTimeLabelObservable, dateTimeObservableValue);

  tableViewer = new TableViewer(parent);
  tableViewer.getTable().setLayoutData(new GridData(GridData.FILL_BOTH));

  tableViewer.add("27.04.1986 00:30:20");
  tableViewer.add("23.06.1976 03:15:30");
  tableViewer.add("27.06.1980 12:05:40");
  tableViewer.add("26.03.2015 22:25:50");
  tableViewer.add("06.12.2015 20:00:10");

  final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm:ss");

  // databinding could be used here similar to the following code:
  // dbc.bindValue(ViewerProperties.singleSelection().
  // observe(tableViewer), 
  // writableDateTimeModel, 
  // stringToLocalDateTimeUpdateStrategy, 
  // LocalDateTimeToStringUpdateStrategy)
  
  // the following used an ISelectionChangedListener instead of databinding as this 
  // results in shorter code
  tableViewer.addSelectionChangedListener(new ISelectionChangedListener() {

    @Override
    public void selectionChanged(SelectionChangedEvent event) {
      ISelection selection = event.getSelection();
      if (selection instanceof IStructuredSelection) {
        String string = (String) ((IStructuredSelection) selection).getFirstElement();

        // parse string from table with the DateTimeFormatter from above
        LocalDateTime localDateTime = LocalDateTime.parse(string, dateTimeFormatter);
        // set the selected localDateTime to the WritableValue,
        // which is bound to the DateTime widget
        writableDateTimeModel.setValue(localDateTime);
      }
    }
  });
} 

In this sample we bind the DateTime widget to a WritableValue, which contains a TemporalAccessor instance and this WritableValue is changed by selecting a date in the TableViewer. The dateTimeObservableValue is also bound to a label, in order to see the changes by manually manipulating to the DateTime widget.

The result of manually manipulating to the DateTime widget looks like this:

If you select a date in the TableViewer, this updates the WritableValue and therefore also the DateTime widget and also the Label, because all these are bound together.

10. Prerequisites for this tutorial

This article assumes what you have basic understanding of development for the Eclipse platform. Please see Eclipse RCP Tutorial or Eclipse Plugin Tutorial.

For the databinding with JFace Viewers you should already be familiar with the concept of JFace Viewers.

For an introduction on JFace Viewers please see JFace Overview, JFace Tables and JFace Trees

11. Data Binding with SWT controls

11.1. First example

Create a new Eclipse RCP project "de.vogella.databinding.example" using the template "RCP application with a View".

Create the de.vogella.databinding.person.model package and the following model classes.

package de.vogella.databinding.example.model;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;

public class Person implements PropertyChangeListener {
  private String firstName;
  private String lastName;
  private boolean married;
  private String gender;
  private Integer age;
  private Address address;
  private PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);

  public Person() {
  }

  public void addPropertyChangeListener(String propertyName,
      PropertyChangeListener listener) {
    propertyChangeSupport.addPropertyChangeListener(propertyName, listener);
  }

  public void removePropertyChangeListener(PropertyChangeListener listener) {
    propertyChangeSupport.removePropertyChangeListener(listener);
  }

  public String getFirstName() {
    return firstName;
  }

  public String getGender() {
    return gender;
  }

  public String getLastName() {
    return lastName;
  }

  public boolean isMarried() {
    return married;
  }

  public void setFirstName(String firstName) {
    propertyChangeSupport.firePropertyChange("firstName", this.firstName,
        this.firstName = firstName);
  }

  public void setGender(String gender) {
    propertyChangeSupport.firePropertyChange("gender", this.gender,
        this.gender = gender);
  }

  public void setLastName(String lastName) {
    propertyChangeSupport.firePropertyChange("lastName", this.lastName,
        this.lastName = lastName);
  }

  public void setMarried(boolean isMarried) {
    propertyChangeSupport.firePropertyChange("married", this.married,
        this.married = isMarried);
  }

  public Integer getAge() {
    return age;
  }

  public void setAge(Integer age) {
    propertyChangeSupport.firePropertyChange("age", this.age,
        this.age = age);
  }

  public Address getAddress() {
    return address;
  }

  public void setAddress(Address address) {
    address.addPropertyChangeListener("country", this);
    propertyChangeSupport.firePropertyChange("address", this.address,
        this.address = address);
  }

  @Override
  public String toString() {
    return firstName + " " + lastName;
  }

  @Override
  public void propertyChange(PropertyChangeEvent event) {
    propertyChangeSupport.firePropertyChange("address", null, address);
  }

} 

package de.vogella.databinding.example.model;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;

public class Address {
  
  public static final String FIELD_STREET = "street";
  public static final String FIELD_NUMBER = "number";
  public static final String FIELD_POSTALCODE = "postalCode";
  public static final String FIELD_CITY = "city";
  public static final String FIELD_COUNTRY = "country";

  private String street;
  private String number;
  private String postalCode;
  private String city;
  private String country;
  
  private PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);

  public void addPropertyChangeListener(String propertyName,
      PropertyChangeListener listener) {
    propertyChangeSupport.addPropertyChangeListener(propertyName, listener);
  }

  public void removePropertyChangeListener(PropertyChangeListener listener) {
    propertyChangeSupport.removePropertyChangeListener(listener);
  }

  public Address() {
  }

  public Address(String postalCode, String city, String country) {
    this.postalCode = postalCode;
    this.city = city;
    this.country = country;
  }

  public String getStreet() {
    return street;
  }

  public void setStreet(String street) {
    propertyChangeSupport.firePropertyChange(FIELD_STREET, this.street,
        this.street = street);
  }

  public String getNumber() {
    return number;
  }

  public void setNumber(String number) {
    propertyChangeSupport.firePropertyChange(FIELD_NUMBER, this.number,
        this.number = number);
  }

  public String getPostalCode() {
    return postalCode;
  }

  public void setPostalCode(String postalCode) {
    propertyChangeSupport.firePropertyChange(FIELD_POSTALCODE, this.postalCode,
        this.postalCode = postalCode);
  }

  public String getCity() {
    return city;
  }

  public void setCity(String city) {
    propertyChangeSupport.firePropertyChange(FIELD_CITY, this.city,
        this.city = city);
  }

  public String getCountry() {
    return country;
  }

  public void setCountry(String country) {
    propertyChangeSupport.firePropertyChange(FIELD_COUNTRY, this.country,
        this.country = country);
  }

  public String toString() {
    String s = "";
    s += street != null ? street + " " : "";
    s += number != null ? number + " " : "";
    s += postalCode != null ? postalCode + " " : "";
    s += city != null ? city + " " : "";
    s += country != null ? country + " " : "";

    return s;
  }

} 

Add the JFace Data Binding plug-ins as dependency to your plug-in.

Change the View class to the following.

package de.vogella.databinding.example;

import org.eclipse.core.databinding.Binding;
import org.eclipse.core.databinding.DataBindingContext;
import org.eclipse.core.databinding.UpdateValueStrategy;
import org.eclipse.core.databinding.beans.BeanProperties;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.core.databinding.validation.IValidator;
import org.eclipse.core.databinding.validation.ValidationStatus;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.jface.databinding.fieldassist.ControlDecorationSupport;
import org.eclipse.jface.databinding.swt.WidgetProperties;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.part.ViewPart;

import de.vogella.databinding.example.model.Address;
import de.vogella.databinding.example.model.Person;

public class View extends ViewPart {
  public static final String ID = "de.vogella.databinding.person.swt.View";
  private Person person;

  private Text firstName;
  private Text ageText;
  private Button marriedButton;
  private Combo genderCombo;
  private Text countryText;

  @Override
  public void createPartControl(Composite parent) {

    person = createPerson();
    // Lets put thing to order
    GridLayout layout = new GridLayout(2, false);
    layout.marginRight = 5;
    parent.setLayout(layout);

    Label firstLabel = new Label(parent, SWT.NONE);
    firstLabel.setText("Firstname: ");
    firstName = new Text(parent, SWT.BORDER);

    GridData gridData = new GridData();
    gridData.horizontalAlignment = SWT.FILL;
    gridData.grabExcessHorizontalSpace = true;
    firstName.setLayoutData(gridData);

    Label ageLabel = new Label(parent, SWT.NONE);
    ageLabel.setText("Age: ");
    ageText = new Text(parent, SWT.BORDER);

    gridData = new GridData();
    gridData.horizontalAlignment = SWT.FILL;
    gridData.grabExcessHorizontalSpace = true;
    ageText.setLayoutData(gridData);

    Label marriedLabel = new Label(parent, SWT.NONE);
    marriedLabel.setText("Married: ");
    marriedButton = new Button(parent, SWT.CHECK);

    Label genderLabel = new Label(parent, SWT.NONE);
    genderLabel.setText("Gender: ");
    genderCombo = new Combo(parent, SWT.NONE);
    genderCombo.add("Male");
    genderCombo.add("Female");

    Label countryLabel = new Label(parent, SWT.NONE);
    countryLabel.setText("Country");
    countryText = new Text(parent, SWT.BORDER);

    Button button1 = new Button(parent, SWT.PUSH);
    button1.setText("Write model");
    button1.addSelectionListener(new SelectionAdapter() {

      @Override
      public void widgetSelected(SelectionEvent e) {
        System.out.println("Firstname: " + person.getFirstName());
        System.out.println("Age " + person.getAge());
        System.out.println("Married: " + person.isMarried());
        System.out.println("Gender: " + person.getGender());
        System.out.println("Country: "
            + person.getAddress().getCountry());
      }
    });

    Button button2 = new Button(parent, SWT.PUSH);
    button2.setText("Change model");
    button2.addSelectionListener(new SelectionAdapter() {
      @Override
      public void widgetSelected(SelectionEvent e) {
        person.setFirstName("Lars");
        person.setAge(person.getAge() + 1);
        person.setMarried(!person.isMarried());
        if (person.getGender().equals("Male")) {
          person.setGender("Male");
        } else {
          person.setGender("Female");
        }
        if (person.getAddress().getCountry().equals("Deutschland")) {
          person.getAddress().setCountry("USA");
        } else {
          person.getAddress().setCountry("Deutschland");
        }
      }
    });

    // now lets do the binding
    bindValues();
  }

  private Person createPerson() {
    Person person = new Person();
    Address address = new Address();
    address.setCountry("Deutschland");
    person.setAddress(address);
    person.setFirstName("John");
    person.setLastName("Doo");
    person.setGender("Male");
    person.setAge(12);
    person.setMarried(true);
    return person;
  }

  @Override
  public void setFocus() {
  }

  private void bindValues() {
    // The DataBindingContext object will manage the databindings
    // Lets bind it
    DataBindingContext ctx = new DataBindingContext();
    IObservableValue widgetValue = WidgetProperties.text(SWT.Modify)
        .observe(firstName);
    IObservableValue modelValue = BeanProperties.value(Person.class,
        "firstName").observe(person);
    ctx.bindValue(widgetValue, modelValue);

    // Bind the age including a validator
    widgetValue = WidgetProperties.text(SWT.Modify).observe(ageText);
    modelValue = BeanProperties.value(Person.class, "age").observe(person);
    // add an validator so that age can only be a number
    IValidator validator = new IValidator() {
      @Override
      public IStatus validate(Object value) {
        if (value instanceof Integer) {
          String s = String.valueOf(value);
          if (s.matches("\\d*")) {
            return ValidationStatus.ok();
          }
        }
        return ValidationStatus.error("Not a number");
      }
    };

    UpdateValueStrategy strategy = new UpdateValueStrategy();
    strategy.setBeforeSetValidator(validator);

    Binding bindValue = ctx.bindValue(widgetValue, modelValue, strategy,
        null);
    // add some decorations
    ControlDecorationSupport.create(bindValue, SWT.TOP | SWT.LEFT);

    widgetValue = WidgetProperties.selection().observe(marriedButton);
    modelValue = BeanProperties.value(Person.class, "married").observe(person);
    ctx.bindValue(widgetValue, modelValue);

    widgetValue = WidgetProperties.selection().observe(genderCombo);
    modelValue = BeanProperties.value("gender").observe(person);

    ctx.bindValue(widgetValue, modelValue);

    // address field is bound to the Ui
    widgetValue = WidgetProperties.text(SWT.Modify).observe(countryText);

    modelValue = BeanProperties.value(Person.class, "address.country")
        .observe(person);
    ctx.bindValue(widgetValue, modelValue);

  }
} 

Run the example and test it. Each time you change the UI element then model changes automatically. If you change the model then the UI will also update. Try to input something else then a number iN the age field you will get an error symbol in the UI and if the mouse hovers over the symbol you see the error message.

11.2. More Customer Validations and ControlDecoration

The following extends the example with the usage of Validators and Decorators.

In this example the Validators ensures that the firstName has at least 2 characters. A new label displays the validation status via a Decorator.

Create the following StringLongerThenTwo class.

package de.vogella.databinding.example.validators;

import org.eclipse.core.databinding.validation.IValidator;
import org.eclipse.core.databinding.validation.ValidationStatus;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;

public class StringLongerThenTwo implements IValidator {

  @Override
  public IStatus validate(Object value) {
    if (value instanceof String) {
      String s = (String) value;
      // We check if the string is longer then 2 signs
      if (s.length() > 2) {
        return Status.OK_STATUS;
      } else {
        return ValidationStatus
            .error("Name must be longer two letters");
      }
    } else {
      throw new RuntimeException("Not supposed to be called for non-strings.");
    }
  }
} 

The following shows the new code for View.java.

package de.vogella.databinding.example;

import org.eclipse.core.databinding.AggregateValidationStatus;
import org.eclipse.core.databinding.Binding;
import org.eclipse.core.databinding.DataBindingContext;
import org.eclipse.core.databinding.UpdateValueStrategy;
import org.eclipse.core.databinding.beans.BeanProperties;
import org.eclipse.core.databinding.beans.BeansObservables;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.core.databinding.validation.IValidator;
import org.eclipse.core.databinding.validation.ValidationStatus;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.jface.databinding.fieldassist.ControlDecorationSupport;
import org.eclipse.jface.databinding.swt.WidgetProperties;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.part.ViewPart;

import de.vogella.databinding.example.model.Address;
import de.vogella.databinding.example.model.Person;
import de.vogella.databinding.example.validators.StringLongerThenTwo;

public class View extends ViewPart {
  public View() {
  }

  public static final String ID = "de.vogella.databinding.person.swt.View";
  private Person person;

  private Text firstName;
  private Text ageText;
  private Button marriedButton;
  private Combo genderCombo;
  private Text countryText;
  private Label errorLabel;

  @Override
  public void createPartControl(Composite parent) {

    person = createPerson();
    GridLayout layout = new GridLayout(2, false);
    layout.marginRight = 5;
    parent.setLayout(layout);

    Label firstLabel = new Label(parent, SWT.NONE);
    firstLabel.setText("Firstname: ");
    firstName = new Text(parent, SWT.BORDER);

    GridData gridData = new GridData();
    gridData.horizontalAlignment = SWT.FILL;
    gridData.grabExcessHorizontalSpace = true;
    firstName.setLayoutData(gridData);

    Label ageLabel = new Label(parent, SWT.NONE);
    ageLabel.setText("Age: ");
    ageText = new Text(parent, SWT.BORDER);

    gridData = new GridData();
    gridData.horizontalAlignment = SWT.FILL;
    gridData.grabExcessHorizontalSpace = true;
    ageText.setLayoutData(gridData);

    Label marriedLabel = new Label(parent, SWT.NONE);
    marriedLabel.setText("Married: ");
    marriedButton = new Button(parent, SWT.CHECK);

    Label genderLabel = new Label(parent, SWT.NONE);
    genderLabel.setText("Gender: ");
    genderCombo = new Combo(parent, SWT.NONE);
    genderCombo.add("Male");
    genderCombo.add("Female");

    Label countryLabel = new Label(parent, SWT.NONE);
    countryLabel.setText("Country");
    countryText = new Text(parent, SWT.BORDER);

    Button button1 = new Button(parent, SWT.PUSH);
    button1.setText("Write model");
    button1.addSelectionListener(new SelectionAdapter() {

      @Override
      public void widgetSelected(SelectionEvent e) {
        System.out.println("Firstname: " + person.getFirstName());
        System.out.println("Age " + person.getAge());
        System.out.println("Married: " + person.isMarried());
        System.out.println("Gender: " + person.getGender());
        System.out.println("Country: "
            + person.getAddress().getCountry());
      }
    });

    Button button2 = new Button(parent, SWT.PUSH);
    button2.setText("Change model");
    button2.addSelectionListener(new SelectionAdapter() {
      @Override
      public void widgetSelected(SelectionEvent e) {
        person.setFirstName("Lars");
        person.setAge(person.getAge() + 1);
        person.setMarried(!person.isMarried());
        if (person.getGender().equals("Male")) {

        } else {
          person.setGender("Male");
        }
        if (person.getAddress().getCountry().equals("Deutschland")) {
          person.getAddress().setCountry("USA");
        } else {
          person.getAddress().setCountry("Deutschland");
        }
      }
    });

    // this label displays all errors of all bindings
    Label descAllLabel = new Label(parent, SWT.NONE);
    descAllLabel.setText("All Validation Problems:");
    errorLabel = new Label(parent, SWT.NONE);
    gridData = new GridData();
    gridData.horizontalAlignment = SWT.FILL;
    gridData.grabExcessHorizontalSpace = true;
    gridData.horizontalAlignment = GridData.FILL;
    gridData.horizontalSpan = 1;
    errorLabel.setLayoutData(gridData);

    // perform the binding
    bindValues();
  }

  private Person createPerson() {
    Person person = new Person();
    Address address = new Address();
    address.setCountry("Deutschland");
    person.setAddress(address);
    person.setFirstName("John");
    person.setLastName("Doo");
    person.setGender("Male");
    person.setAge(12);
    person.setMarried(true);
    return person;
  }

  @Override
  public void setFocus() {
  }

  private void bindValues() {
    // the DataBindingContext object will manage the databindings
    DataBindingContext ctx = new DataBindingContext();
    IObservableValue widgetValue = WidgetProperties.text(SWT.Modify)
        .observe(firstName);
    IObservableValue modelValue = BeanProperties.value(Person.class,
        "firstName").observe(person);
    // define the UpdateValueStrategy
    UpdateValueStrategy update = new UpdateValueStrategy();
    update.setAfterConvertValidator(new StringLongerThenTwo());
    ctx.bindValue(widgetValue, modelValue, update, null);

    // bind the age including a validator
    widgetValue = WidgetProperties.text(SWT.Modify).observe(ageText);
    modelValue = BeanProperties.value(Person.class, "age").observe(person);
    // add an validator so that age can only be a number
    IValidator validator = new IValidator() {
      @Override
      public IStatus validate(Object value) {
        if (value instanceof Integer) {
          String s = String.valueOf(value);
          if (s.matches("\\d*")) {
            return ValidationStatus.ok();
          }
        }
        return ValidationStatus.error("Not a number");
      }
    };

    UpdateValueStrategy strategy = new UpdateValueStrategy();
    strategy.setBeforeSetValidator(validator);

    Binding bindValue = ctx.bindValue(widgetValue, modelValue, strategy,
        null);
    // add some decorations
    ControlDecorationSupport.create(bindValue, SWT.TOP | SWT.LEFT);

    widgetValue = WidgetProperties.selection().observe(marriedButton);
    modelValue = BeanProperties.value(Person.class, "married").observe(person);
    ctx.bindValue(widgetValue, modelValue);

    widgetValue = WidgetProperties.selection().observe(genderCombo);
    modelValue = BeanProperties.value("gender").observe(person)

    ctx.bindValue(widgetValue, modelValue);

    widgetValue = WidgetProperties.text(SWT.Modify).observe(countryText);

    modelValue = BeanProperties.value(Person.class, "address.country")
        .observe(person);
    ctx.bindValue(widgetValue, modelValue);

    // listen to all errors via this binding
    // we do not need to listen to any SWT event on this label as it never
    // changes independently
    final IObservableValue errorObservable = WidgetProperties.text()
        .observe(errorLabel);
    // this one listenes to all changes
    ctx.bindValue(errorObservable,
        new AggregateValidationStatus(ctx.getBindings(),
            AggregateValidationStatus.MAX_SEVERITY), null, null);

  }
} 

12. Tutorial: WritableValue

Create a new View in your "de.vogella.databinding.example" plug-in with the following class. Via the buttons you can change the details of the WritableObject.

package de.vogella.databinding.example;

import org.eclipse.core.databinding.DataBindingContext;
import org.eclipse.core.databinding.beans.BeanProperties;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.core.databinding.observable.value.WritableValue;
import org.eclipse.jface.databinding.swt.WidgetProperties;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Text;

import de.vogella.databinding.example.model.Person;

public class ViewWritableValue extends View {
  private WritableValue value;

  @Override
  public void createPartControl(Composite parent) {
    value = new WritableValue();
    parent.setLayout(new GridLayout(3, false));
    GridData gd = new GridData();
    gd.grabExcessHorizontalSpace = true;
    Text text = new Text(parent, SWT.BORDER);
    Button button = new Button(parent, SWT.PUSH);
    button.setText("New Person");
    button.addSelectionListener(new SelectionAdapter() {
      @Override
      public void widgetSelected(SelectionEvent e) {
        Person p = new Person();
        p.setFirstName("Lars");
        value.setValue(p);
      }
    });

    button = new Button(parent, SWT.PUSH);
    button.setText("Another Person");
    button.addSelectionListener(new SelectionAdapter() {
      @Override
      public void widgetSelected(SelectionEvent e) {
        Person p = new Person();
        p.setFirstName("Jack");
        value.setValue(p);
      }
    });
    DataBindingContext ctx = new DataBindingContext();
    IObservableValue target = WidgetProperties.text(SWT.Modify).observe(text);
    IObservableValue model = BeanProperties.value("firstName")
        .observeDetail(value);
    ctx.bindValue(target, model);
  }

  @Override
  public void setFocus() {
  }
} 

13. Tutorial: Data Binding for a JFace Viewer

Create a new Eclipse RCP project "de.vogella.databinding.viewer" using the "RCP Application with a view" template. Add the databinding plug-ins as dependency to your plug-in project.

Create the de.vogella.databinding.viewer.model package and re-create the Person and Address class from the previous example in this book in this package.

Create the following MyModel class to get some example data.

package de.vogella.databinding.viewer.model;

import java.util.ArrayList;
import java.util.List;

public class MyModel {
  public static List<Person> getPersons() {
    List<Person> persons = new ArrayList<Person>();
    Person p = new Person();
    p.setFirstName("Joe");
    p.setLastName("Darcey");
    persons.add(p);
    p = new Person();
    p.setFirstName("Jim");
    p.setLastName("Knopf");
    persons.add(p);
    p = new Person();
    p.setFirstName("Jim");
    p.setLastName("Bean");
    persons.add(p);
    return persons;
  }
} 

Create a new view called ViewTable add it to your RCP application. Change ViewTable.java to the following.

package de.vogella.databinding.viewer;

import org.eclipse.core.databinding.beans.BeanProperties;
import org.eclipse.core.databinding.observable.list.WritableList;
import org.eclipse.jface.databinding.viewers.ViewerSupport;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TableViewerColumn;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;

import de.vogella.databinding.viewer.model.MyModel;
import de.vogella.databinding.viewer.model.Person;

public class ViewTable extends View {
  private TableViewer viewer;
  private WritableList input;

  @Override
  public void createPartControl(Composite parent) {
    parent.setLayout(new GridLayout(1, false));
    GridData gd = new GridData();
    gd.grabExcessHorizontalSpace = true;

    // Define the viewer
    viewer = new TableViewer(parent);
    viewer.getControl().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
    TableViewerColumn column = new TableViewerColumn(viewer, SWT.NONE);
    column.getColumn().setWidth(100);
    column.getColumn().setText("First Name");
    column = new TableViewerColumn(viewer, SWT.NONE);
    column.getColumn().setWidth(100);
    column.getColumn().setText("Last Name");
    column = new TableViewerColumn(viewer, SWT.NONE);
    column.getColumn().setWidth(100);
    column.getColumn().setText("Married");
    viewer.getTable().setHeaderVisible(true);

    // now lets bind the values
    // No extra label provider / content provider / setInput required
    input = new WritableList(MyModel.getPersons(), Person.class);
    ViewerSupport.bind(viewer,
        input,
        BeanProperties.values(new String[] { "firstName", "lastName",
            "married" }));

    // The following buttons are there to test the binding
    Button delete = new Button(parent, SWT.PUSH);
    delete.setText("Delete");
    delete.addSelectionListener(new SelectionAdapter() {
      @Override
      public void widgetSelected(SelectionEvent e) {
        if (!viewer.getSelection().isEmpty()) {
          IStructuredSelection selection = (IStructuredSelection) viewer
              .getSelection();
          Person p = (Person) selection.getFirstElement();
          input.remove(p);
        }
      }
    });

    Button add = new Button(parent, SWT.PUSH);
    add.setText("Add");
    add.addSelectionListener(new SelectionAdapter() {
      @Override
      public void widgetSelected(SelectionEvent e) {
        Person p = new Person();
        p.setFirstName("Test1");
        p.setLastName("Test2");
        input.add(p);
      }
    });
    Button change = new Button(parent, SWT.PUSH);
    change.setText("Switch First / Lastname");
    change.addSelectionListener(new SelectionAdapter() {
      @Override
      public void widgetSelected(SelectionEvent e) {
        if (!viewer.getSelection().isEmpty()) {
          IStructuredSelection selection = (IStructuredSelection) viewer
              .getSelection();
          Person p = (Person) selection.getFirstElement();
          String temp = p.getLastName();
          p.setLastName(p.getFirstName());
          p.setFirstName(temp);
        }
      }
    });
    
  }

  @Override
  public void setFocus() {
    viewer.getControl().setFocus();
  }
} 

In this example the user interface is updated if you delete and element or add an element to the collection. Run this example and test it.

14. Using ObservableListContentProvider and ObservableMapLabelProvider

If you use WritableList and ObservableListContentProvider you only listens to the changes in the list. You can use ObservableMapLabelProvider to listen to changes of the individual objects.

Change the View.java to the following.

package de.vogella.databinding.viewer;

import java.util.List;

import org.eclipse.core.databinding.beans.BeanProperties;
import org.eclipse.core.databinding.observable.list.WritableList;
import org.eclipse.core.databinding.observable.map.IObservableMap;
import org.eclipse.core.databinding.observable.set.IObservableSet;
import org.eclipse.jface.databinding.viewers.ObservableListContentProvider;
import org.eclipse.jface.databinding.viewers.ObservableMapLabelProvider;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ListViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.part.ViewPart;

import de.vogella.databinding.viewer.model.MyModel;
import de.vogella.databinding.viewer.model.Person;

// direct usage of ObservableListContentProvider
// listens to the labels changes too via ObservableMapLabelProvider

public class View extends ViewPart {
  private ListViewer viewer;
  private WritableList input;

  @Override
  public void createPartControl(Composite parent) {
    parent.setLayout(new GridLayout(1, false));
    GridData gd = new GridData();
    gd.grabExcessHorizontalSpace = true;

    // define the viewer
    viewer = new ListViewer(parent);
    viewer.getControl().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
    ObservableListContentProvider contentProvider = new ObservableListContentProvider();
    viewer.setContentProvider(contentProvider);

    // create the label provider including monitoring 
    // of label changes
    IObservableSet knownElements = contentProvider.getKnownElements();
    final IObservableMap firstNames = BeanProperties.value(Person.class,
        "firstName").observeDetail(knownElements);
    final IObservableMap lastNames = BeanProperties.value(Person.class,
        "lastName").observeDetail(knownElements);

    IObservableMap[] labelMaps = { firstNames, lastNames };

    ILabelProvider labelProvider = new ObservableMapLabelProvider(labelMaps) {
      public String getText(Object element) {
        return firstNames.get(element) + " " + lastNames.get(element);
      }
    };

    viewer.setLabelProvider(labelProvider);

    // create sample data
    List<Person> persons = MyModel.getPersons();
    input = new WritableList(persons, Person.class);
    // set the writeableList as input for the viewer
    viewer.setInput(input);

    Button delete = new Button(parent, SWT.PUSH);
    delete.setText("Delete");
    delete.addSelectionListener(new SelectionAdapter() {
      @Override
      public void widgetSelected(SelectionEvent e) {
        deletePerson();
      }

    });

    Button add = new Button(parent, SWT.PUSH);
    add.setText("Add");
    add.addSelectionListener(new SelectionAdapter() {
      @Override
      public void widgetSelected(SelectionEvent e) {
        addPerson();
      }

    });
    Button change = new Button(parent, SWT.PUSH);
    change.setText("Switch First / Lastname");
    change.addSelectionListener(new SelectionAdapter() {
      @Override
      public void widgetSelected(SelectionEvent e) {
        switchFirstLastName();
      }

    });
  }

  public void switchFirstLastName() {
    if (!viewer.getSelection().isEmpty()) {
      IStructuredSelection selection = (IStructuredSelection) viewer
          .getSelection();
      Person p = (Person) selection.getFirstElement();
      String temp = p.getLastName();
      p.setLastName(p.getFirstName());
      p.setFirstName(temp);
    }
  }

  public void deletePerson() {
    if (!viewer.getSelection().isEmpty()) {
      IStructuredSelection selection = (IStructuredSelection) viewer
          .getSelection();
      Person p = (Person) selection.getFirstElement();
      input.remove(p);
    }
  }

  public void addPerson() {
    Person p = new Person();
    p.setFirstName("Test1");
    p.setLastName("Test2");
    input.add(p);
  }

  @Override
  public void setFocus() {
    viewer.getControl().setFocus();
  }

} 

15. About this website

16. Links and Literature

16.2. vogella GmbH training and consulting support

TRAINING SERVICE & SUPPORT
The vogella company provides comprehensive training and education services from experts in the areas of Eclipse RCP, Android, Git, Java, Gradle and Spring. We offer both public and inhouse training. Whichever course you decide to take, you are guaranteed to experience what many before you refer to as “The best IT class I have ever attended”. The vogella company offers expert consulting services, development support and coaching. Our customers range from Fortune 100 corporations to individual developers.