vogella training Android Training Eclipse Training

JFace Data Binding - Tutorial

Lars Vogel

Version 2.6

18.05.2012

Revision History
Revision 0.1 23.05.2008 Lars
Vogel
Created
Revision 0.2 - 2.6 25.12.2008 - 18.05.2012 Lars
Vogel
bugfixes and enhancements

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 3.7 (Indigo) and Java 1.6.


Table of Contents

1. Introduction
1.1. Overview
1.2. PropertyChangeSupport
1.3. Observables
1.4. DataBindingContext and Bindings
1.5. UpdateValueStrategy
1.6. Converter and Validators
1.7. ControlDecorators
1.8. WritableValues
1.9. Listening to all changes in the binding
2. Data Binding for JFace Viewer
2.1. Binding Viewers
2.2. Observing list details
2.3. ViewerSupport
2.4. Observing Viewer properties
3. JFace Data Binding Plug-ins
4. Prerequisites for this tutorial
5. Data Binding with SWT controls
5.1. First example
5.2. More Customer Validations and ControlDecoration
6. Tutorial: WritableValue
7. Tutorial: Data Binding for JFace Viewer
8. Tutorial: Using ObservableListContentProvider and ObservableMapLabelProvider
9. Thank you
10. Questions and Discussion
11. Links and Literature
11.1. Source Code
11.2. Eclipse DataBinding Resources
11.3. vogella Resources

1. Introduction

1.1. Overview

JFace Data Binding is a framework which 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 synchronizes changes of these properties. It allows to include validations and conversions into this synchronization process.

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.

JFace Data Binding is mainly used in Eclipse applications and Eclipse Plug-ins. The model are typical Java POJOs or Java Beans but other technologies are also supported, e.g. Eclipse EMF or GWT.

1.2.  PropertyChangeSupport

To be able to react to changes in an attribute of a Java objects, JFace Data Binding needs to be able to register itself as listener to the attribute. The SWT and JFace widgets support this.

JFace Data Binding can be used to observe attribute of a domain model. This requires that the domain model objects follow the Java Bean specification. This specification requires that the class implement the PropertyChangeSupport interface and propagate changes to registered listeners.

JFace Data Binding framework can register listeners on these Java Objects and gets notified if a change in the model happens.

1.3. Observables

The IObservableValue interface is used to observe properties of objects.

JFace Data Binding contains the Properties API as the recommended way of using the framework. The Properties API provides factories to create IObservableValue objects.

The main factories are PojoProperties, BeanProperties and WidgetProperties.

Table 1. Factories

Factory Description
PojoProperties Used to create IObservableValues for Java objects.
BeanProperties Used to create IObservableValue objects for Java Beans.
WidgetProperties Used to create IObservableValues for properties of SWT widgets.

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

				
IObservableValue myModel = PojoProperties.value("firstName").observe(person)
			

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

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

JFace Data Binding allows to observe arbitrary attributes. For widgets you typically observe the text property but you can observe also other values. For example, you could bind the enabled property to a boolean value of the data model.

You can also observe nested model properties, e.g. attributes of classes which are contained in another class. The following 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);
			

1.4. DataBindingContext and Bindings

The DataBindingContext class provides the functionality to bind IObservableValues.

Via the DataBindingContex.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 will be 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);

			

It creates as a result an object of type Binding.

1.5. UpdateValueStrategy

The bindValue() method allows to specify UpdateValueStrategy objects as third and forth parameter. These objects allow to control how and when the values are updated. If no UpdateValueStrategy is specified, defaults will be used.

1.6.  Converter and Validators

You can also add Validator and Converter objects to the UpdateValueStrategy.

Validator allows 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() {
	@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");
	}
};

// Create UpdateValueStratgy and assign
// to the binding
UpdateValueStrategy strategy = new UpdateValueStrategy();
strategy.setBeforeSetValidator(validator);

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

1.7. ControlDecorators

JFace Data Binding allows to use icon decorators in the user interface which reflect the status of the field validation. This allows the provide immediate feedback to the user.

				
// Assume that a validator is already set
Binding bindValue = ctx.bindValue(widgetValue, modelValue, strategy, null);

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

1.8.  WritableValues

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

You can exchange the reference object 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 WritableValue you use the observeDetails() 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
// Make the binding valid for this new object
value.setValue(p);

			

1.9. Listening to all changes in the binding

You can register a listener to all bindings of the DataBindingContext. Your listener will be called in case something is changed.

This can for example to be used to determine the status of an Part which behaves like an Editor. If something is changed, it will mark itself as dirty.

				

// Define your change listener
// dirtable holds the state for the changed status of the editor
IChangeListener listener = new IChangeListener() {
	@Override
	public void handleChange(ChangeEvent event) {
		dirtable.setDirty(true);
		}
};


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

