Thursday, October 25, 2007

First look at JFace Data-Binding 3.3

I took a look at the JFace Data-Binding framework that shipped with Eclipse 3.3 Europa, and was delighted to find a number of improvements over the older provisional version that was released with Eclipse 3.2 Callisto.

What is data-binding though?

The basic use-case of Data-binding is to synchronize the contents of user-interface widgets (e.g. text boxes, labels, etc...) with properties on domain models bidirectionally, primarily by relying on the Mediator and Observer Design Patterns from the Gang of Four, shielding the developer from the details.

For example, if you data-bind a text box to the name of a person, then whenever you modify the text box contents, the new value is automatically copied to the name property on the person model. In the same token, if the name on the person model gets modified programmatically, it gets automatically copied to the text box on the screen.

Here is some code to demonstrate how to do that with JFace data-binding:

Person person = new Person();
person.setName("Bob");
Text nameTextBox = new Text(composite, SWT.BORDER);
DataBindingContext dataBindingContext = new DataBindingContext();
dataBindingContext.bindValue(SWTObservables.observeText(nameTextBox, SWT.FocusOut), BeansObservables.observeValue(person, "name"));

Of course, the Person model has to have an addPropertyChangeListener method and a name setter that fires property changes according to the JavaBean spec:

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

One of the biggest differences in the new JFace Data-binding framework is the binding syntax:

3.2:
dataBindingContext.bind(text, new Property(person, "name"), null);

3.3:
dataBindingContext.bindValue(SWTObservables.observeText(nameTextBox, SWT.FocusOut), BeansObservables.observeValue(person, "name"));

Does that make you wonder if it's less user-friendly than the older syntax?

The benefit of the new syntax is realized in the method signature. The older bind method had Object in its signature parameters, so you could have made mistakes when invoking it. Therefore, the signature did not guide programmers about which parameters to pass.

The new API has bindValue and bindList to separate between them. The signature of the new bindValue only takes ObservableValue objects, which are easily created in factories like SWTObservables or BeansObservables. That restricts the parameters that can be passed

The observeText method takes any Control however, and I had problems realizing that I was supposed to only use observeText(Control, int) instead of observeText(Control) when observing a Text widget. I would like these methods to be more guiding to the developer with their signature. This can be done by having observeText, observeLabel, observeSpinner, etc... that have the exact widget they support in their signature (e.g. observeText(Text, int))

The cost of using the new 3.3 API is a much more verbose syntax that again makes me wonder if it is easier to just write observers myself to handle two-way synchronization for simple cases.

Nonetheless, JFace Data-binding lets you customize the way data flows from the widget to the model and vice versa using UpdateValueStrategy objects. This helps with more complex use-cases of data-binding when you would like life-cycle events to occur before or after a value is copied to the widget or model. Examples would involve data-conversion and business validation.

Still, I believe we need a simpler syntax for simple cases, which usually constitute about 80% of all use-cases. I look forward to seeing that being taken care of in future versions of JFace Data-Binding.

3 comments:

Brad Reynolds said...

Great post.

>"I would like these methods to be more guiding to the developer with their signature. This can be done by having observeText, observeLabel, observeSpinner, etc... that have the exact widget they support in their signature (e.g. observeText(Text, int))"

That's what we had initially. The problem is that with the API being that granular, and thus statically typed, is that the API will not be backwards compatible with older versions of SWT. So if we added observeTextOfNewWidget(...) we'd no longer be able to support the previous version of SWT. So we decided that for now we will document the API as to what can be observed thoroughly in javadoc to enable this possibility.

>"The cost of using the new 3.3 API is a much more verbose syntax that again makes me wonder if it is easier to just write observers myself to handle two-way synchronization for simple cases."

Yes, it is verbose but it's kind of meant to be that way as optimizations at the core could pose issues with expanding the API in the future. We're throwing around some ideas of a builder to clean things up a bit. Now that 1.0 is out the door we can take feedback, like what you provided, and look to providing some optimizations.

Also if using Java 5 you can use static imports to shorten the amount of code to invoke the static factory methods.

-brad

Frederick Polgardy said...


Of course, the Person model has to have an addPropertyChangeListener method and a name setter that fires property changes according to the JavaBean spec:

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


Andy,

This is a big tangential to the point of your post, but have you tried to use AOP at all to support making otherwise "plain vanilla JavaBeans" observable? I was playing with some binding in Struts about a year ago, and was able to inject the firePropertyChange call in an after advice.

Andy Maleh said...

Great suggestion Fred! I toyed with the idea in my mind only. :) It definitely helps in keeping domain models focused on business concerns instead of tangential concerns like firing property changes.