This tutorial explains the usage of the Eclipse JFace Data Binding framework that can be used to synchronize data between different objects.
1. Data binding with JFace
1.1. Using JFace Data Binding
The JFace data binding framework allows you to synchronize changes in object properties. Validation and conversion during the synchronization process are supported.
For example, you can bind the firstName String property of a Person object to the text property of an SWT Text widget.
If you change the text in the user interface, the corresponding property in the Person object is updated.
To observe changes in an attribute of a Java object, the data binding framework registers itself as a listener to this attribute.
JFace data binding provides API to register for changes in SWT widgets and JFace UI elements.
Other Java objects must implement this support.
For example, they can use PropertyChangeSupport according to the Java Bean specification.
Alternatively, you can use the WritableValue class from JFace inside the model.
The following plug-ins implement data binding.
-
org.eclipse.core.databinding
-
org.eclipse.core.databinding.beans
-
org.eclipse.core.databinding.property
-
org.eclipse.jface.databinding
The IObservableValue interface is the basis for observing properties in JFace.
The data binding framework provides factory classes to create IObservableValue objects.
The following table gives an overview of available classes to create instances of IObservableValue.
| Factory | Description |
|---|---|
|
Used to create |
|
Used for Java Beans.
A Java Bean is a Java object that 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 |
|
Used for properties of SWT widgets. |
|
Used for properties of JFace viewers. |
|
Used for properties of non-specialized types, like Objects, Collections or Maps. |
|
Used for properties of special objects, collections, maps and entries of an |
The following code demonstrates how to create an IObservableValue object for the firstName property of a Java object called person.
import org.eclipse.core.databinding.beans.typed.BeanProperties;
import org.eclipse.core.databinding.beans.typed.PojoProperties;
// more code
// if person is a POJO
var pojoModel = PojoProperties.value(Person.class, "firstName", String.class).observe(person);
var pojoModel2 = PojoProperties.value(Person.class, "firstName").observe(person);
// prefer beans if your data model provides property change support
var beansModel = BeanProperties.value("firstName").observe(person);
var beansModel2 = BeanProperties.value(Person.class, "firstName", String.class).observe(person);
The next example demonstrates how to create an IObservableValue for the text property of an SWT Text widget called text.
import org.eclipse.jface.databinding.swt.typed.WidgetProperties;
// more code
var observe = WidgetProperties.text(SWT.Modify).observe(text);
1.2. Example of a Java bean implementation
A Java class that 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 can extend this class, as demonstrated in the following example.
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);
}
}
1.3. Using the WritableValue class to track changes
If your domain model supports the JFace API, you can use WritableValue to provide the option to track values.
import org.eclipse.core.databinding.observable.value.WritableValue;
public class Person {
private final WritableValue<String> firstName = new WritableValue<>();
private final WritableValue<String> lastName = new WritableValue<>();
/**
* @TrackedGetter
*/
public String getFirstName() {
return firstName.getValue();
}
public void setFirstName(String firstName) {
this.firstName.setValue(firstName);
}
/**
* @TrackedGetter
*/
public String getLastName() {
return lastName.getValue();
}
public void setLastName(String lastName) {
this.lastName.setValue(lastName);
}
}
1.4. Data binding and Java objects without change notification
If you connect two properties and one of them cannot be observed, you can still use data binding for a unidirectional binding.
For example, you can connect the text property of an SWT Text field to the summary field of a Task object, even if a task cannot be observed.
In this case, updates in the summary of the Task object do not update the Text widget.
A change in the Text widget updates the property in the task object.
2. Create bindings
2.1. Using the ISideEffect API
The ISideEffect data binding API allows you to run code whenever an observable changes.
An ISideEffect is similar to a listener, but it does not need to be attached to any component.
Instead, it automatically reacts to changes in the tracked properties.
For example, the following code updates the label with the value of the user name every time the user name changes.
IObservableValue<String> username = ...
Label yourUsername = ...
ISideEffect sideEffect = ISideEffect.create(
() -> "Your username is: " + username.getValue(),
yourUsername::setText);
An ISideEffect can react to changes in multiple observables and avoids excessive updates if the observables fire many change events in a short period of time.
You can use the ISideEffectFactory to create several ISideEffect instances that share the same lifecycle.
For example, all ISideEffect instances created by the ISideEffectFactory from the WidgetSideEffects.createFactory(Widget) method are automatically disposed once the corresponding widget is disposed.
ISWTObservableValue<String> personFirstNameTextObservable = WidgetProperties.text(SWT.Modify)
.observe(personFirstNameText);
ISWTObservableValue<String> personLastNameTextObservable = WidgetProperties.text(SWT.Modify)
.observe(personLastNameText);
ISideEffectFactory sideEffectFactory = WidgetSideEffects.createFactory(personFirstNameText);
sideEffectFactory.create(person::getFirstName, personFirstNameText::setText);
sideEffectFactory.create(personFirstNameTextObservable::getValue, person::setFirstName);
sideEffectFactory.create(person::getLastName, personLastNameText::setText);
sideEffectFactory.create(personLastNameTextObservable::getValue, person::setLastName);
2.2. Connecting properties with the DataBindingContext
The DataBindingContext class provides the functionality to connect IObservableValue objects.
The DataBindingContext.bindValue() method connects two IObservableValue objects.
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.
For example, if you bind an attribute of a Person object p to the text attribute of a Text widget txtName, the value from p is initially copied to txtName.
// create a new context
DataBindingContext ctx = new DataBindingContext();
// define the observables
IObservableValue<String> target = WidgetProperties.text(SWT.Modify).observe(firstName);
IObservableValue<String> model = BeanProperties.value(Person.class, "firstName", String.class)
.observe(person);
// connect them
ctx.bindValue(target, model);
You can observe nested model properties, for example, attributes of classes that are contained in another class.
The following code demonstrates how to access the country property in the address field of the object person.
var model = PojoProperties.value(Person.class, "address.country").observe(person);
2.3. Observing map properties
You can observe entries of an IObservableMap via the Observables.observeMapEntry() method.
This allows you to control the point in time when you want to update or use the values from the map.
import jakarta.annotation.PostConstruct;
import jakarta.inject.Inject;
import jakarta.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.typed.WidgetProperties;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Text;
import com.vogella.tasks.model.Task;
public class ObservableMapEntry {
private static final String SECOND_ATTRIBUTE = "secondAttribute";
private static final String FIRST_ATTRIBUTE = "firstAttribute";
private final IObservableMap<String, String> attributesMap = new WritableMap<>();
private DataBindingContext dbc;
private Task task;
@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<String> txtFirstAttributeObservable = WidgetProperties.text(SWT.Modify)
.observe(txtFirstAttribute);
ISWTObservableValue<String> txtSecondAttributeObservable = WidgetProperties.text(SWT.Modify)
.observe(txtSecondAttribute);
// create observables for the map entries
IObservableValue<String> firstAttributeObservable = Observables.observeMapEntry(attributesMap,
FIRST_ATTRIBUTE);
IObservableValue<String> secondAttributeObservable = Observables.observeMapEntry(attributesMap,
SECOND_ATTRIBUTE);
dbc.bindValue(txtFirstAttributeObservable, firstAttributeObservable);
dbc.bindValue(txtSecondAttributeObservable, secondAttributeObservable);
}
@Inject
@Optional
public void setModel(@Named(IServiceConstants.ACTIVE_SELECTION) Task task) {
if (task != null) {
this.task = task;
// set new values for the map entries from a model object
attributesMap.put(FIRST_ATTRIBUTE, task.getSummary());
attributesMap.put(SECOND_ATTRIBUTE, task.getDescription());
}
}
@Persist
public void save() {
if (task != null) {
// only store the values in the model on save
task.setSummary(attributesMap.get(FIRST_ATTRIBUTE));
task.setDescription(attributesMap.get(SECOND_ATTRIBUTE));
}
}
}
3. Update strategy, converters and validators
3.1. UpdateValueStrategy
The bindValue() method of DataBindingContext allows you to specify UpdateValueStrategy objects as third and fourth parameters.
These objects allow you to control the update of the values.
The following update policies are available:
| Value | Description |
|---|---|
|
The state of the source observable is not tracked and the value of the destination observable is never updated. |
|
The state of the source observable is not tracked. Validation, conversion and the update of the destination observable are performed only when explicitly requested. Use Use |
|
The state of the source observable is tracked, including validation of changes (except for |
|
The state of the source observable is tracked. Validation, conversion and updating the value of the destination observable are performed automatically on each change of the source observable. |
If no UpdateValueStrategy is specified, UpdateValueStrategy.POLICY_UPDATE is used by default.
You can register converters and validators in the UpdateValueStrategy object.
3.2. Converter
Converters convert the values between the observed properties.
Converters are defined based on the IConverter interface.
UpdateValueStrategy.setConverter() sets the converter.
The static IConverter.create(Function) and IConverter.create(Object, Object, Function) methods create a converter from a lambda expression.
The static UpdateValueStrategy.create(IConverter) method creates an update strategy with a converter.
For example, you can convert a String to another object and vice versa.
Assume you have a Developer object with a programmingSkills property.
package com.vogella.databinding.example;
public class Developer extends ModelObject {
private String name;
private String[] programmingSkills;
public String getName() {
return name;
}
public void setName(String name) {
firePropertyChange("name", this.name, this.name = name);
}
public String[] getProgrammingSkills() {
return programmingSkills;
}
public void setProgrammingSkills(String[] programmingSkills) {
firePropertyChange("programmingSkills", this.programmingSkills,
this.programmingSkills = programmingSkills);
}
}
The entries of programmingSkills should be shown in the Text widget as comma-separated text.
Therefore, a conversion is necessary in both directions, defined via an UpdateValueStrategy for each direction of the binding.
package com.vogella.databinding.example;
import jakarta.annotation.PostConstruct;
import org.eclipse.core.databinding.DataBindingContext;
import org.eclipse.core.databinding.UpdateValueStrategy;
import org.eclipse.core.databinding.beans.typed.BeanProperties;
import org.eclipse.core.databinding.conversion.IConverter;
import org.eclipse.jface.databinding.swt.typed.WidgetProperties;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
public class ConverterPart {
@PostConstruct
public void createPartControl(Composite parent) {
// create the Developer model with programming skills
Developer person = new Developer();
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();
// define converters
IConverter<String, String[]> convertToStringArray = IConverter.create(String.class, String[].class,
s -> s.split(","));
IConverter<String[], String> convertToString = IConverter.create(String[].class, String.class,
skills -> String.join(",", skills));
// create the observables, which should be bound
var programmingSkillsTarget = WidgetProperties.text(SWT.Modify).observe(programmingSkillsText);
var programmingSkillsModel = BeanProperties.value(Developer.class, "programmingSkills", String[].class)
.observe(person);
// bind observables together with the appropriate UpdateValueStrategies
dbc.bindValue(programmingSkillsTarget, programmingSkillsModel,
UpdateValueStrategy.create(convertToStringArray),
UpdateValueStrategy.create(convertToString));
// button to check the data model
Button button = new Button(parent, SWT.PUSH);
button.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false));
button.setText("Show data model");
button.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
for (String skill : person.getProgrammingSkills()) {
System.out.println(skill);
}
}));
}
}
3.3. Validator
A validator allows you to implement validation of the data before it is propagated to the other connected property.
A class that provides this functionality must implement the org.eclipse.core.databinding.validation.IValidator interface.
import org.eclipse.core.databinding.Binding;
import org.eclipse.core.databinding.DataBindingContext;
import org.eclipse.core.databinding.UpdateValueStrategy;
import org.eclipse.core.databinding.conversion.IConverter;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.core.databinding.validation.ValidationStatus;
// converter as before
IConverter<String, String[]> convertToStringArray = IConverter.create(String.class, String[].class,
s -> s.split(","));
// reject Perl as programming language
UpdateValueStrategy<String, String[]> updateStrategy = UpdateValueStrategy.create(convertToStringArray);
updateStrategy.setAfterGetValidator(s -> s.contains("Perl")
? ValidationStatus.error("Perl is not a programming language")
: ValidationStatus.ok());
Binding binding = dbc.bindValue(widgetValue, modelValue, updateStrategy, null);
|
The |
4. More on bindings
4.1. ControlDecorators
Data binding allows you to use icon decorators in the user interface that reflect the status of the field validation.
This allows you to provide immediate feedback to the user.
To create the control decoration, use the return object from the bindValue() method of the DataBindingContext object.
// assumes that the update strategy has a validator
Binding binding = ctx.bindValue(widgetValue, modelValue, strategy, null);
// show validation results as decorations on the control
ControlDecorationSupport.create(binding, SWT.TOP | SWT.LEFT);
The result might look like the following screenshot.