2. Data Binding for JFace Viewer

2.1. Binding Viewers

JFace Data Binding provides functionality to bind the data of JFace Viewers, e.g. for TableViewers.

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

In the case that Data Binding observes a collection, its requires a ContentProvider which notifies it, once the data in the collection changes.

ObservableListContentProvider is a ContentProvider which requires a list implementing the IObservableCollection interface. The WritableList class allows to wrap another list and implements this interface.

The following snippet demonstrates the usage:

				
// Use ObservableListContentProvider
viewer.setContentProvider(new ObservableListContentProvider());
// Create sample data
List<Person> persons = createExampleData(persons);
// Wrap the input into a writable list
input = new WritableList(persons, Person.class);
// Set the writeableList as input for the viewer
viewer.setInput(input);
			

2.2. Observing list details

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

				
IObservableMap[] labelMaps = { firstNames, lastNames };

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

2.3. ViewerSupport

ViewerSupport simplifies the setup for JFace Viewers in case all columns should be displayed. It registers changes listener on the collection as well as on the individual elements.

ViewerSupport creates via its bind() the LabelProvider and ContentProvider automatically.

				

// MyModel.getPersons() gives a List of Person objects
// Writable list wraps them in an IObservableList

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

// The following will create and bind the data for the table based on this input
// No extra label provider / content provider / setInput required

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

2.4. Observing Viewer properties

The ViewerProperties class allows to create IObservableValues for properties of the viewer. For example you can track the current selection, e.g. which data object is currently selected.

To access fields in the selection you can use PojoObservables.observeDetailValue() or BeansObservables.observeDetailValue() which allow to observe a detail value of a IObservableValues 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 List<Todo>
final ComboViewer viewer = new ComboViewer(parent, SWT.DROP_DOWN);
viewer.setContentProvider(new ArrayContentProvider());
viewer.setLabelProvider(new LabelProvider() {
	public String getText(Object element) {
	  Todo todo = (Todo) element;
      return todo.getSummary();
	};
});
viewer.setInput(todos);

// 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);
// We want to get the summary attribute of the selection
IObservableValue detailValue = PojoObservables.observeDetailValue(
		selectedTodo, "summary", String.class);

dbc.bindValue(target, detailValue);
			

3. 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

4. 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

5. Data Binding with SWT controls

5.1. First example

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

Create the package "de.vogella.databinding.person.model" 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 {

	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("street", this.street,
				this.street = street);
	}

	public String getNumber() {
		return number;
	}

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

	public String getPostalCode() {
		return postalCode;
	}

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

	public String getCity() {
		return city;
	}

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

	public String getCountry() {
		return country;
	}

	public void setCountry(String country) {
		propertyChangeSupport.firePropertyChange("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 Databinding 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.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;

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")) {

				} else {
					person.setGender("Male");
				}
				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 = BeansObservables.observeValue(person, "gender");

		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.

5.2. More Customer Validations and ControlDecoration

Lets extend the usage of Validator s and Decorators with JFace Data Binding. We will ensure that the firstName have at least 2 characters. We will add a label to the UI which displays the Validation status.

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 coding 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();
		// 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")) {

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

		// This label will display 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);

		// 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);
		// Here we 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 = BeansObservables.observeValue(person, "gender");

		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);

		// We listen to all errors via this binding
		// We don't 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);

	}
}

			

6. 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() {
	}
}

		

7. Tutorial: Data Binding for 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 plugin project.

Create the package "de.vogella.databinding.viewer.model" and re-create the class "Person" and "Address" from the SWT databinding example in this package. Create the following class "MyModel" 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 "ViewTable" add 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 UI will be updated if you delete and element or add an element to the collection. Run this example and test it.

8. Tutorial: Using ObservableListContentProvider and ObservableMapLabelProvider

If you use WritableList and ObservableListContentProvider directly you will 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 the changes of 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);
			}
		};

		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();
	}

}
		

9. Thank you

Please help me to support this article:

Flattr this

10. Questions and Discussion

Before posting questions, please see the vogella FAQ. If you have questions or find an error in this article please use the www.vogella.com Google Group. I have created a short list how to create good questions which might also help you.

11. Links and Literature

11.1. Source Code

Source Code of Examples

11.3. vogella Resources

Eclipse RCP Training (German) Eclipse RCP Training with Lars Vogel

Android Tutorial Introduction to Android Programming

GWT Tutorial Program in Java and compile to JavaScript and HTML

Eclipse RCP Tutorial Create native applications in Java

JUnit Tutorial Test your application

Git Tutorial Put everything you have under distributed version control system