Jump to: navigation, search

JFace Data Binding/The New Binding API

The existing API for the JFace Data Binding is showing its age. A new API is now available. The same classes for observables, properties, etc are the same. It is only the way the observables are bound together that is different.

The DataBindingContext class is no longer used. Instead use the new Bind class. This code is currently under review in the e4 project (https://git.eclipse.org/r/#/c/15202/). You can get it as follows:

git fetch git://git.eclipse.org/gitroot/e4/org.eclipse.e4.databinding refs/changes/02/15202/3 && git checkout FETCH_HEAD

Basic Usage

To bind a model observable (IObservableValue<T> modelObservable) to a target observable (ObservableValue<T> targetObservable), use code as follows:

Bind.twoWay(modelObservable).to(targetObservable)

This will set up two way binding between the model and the target, with the initial value from the model being initially set into the target. If you want one way binding from the model to the target then use the following:

Bind.oneWay(modelObservable).to(targetObservable)

If you want to validate values going from the target to the model then you can insert a validator (IValidator<T> myValidator) as follows:

Bind.twoWay(modelObservable).validate(myValidator).to(targetObservable)

Notice how methods are chained together. You start the chain by calling either the static method Bind.oneWay or the static method Bind.twoWay, passing the model observable. These two methods return respectively, an IOneWayBinding or an ITwoWayBinding implementation. Both of these interfaces contain various methods for validations, conversions, and other operations. These methods in general return an IOneWayBinding or an ITwoWayBinding implementation, so you can chain operations together. Finally you call the to method passing the target observable.

Conversions

A value may be converted during data binding by including the convert method in the chain. For example, to bind a model observable (IObservableValue<T1> modelObservable) to a target observable (ObservableValue<T2> targetObservable) using a converter (IBidiConverter<T1,T2> myConverter), use code as follows:

Bind.twoWay(modelObservable).convert(myConverter).to(targetObservable)

For one-way binding, the convert method uses the old IConverter interface which has one method and converts one way. For two-way binding, the convert method uses the new IBidiConverter interface. This interface has two methods, modelToTarget and targetToModel. So, instead of having to supply two converters as with the old DataBindingContext, we supply one converter that can convert both ways.

The converter can also perform validation. Validation done inside a converter should be restricted to validating that the value can be converted. For example, a converter that converts between String and Integer should, when converting from String to Integer, check only that the string value can in fact be converted to an integer.

This keeps the usage simpler because it is not then necessary to separately specify a validator. This is more robust because the user cannot forget to validate a value before converting it. Often the converter would check that a value is valid as it is converting the value, so validation was effectively done twice. Take for example the conversion of a String to a URL. The only easy way of validating that a string is a valid URL is to do the conversion. The URL code will check the URL over the Internet and is quite expensive. We really don't want to have to do that twice.

Default values

Sometimes one may want a default value to be pre-populated in the UI. Furthermore the default value may change depending on what the user enters in other controls. For example suppose you have two fields, 'amount' and 'sales tax'. When the user enters an amount, the requirement is that the 'sales tax' field is completed based on the amount using a given tax rate. If the user edits the amount, the sales tax amount changes accordingly. However once the user edits the sales tax field then changes to the amount field no longer affect the sales tax field.

This is done by first creating an observable for the default value. This is typically a ComputedValue. This is then bound using one-way binding, chained through a call to the untilTargetChanges() method, then bound onwards to the target control. The target control will be updated with changes in the computed value until the user changes the value in the target control. At that point the binding is terminated.

Bounce-back bindings

Sometimes one wants text to be cleaned-up or normalized. For example you have a number that is stored in the model as an Integer. You want to display the number in a text box with separators, so 1234567 would be displayed as 1,234,567 or 1.234.567 depending on your regional settings. You want to allow more flexibility on what the user can enter. For example you may want to allow the user to miss out the separators. As the user types, the value is updated in the model. When the control loses focus you want the separators to be inserted in the Text control in the proper positions.

This is done using the bounceBack static method. This method is used to 'bounce back' a value from the target. Specifically this means whenever the target value changes, the value is converted using the given converter (targetToModel). The resulting value is the converted back using the same converter (modelToTarget).

To do this you create two bindings. One is a two-way binding that observes the Text control with SWT.Modify. This is the normal binding that moves data between the model and the control. The other binding is a 'bounce back' that observes the control with SWT.FocusOut. It might be coded as follows:

Bind.bounceBack(myIntegerToTextConverter) .to(SWTObservables.observeText(textControl, SWT.FocusOut));

SWT.FocusOut is used here because it would be very confusing to the user if the text were being normalized as the user types.

Further proposed work

The above features are already implemented and are available for use using the instructions at the top of this page.

Further proposed work is documented here