4.2. Binding values of a radio button group
In a radio button group, each button usually represents a certain value.
To bind the value of the selected radio button, use the SelectObservableValue class.
The following example shows how to bind the currently selected radio value to a Label.
import org.eclipse.core.databinding.DataBindingContext;
import org.eclipse.core.databinding.observable.value.SelectObservableValue;
import org.eclipse.jface.databinding.swt.ISWTObservableValue;
import org.eclipse.jface.databinding.swt.typed.WidgetProperties;
// more code
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 = { "Matthew Hall", "Dirk Fauth", "Lars Vogel", "Simon Scholz" };
SelectObservableValue<String> selectedRadioButtonObservable = new SelectObservableValue<>();
for (String name : names) {
Button button = new Button(group, SWT.RADIO);
button.setText(name);
// the option value is used if the corresponding button is selected
selectedRadioButtonObservable.addOption("Selected: " + name, WidgetProperties.buttonSelection().observe(button));
}
Label label = new Label(shell, SWT.NONE);
GridDataFactory.fillDefaults().applyTo(label);
ISWTObservableValue<String> 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 the following.

4.3. Listening to all changes in the binding
You can register a listener to all bindings of the DataBindingContext class.
Your listener is called whenever something changes.
For example, a part that behaves like an editor can use this to mark itself as dirty if its data model changes.
// part is the injected MPart, ctx the DataBindingContext field of the editor
IChangeListener listener = event -> {
// part is null if this class is not used as a part
if (part != null) {
part.setDirty(true);
}
};
private void updateUserInterface(Task task) {
// check that the user interface is available
if (txtSummary != null && !txtSummary.isDisposed()) {
// deregister from all validation status providers, a MultiValidator is not a binding
ctx.getValidationStatusProviders()
.forEach(p -> p.getTargets().forEach(t -> t.removeChangeListener(listener)));
// dispose the bindings, a disposed context cannot be reused
ctx.dispose();
ctx = new DataBindingContext();
// the data binding code is left out for brevity
// register the listener with the targets of all validation status providers
ctx.getValidationStatusProviders()
.forEach(p -> p.getTargets().forEach(t -> t.addChangeListener(listener)));
}
}
4.4. Placeholder binding with WritableValue
You can create bindings to a WritableValue object.
A WritableValue object can hold a reference to another object.
If you exchange this reference, data binding uses the new object for its binding. This way, you create the binding once and still exchange the bound object.
To bind to the object contained in a WritableValue, use the observeDetail() method.
WritableValue<Person> value = new WritableValue<>();
// create the binding
DataBindingContext ctx = new DataBindingContext();
IObservableValue<String> target = WidgetProperties.text(SWT.Modify).observe(text);
IObservableValue<String> model = BeanProperties.value(Person.class, "firstName", String.class)
.observeDetail(value);
ctx.bindValue(target, model);
// create a Person object named p
Person p = new Person();
// make the binding valid for this new object
value.setValue(p);
4.5. ComputedValue and observable trackers
A ComputedValue provides an abstract calculate method, which computes a value from the observables that it reads.
public class FormattedName extends ComputedValue<String> {
private final IObservableValue<String> firstName;
private final IObservableValue<String> lastName;
FormattedName(IObservableValue<String> firstName, IObservableValue<String> lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
@Override
protected String calculate() {
// getValue() is a tracked getter, so this ComputedValue is recomputed
// whenever lastName or firstName changes
String lastName = this.lastName.getValue();
String firstName = this.firstName.getValue();
lastName = (lastName == null || lastName.isEmpty()) ? "[Last Name]" : lastName;
firstName = (firstName == null || firstName.isEmpty()) ? "[First Name]" : firstName;
return lastName + ", " + firstName;
}
}
The static create method accepts a Supplier which implements the calculation.
IObservableValue<String> lastName = // ...
IObservableValue<String> firstName = // ...
IObservableValue<String> formattedName = ComputedValue.create(() -> {
// getValue() is a tracked getter, so this value is recomputed
// whenever lastName or firstName changes
String lastNameValue = lastName.getValue();
String firstNameValue = firstName.getValue();
lastNameValue = (lastNameValue == null || lastNameValue.isEmpty()) ? "[Last Name]" : lastNameValue;
firstNameValue = (firstNameValue == null || firstNameValue.isEmpty()) ? "[First Name]" : firstNameValue;
return lastNameValue + ", " + firstNameValue;
});
A ComputedValue can combine different observables and is recomputed every time one of the observables it read changes.
An example of a ComputedValue implementation is the AggregateValidationStatus.
It keeps track of all validation statuses that have been passed to it and aggregates them to one MultiStatus or returns the one with maximum severity.
A ComputedValue is a good alternative to converter implementations.
For instance, the AggregateValidationStatus returns an IStatus as value.
To bind it to the enabled state of a Finish button, you need a boolean value.
// track the validation status of all bindings in the DataBindingContext
AggregateValidationStatus aggregateValidationStatus = new AggregateValidationStatus(dbc.getBindings(),
AggregateValidationStatus.MAX_SEVERITY);
// create a boolean observable, which depends on the OK status
IObservableValue<Boolean> isValidationOk = ComputedValue
.create(() -> aggregateValidationStatus.getValue().isOK());
ISWTObservableValue<Boolean> buttonEnabledObservable = WidgetProperties.enabled().observe(finishButton);
// bind the enablement of the finish button to the validation
dbc.bindValue(buttonEnabledObservable, isValidationOk);
An IConverter from IStatus to boolean would require considerably more code.
5. Data binding for JFace viewers
5.1. Binding viewers
JFace data binding provides functionality to bind the data of JFace viewers. Data binding for these viewers distinguishes between changes in the collection and changes in the individual object.
If 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 that 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<Person>());
// create sample data
List<Person> persons = createExampleData();
// wrap the input into an observable list
IObservableList<Person> input = Properties.selfList(Person.class).observe(persons);
// set the IObservableList as input for the viewer
viewer.setInput(input);
5.2. Observing list details
You can use the ObservableMapLabelProvider class to observe changes of the list elements.
ObservableListContentProvider<Person> contentProvider = new ObservableListContentProvider<>();
// the label provider monitors the known elements and updates the labels on changes
IObservableSet<Person> knownElements = contentProvider.getKnownElements();
IObservableMap<Person, String> firstNames = BeanProperties
.value(Person.class, "firstName", String.class).observeDetail(knownElements);
IObservableMap<Person, String> lastNames = BeanProperties
.value(Person.class, "lastName", String.class).observeDetail(knownElements);
IObservableMap<?, ?>[] labelMaps = { firstNames, lastNames };
ILabelProvider labelProvider = new ObservableMapLabelProvider(labelMaps) {
@Override
public String getText(Object element) {
return firstNames.get(element) + " " + lastNames.get(element);
}
};
5.3. ViewerSupport
ViewerSupport simplifies the setup for JFace viewers in cases where selected columns should be displayed.
It registers change listeners on the collection as well as on the individual elements.
The ViewerSupport.bind() method creates the label provider and the content provider for a viewer automatically.
// MyModel.getPersons() returns a List<Person>,
// WritableList wraps it in an IObservableList
WritableList<Person> input = new WritableList<>(MyModel.getPersons(), Person.class);
// creates the content and label provider and sets the input,
// the table columns show the given properties
ViewerSupport.bind(viewer, input,
BeanProperties.values(Person.class, "firstName", "lastName", "married"));
5.4. Master-detail binding
The ViewerProperties class allows you to create an IObservableValue for properties of the viewer.
For example, you can track 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, use the PojoProperties or the BeanProperties class.
Both provide the value().observeDetail() method chain that allows you to observe a detailed value of an IObservableValue object.
For example, the following code synchronizes the summary property of the Task domain object to a Label based on the selection of a ComboViewer.
import org.eclipse.core.databinding.DataBindingContext;
import org.eclipse.core.databinding.beans.typed.PojoProperties;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.jface.databinding.swt.typed.WidgetProperties;
import org.eclipse.jface.databinding.viewers.IViewerObservableValue;
import org.eclipse.jface.databinding.viewers.typed.ViewerProperties;
// assume we have Task domain objects
// tasks is of type: List<Task>
ComboViewer viewer = new ComboViewer(parent, SWT.DROP_DOWN);
viewer.setContentProvider(ArrayContentProvider.getInstance());
viewer.setLabelProvider(LabelProvider.createTextProvider(element -> ((Task) element).getSummary()));
viewer.setInput(tasks);
// 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<String> target = WidgetProperties.text().observe(label);
// observe the selection
IViewerObservableValue<Task> selectedTask = ViewerProperties.singleSelection(Task.class).observe(viewer);
// observe the summary attribute of the selection
IObservableValue<String> detailValue = PojoProperties.value(Task.class, "summary", String.class)
.observeDetail(selectedTask);
dbc.bindValue(target, detailValue);
5.5. Chaining properties
You can chain properties together to simplify observing nested properties. The following examples demonstrate this usage.
IObservableValue<String> viewerSelectionSummaryObservable = ViewerProperties
.singleSelection(Task.class)
.value(BeanProperties.value(Task.class, "summary", String.class))
.observe(viewer);
IListProperty<Node, String> siblingNames = BeanProperties.value(Node.class, "parent")
.list("children")
.values("name", String.class);
IObservableList<String> siblingNamesObservable = siblingNames.observe(node);
6. Extending data binding with custom observables
6.1. Developing custom observables
You can implement observables for your own classes.
This implementation can extend the AbstractObservable class or one of its subclasses, like the AbstractObservableValue class.
For example, assume you have the following custom widget that draws a text on a canvas.
public class CustomWidget extends Canvas {
private static final int MARGIN = 5;
private String text;
public CustomWidget(Composite parent) {
super(parent, SWT.NONE);
addPaintListener(e -> e.gc.drawText(getDisplayText(), MARGIN, MARGIN));
}
@Override
public Point computeSize(int wHint, int hHint, boolean changed) {
checkWidget();
GC gc = new GC(this);
try {
Point extent = gc.textExtent(getDisplayText());
return new Point(extent.x + 2 * MARGIN, extent.y + 2 * MARGIN);
} finally {
gc.dispose();
}
}
public String getText() {
checkWidget();
return text;
}
public void setText(String text) {
checkWidget();
this.text = text;
// the preferred size depends on the text
requestLayout();
redraw();
}
private String getDisplayText() {
return text != null ? text : "Good Default";
}
}
Since there is no predefined IObservable for custom widgets, you must implement your own.
|
While it is possible to use the PojoProperties class to create an IObservableValue (for example, PojoProperties.value("text").observe(customWidget)), this does not produce an ISWTObservableValue.
As a result, classes such as ControlDecorationSupport do not work out of the box with your custom widget.
6.2. Directly implement IObservable
The CustomWidgetObservableValue extends AbstractObservableValue and implements the ISWTObservableValue interface.
public class CustomWidgetObservableValue extends AbstractObservableValue<String>
implements ISWTObservableValue<String> {
private final 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 String doGetValue() {
return customWidget.getText();
}
@Override
protected void doSetValue(String value) {
customWidget.setText(value);
}
// ISWTObservableValue enables ControlDecorationSupport for this observable
@Override
public Widget getWidget() {
return customWidget;
}
}
This observable can then be used as follows.
@PostConstruct
public void createComposite(Composite parent) {
parent.setLayout(new GridLayout(1, false));
DataBindingContext dbc = new DataBindingContext();
CustomWidget widget = new CustomWidget(parent);
var customWidgetObservableValue = new CustomWidgetObservableValue(widget);
Task task = // ...
IObservableValue<String> taskSummaryObservable = PojoProperties
.value(Task.class, "summary", String.class).observe(task);
dbc.bindValue(customWidgetObservableValue, taskSummaryObservable);
}
6.3. Implement an IProperty rather than IObservable directly
A better approach is to implement the IProperty interface, like IValueProperty, IWidgetValueProperty and others.
The following code implements the solution of the previous section with an IProperty implementation.
public class CustomWidgetProperty extends WidgetValueProperty<CustomWidget, String> {
@Override
public Object getValueType() {
return String.class;
}
@Override
protected String doGetValue(CustomWidget source) {
return source.getText();
}
@Override
protected void doSetValue(CustomWidget source, String value) {
source.setText(value);
}
}
This WidgetValueProperty can be used as follows.
@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
CustomWidgetProperty customWidgetProperty = new CustomWidgetProperty();
ISWTObservableValue<String> customWidgetObservableValue = customWidgetProperty.observe(widget);
Task task = // ...
IObservableValue<String> taskSummaryObservable = PojoProperties
.value(Task.class, "summary", String.class).observe(task);
dbc.bindValue(customWidgetObservableValue, taskSummaryObservable);
}
6.4. Delegates for common properties of different objects
Delegating properties act like a factory for IProperty objects, where the IProperty is not created directly, but when the observe method is called.
For instance, a DelegatingValueProperty creates an IValueProperty according to the object that should be observed.
Imagine you want such a factory that returns the CustomWidgetProperty from the previous section for a CustomWidget and the existing WidgetProperties.localDateSelection() for a DateTime.
For this, derive from DelegatingValueProperty.
public class VogellaDelegatingValueProperty extends DelegatingValueProperty {
public VogellaDelegatingValueProperty() {
this(null);
}
public VogellaDelegatingValueProperty(Object valueType) {
super(valueType);
}
// called by observe(source) to pick the property for the given source
@Override
protected IValueProperty doGetDelegate(Object source) {
return switch (source) {
case CustomWidget _ -> new CustomWidgetProperty();
case DateTime _ -> WidgetProperties.localDateSelection();
default -> null;
};
}
}
To have a factory like the WidgetProperties class, 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() {
// handles all widget types known to VogellaDelegatingValueProperty
return new VogellaDelegatingValueProperty();
}
}
The WidgetProperties class contains several methods that are better examples of delegating properties.
The following code uses the factory for "vogella" properties and shows samples that fit the intention of the delegates.
import org.eclipse.jface.databinding.swt.ISWTObservableValue;
import org.eclipse.jface.databinding.swt.typed.WidgetProperties;
// more code
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 dateTimeObservable = VogellaProperties.vogellaProperty().observe(dateTime);
// common properties for different kinds of widgets
Button button = new Button(parent, SWT.CHECK);
CCombo combo = new CCombo(parent, SWT.READ_ONLY);
// see WidgetSelectionProperty for more details
ISWTObservableValue oldDateTimeObservable = WidgetProperties.widgetSelection().observe(dateTime);
ISWTObservableValue buttonSelectionObservable = WidgetProperties.widgetSelection().observe(button);
ISWTObservableValue comboSelectionObservable = WidgetProperties.widgetSelection().observe(combo);
// and much more
}
}
7. Example: Binding a date range with validation
WidgetProperties.localDateSelection() binds the SWT DateTime widget to a java.time.LocalDate, and WidgetProperties.localTimeSelection() binds it to a java.time.LocalTime.
The following example uses it for a booking form with a check-in and a check-out date.
Checking each date on its own is not enough, as the check-out date must be after the check-in date.
A MultiValidator validates several observables together.
Its observeValidatedValue() method wraps a target observable, so that the model receives a value only while the whole validator reports a valid status.
@PostConstruct
public void createComposite(Composite parent) {
GridLayoutFactory.swtDefaults().numColumns(2).spacing(15, 8).applyTo(parent);
DataBindingContext dbc = new DataBindingContext();
// the model, receives only valid date ranges
WritableValue<LocalDate> checkIn = new WritableValue<>(LocalDate.now(), LocalDate.class);
WritableValue<LocalDate> checkOut = new WritableValue<>(LocalDate.now().plusDays(3), LocalDate.class);
new Label(parent, SWT.NONE).setText("Check-in:");
DateTime checkInWidget = new DateTime(parent, SWT.DATE | SWT.DROP_DOWN);
new Label(parent, SWT.NONE).setText("Check-out:");
DateTime checkOutWidget = new DateTime(parent, SWT.DATE | SWT.DROP_DOWN);
IObservableValue<LocalDate> checkInTarget = WidgetProperties.localDateSelection().observe(checkInWidget);
IObservableValue<LocalDate> checkOutTarget = WidgetProperties.localDateSelection().observe(checkOutWidget);
// validates both widgets together
MultiValidator dateRangeValidator = new MultiValidator() {
@Override
protected IStatus validate() {
if (!checkOutTarget.getValue().isAfter(checkInTarget.getValue())) {
return ValidationStatus.error("Check-out must be after check-in");
}
return ValidationStatus.ok();
}
};
dbc.addValidationStatusProvider(dateRangeValidator);
// the validated values only pass a valid date range on to the model
dbc.bindValue(dateRangeValidator.observeValidatedValue(checkInTarget), checkIn);
dbc.bindValue(dateRangeValidator.observeValidatedValue(checkOutTarget), checkOut);
ControlDecorationSupport.create(dateRangeValidator, SWT.TOP | SWT.LEFT);
// derived from the model, so it always shows the last valid booking
DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM);
IObservableValue<String> summary = ComputedValue.create(() -> {
long nights = ChronoUnit.DAYS.between(checkIn.getValue(), checkOut.getValue());
return formatter.format(checkIn.getValue()) + " to " + formatter.format(checkOut.getValue()) + " ("
+ nights + (nights == 1 ? " night)" : " nights)");
});
Label summaryLabel = new Label(parent, SWT.NONE);
GridDataFactory.fillDefaults().span(2, 1).grab(true, false).applyTo(summaryLabel);
dbc.bindValue(WidgetProperties.text().observe(summaryLabel), summary, UpdateValueStrategy.never(), null);
Label statusLabel = new Label(parent, SWT.NONE);
GridDataFactory.fillDefaults().span(2, 1).grab(true, false).applyTo(statusLabel);
Button bookButton = new Button(parent, SWT.PUSH);
bookButton.setText("Book");
GridDataFactory.swtDefaults().span(2, 1).align(SWT.END, SWT.CENTER).applyTo(bookButton);
// converters turn the validation status into a message and an enabled state
IObservableValue<IStatus> status = dateRangeValidator.getValidationStatus();
dbc.bindValue(WidgetProperties.text().observe(statusLabel), status, UpdateValueStrategy.never(),
UpdateValueStrategy.create(IConverter.create(IStatus.class, String.class,
s -> s.isOK() ? "" : s.getMessage())));
dbc.bindValue(WidgetProperties.enabled().observe(bookButton), status, UpdateValueStrategy.never(),
UpdateValueStrategy.create(IConverter.create(IStatus.class, Boolean.class, IStatus::isOK)));
}
The example combines several data binding features:
-
ControlDecorationSupportshows the error of theMultiValidatoron bothDateTimewidgets. -
A
ComputedValuederives the number of nights from the model and formats the dates with a localizedDateTimeFormatter. -
Converters in an
UpdateValueStrategyturn the validation status into the message of a label and the enabled state of the Book button.
If you select a check-out date before the check-in date, the form shows the error and disables the button. The summary still shows the last valid booking, as the invalid date never reaches the model.

