JFace Data Binding: Buffering Binding Updates

Bind Me Later

One of the key elements of any non-trivial data binding implementation is how you control the timing of the binding of data. Most of the simple examples of JFace or JGoodies data binding simply have ‘live’ bindings, where any change made on one side is immediately propagated to the other side. While this is valuable, and is certainly a neat party trick when you have two UI controls bound to the same field, it is in my estimation, the less common scenario.

In a majority of cases, you have a set of data; perhaps a large form in your UI, that is filled out by the user and changed as they see fit, and only when they are complete and ‘accept’ the changes are the changes actually bound back to the underlying model (at which time the model may also be saved to some data store).

This has the dual benefit of not only providing a working copy (the UI itself), but also of preventing a large amount of unnecessary messages being sent back and forth between the model and the UI.

JGoodies Comparison

In JGoodies, this type of delayed binding was handled by the BufferedValueModel, along with something that was referred to as a trigger channel, which was really just another binding in JGoodies that, when changed, cause the value model to ‘flush’ its buffer.

UpdateValueStrategy

JFace, on the other hand, has the concept of an UpdateValueStrategy that can be applied when creating any binding. This class contains several of the components of the binding, including the validation routine, the conversion routine, as well as the actual strategy for performing the update.

One of the mandatory arguments when you create an UpdateValueStrategy is an integer representing the update ‘policy’. There are four possible policies:

  • POLICY_UPDATE - This means update as soon as the change occurs on the ‘source’ side.
  • POLICYONREQUEST - This means, you guessed it, update on request. The question to be answered (which I promise I will, shortly) is how you request it.
  • POLICY_CONVERT - This is just like POLICY_ON_REQUEST but also automatically runs the new value through the validation and conversion routines, so you can immediately provide user feedback as they type. It still requires you manually request the final binding however. This is the most likely candidate in real world use.
  • POLICY_NEVER - This allows you to create a uni-directional binding, where one side will never update the other.

In my previous blog entry, I simply used the UpdateValueStrategy.POLICY_UPDATE policy.

When you want to trigger an update, you need to tell all of the appropriate bindings to update from one direction to another. This is typically done through the DataBindingContext which is created as part of the binding process for a particular view.

A (Relatively) Simple Example

Here is a simple example, using the classes from my previous entry.

final DataBindingContext ctx = new DataBindingContext(); Email someEmail = // .. (retrieve from somewhere) UpdateValueStrategy fromModel = new UpdateValueStrategy(UpdateValueStrategy.POLICYUPDATE); UpdateValueStrategy toModel = new UpdateValueStrategy(UpdateValueStrategy.POLICYON_REQUEST);

Text t = new Text(parent, SWT.BORDER); // At this time, the data binding context will capture this binding // and can refer back to it at a later time. ctx.bindValue( SWTObservables.observeText(t, SWT.Modify), BeanObservables.observeValue(someEmail, “subject”), toModel, fromModel );

Now, all we need to do, is create buttons to actually push the changes to the model at user request. As a bonus, we can also use this technique to perform a cancel or reset:

Button save = new Button(parent, SWT.PUSH); save.setText(“Save”); save.addListener(SWT.Selection, new Listener() { public void handleEvent(Event evt) { // Tell the context to push from the target to the models. ctx.updateModels(); } });

Button cancel = new Button(parent, SWT.PUSH); cancel.setText(“Cancel”); cancel.addListener(SWT.Selection, new Listener() { public void handleEvent(Event evt) { // Tell the context to re-set all of the targets to the model values. ctx.updateTargets(); } });