8. 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 plug-in 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.
9. Data Binding with SWT controls
9.1. First example
Create a new Eclipse RCP project named 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 a 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.typed.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.typed.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.buttonSelection().observe(marriedButton);
modelValue = BeanProperties.value(Person.class, "married").observe(person);
ctx.bindValue(widgetValue, modelValue);
widgetValue = WidgetProperties.comboSelection().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 the model changes automatically. If you change the model then the UI will update. If you try to input something else than a number in the age field you will get an error symbol in the UI. If the mouse hovers over the symbol, you see the error message.
9.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 StringLongerThanTwo 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 StringLongerThanTwo implements IValidator {
@Override
public IStatus validate(Object value) {
if (value instanceof String) {
String s = (String) value;
// We check if the string is longer than 2 signs
if (s.length() > 2) {
return Status.OK_STATUS;
} else {
return ValidationStatus
.error("Name must be longer than 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.typed.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.typed.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.StringLongerThanTwo;
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 StringLongerThanTwo());
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.buttonSelection().observe(marriedButton);
modelValue = BeanProperties.value(Person.class, "married").observe(
person);
ctx.bindValue(widgetValue, modelValue);
widgetValue = WidgetProperties.comboSelection().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);
}
}
10. 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.typed.BeanProperties;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.core.databinding.observable.value.WritableValue;
import org.eclipse.jface.databinding.swt.typed.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<String> target = WidgetProperties.text(SWT.Modify).observe(
text);
IObservableValue<String> model = BeanProperties.value("firstName")
.observeDetail(value);
ctx.bindValue(target, model);
}
@Override
public void setFocus() {
}
}
11. Exercise: 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 script 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.typed.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 = viewer.getStructuredSelection();
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 = viewer.getStructuredSelection();
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.
12. Using ObservableListContentProvider and ObservableMapLabelProvider
If you use
WritableList
and
ObservableListContentProvider
you only listen 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.typed.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 = viewer.getStructuredSelection();
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 = viewer.getStructuredSelection();
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();
}
}
13. Eclipse Data Binding resources
13.1. vogella Java example code
If you need more assistance we offer Online Training and Onsite training as well as consulting