Showing posts with label observables. Show all posts
Showing posts with label observables. Show all posts

Thursday, February 12, 2009

Master-detail editing with multiple selection

A few weeks ago we released DuplexingObservableValue. This class, among other things, helps make possible master-detail editing on a multiple selection:


Because the selected movies have the same director and same writer, those values are displayed in the master-detail fields at the bottom. However, since they have different titles and release dates, those fields use a stand-in "multi value" instead.

Editing the release date field simultaneously changes the release date of all movies in the selection.


Here's the code that was used to hook up the detail fields to the multi-selection of the viewer:

IObservableList selections = ViewerProperties.multipleSelection()
.observe(viewer);

dbc.bindValue(
WidgetProperties.text(SWT.Modify).observe(title),
DuplexingObservableValue.withDefaults(
BeanProperties.value("title").observeDetail(selections),
"", "<Multiple titles>"));

dbc.bindValue(
WidgetProperties.text(SWT.Modify).observe(releaseDate),
DuplexingObservableValue.withDefaults(
BeanProperties.value("releaseDate").observeDetail(selections),
"", "<Multiple dates>"));

dbc.bindValue(
WidgetProperties.text(SWT.Modify).observe(director),
DuplexingObservableValue.withDefaults(
BeanProperties.value("director").observeDetail(selections),
"", "<Multiple directors>"));

dbc.bindValue(
WidgetProperties.text(SWT.Modify).observe(writer),
DuplexingObservableValue.withDefaults(
BeanProperties.value("writer").observeDetail(selections),
"", "<Multiple writers>"));
See the full code listing for this snippet here.

Disclaimer: The method DuplexingObservableValue.withDefaults() was added after the 3.5M5 milestone, so in order to use it you need to check out the databinding projects from CVS HEAD, or be using a more recent integration build.

Tuesday, February 03, 2009

Introducing the Properties API

Observables are hard to implement. There are a handful of rules, guidelines and corner cases that observables implementors must pay attention to for data binding to work properly. Observables must call ObservableTracker.getterCalled() for things like ComputedValue or MultiValidator to work; observables must ensure that all invocations are made from within the realm; a ChangeEvent must be fired before ValueChangeEvent; the list goes on. To an extent we've been able to isolate these concerns within the framework, but over time I couldn't help noticing that many of our observables followed a template--a lengthy, complicated one.

In June 2008 I stumbled across bug 194734 and I thought Tom was on to something. I started working on a prototype, which we've been evolving and refining for the past six months. I'm very pleased with the result, and if you've tried implementing your own observables before, I think you will be too.

There are four types of properties: IValueProperty, IListProperty, ISetProperty and IMapProperty. Properties serve two main purposes:
  • They act as convenient, portable observable factories for a particular property.
  • They act as strategy objects for the observables they create. That is, the observables use the properties to access, modify, and listen to the property source object.
Suppose you want to observe the "name" property of a bean object:

Person person = new Person("Joe");
IValueProperty nameProperty = BeanProperties.value("name");
IObservableValue personName =
nameProperty.observe(person);

One of the strengths of properties is how easy it is to combine them to observe nested attributes:

IListProperty childrenProperty = BeanProperties.list("children");
IValueProperty nameProperty = BeanProperties.value("name");
IObservableList namesOfChildren =
childrenProperty.values(nameProperty).observe(person);

or, a shorter version:

IObservableList namesOfChildren = BeanProperties
.list("children").values("name").observe(person);

Properties are reusable:

IValueProperty selection = WidgetProperties.selection();
IObservableValue buttonSelection = selection.observe(button);
IObservableValue comboSelection = selection.observe(combo);
IObservableValue spinnerSelection = selection.observe(spinner);

But most importantly, they are easy to implement. Let's close with a real-world example:

DataBinding does not come with built-in support for observing the selection of a DateTime widget (see bug 169876 for an explanation why). However a useful use case is to observe the date portion (year+month+day) and set the time of day to midnight.

The following is a complete property implementation for the DateTime#selection property, that you are free to use in your own projects:

public class DateTimeSelectionProperty extends SimpleValueProperty {
public Object getValueType() {
return Date.class;
}

// One calendar per thread to preserve thread-safety
private static final ThreadLocal calendar =
new ThreadLocal() {
protected Object initialValue() {
return Calendar.getInstance();
}
};

protected Object doGetValue(Object source) {
Calendar cal = (Calendar) calendar.get();

DateTime dateTime = (DateTime) source;
cal.clear();
cal.set(dateTime.getYear(), dateTime.getMonth(),
dateTime.getDay());
return cal.getTime();
}

protected void doSetValue(Object source, Object value) {
Calendar cal = (Calendar) calendar.get();
cal.setTime((Date) value);

DateTime dateTime = (DateTime) source;
dateTime.setYear(cal.get(Calendar.YEAR));
dateTime.setMonth(cal.get(Calendar.MONTH));
dateTime.setDay(cal.get(Calendar.DAY_OF_MONTH));
}

public INativePropertyListener adaptListener(
ISimplePropertyListener listener) {
return new WidgetListener(listener);
}

private class WidgetListener implements INativePropertyListener,
Listener {
private final ISimplePropertyListener listener;

protected WidgetListener(ISimplePropertyListener listener) {
this.listener = listener;
}

public void handleEvent(Event event) {
listener.handlePropertyChange(
new SimplePropertyEvent(event.widget,
DateTimeSelectionProperty.this, null));
}
}

protected void doAddListener(Object source,
INativePropertyListener listener) {
((DateTime) source).addListener(
SWT.Selection, (Listener) listener);
}

protected void doRemoveListener(Object source,
INativePropertyListener listener) {
((DateTime) source).removeListener(
SWT.Selection, (Listener) listener);
}

public IObservableValue observe(Object source) {
// For widgets, we want the display realm instead of
// Realm.getDefault()
Realm realm = SWTObservables.getRealm(
((Widget) source).getDisplay());
return observe(realm, source);
}
}


Now we can observe the selection of a DateTime:

IObservableValue selection =
new DateTimeSelectionProperty().observe(dateTime);


Today we released WidgetValueProperty as public API, but did not make it in time for M5. However in the future (starting in the next integration build), implementing properties for widgets will be much simpler:

  • Omit the adaptListener, doAddListener, and doRemoveListener methods
  • Omit the WidgetListener class
  • Omit the observe() method
  • Add a no-arg constructor with a call to super(eventType) in the constructor.

In the example the constructor would call "super(SWT.Selection)". As long as the the widget supports untyped listeners, WidgetValueProperty will handle the listener parts for you.

Friday, January 30, 2009

Watch this guy: Angelo Zerr

A couple of months ago, Angelo Zerr contacted me out of the blue and asked for feedback on a project he had worked on called "DOM Binding". I was pretty busy at that time and put it on my "someday maybe" list, but he kept pinging me about it and even contributed his code in a Bugzilla so I decided to take a look. Even then, I thought "yeah, w3c DOM stuff, what could be more boring".

Until I ran the small demo app he had written.

I was excited and impressed by this demo app and with how little code it implemented bi-directional synchronization between a forms-based UI and data in XML form. Quite a mouthful, I know, but imagine you had to write one of the editors provided by PDE: some random XML file format cooked up by someone else, which is to be presented in a nice form-based UI through which mere mortals can edit the data. At the same time, the editor should allow experts to view and change the XML text itself, such that any changes get reflected in the UI.

I had always assumed that implementing this would be a major pain in the neck - you would probably write a bunch of Java classes that would be the in-memory representation of the XML data, call the verbose w3c DOM API to get the data into your Java objects, write UI code for editing the Java objects, and sprinkle in code that keeps everything in sync. Lots of repetitive code, with many opportunities to make small mistakes that would be hard to find or debug.

Enter Angelo - he took the Eclipse data binding framework and implemented observables that work against w3c DOM nodes directly, resulting in suprisingly concise code. This is one of those things that you have to see in action to "get" it, an ideal topic for a screencast.

Last night, I decided to learn how to make screencasts by using this as a warm-up exercise. The result turned out pretty amateurish. I apologize for all the "erm"s and that you can hear me breathe, but I think it's still worth watching. Click to watch the screencast (five minutes, 7 MB).

Btw - Angelo, congratulations on becoming an e4 committer!

Monday, November 06, 2006

A Focus on Data Structures

HEAD hasn't had many changes lately for data binding because of the 3.3M3 release. But this doesn't mean that nothing has been happening. Changes have been occurring in the concurrency branch (Bug116920_investigation) for, you guessed it, concurrency. But the branch has also been used for removing provisional packages and the refactoring of some of the provisional code into API. But before I get into the other changes it's very important to us that we get as many eyes on the concurrency approach as possible. If you're a concurrency guru or just someone with a interest please read through bug 116920 and provide feedback if you have any.

The other changes that have been occurring are interesting. If you are vaguely familiar with JFace Data Binding you probably know that data binding is comprised of two parts: observables and bindings. The idea behind bindings is pretty straight forward and what you would expect from the name. They bind two entities and provides a MVC-esque flow of data to keep the 2 in sync. This is normally applied in the context of a graphical user interface in order to keep the UI in sync with a model. But there's more that needs to be implemented in order to make this synchronization and this is where observables come in. Observables are an implementation of the observer pattern and they create a common abstraction that allows for the observing of changes in an object. This all begins with IObservable but branches out into:

IObservableValue

Interface for the observing of a single value.

IObservableList

Interface for observing changes in a List. The notifications are notifications of when items are added or removed from the List. If an object being maintained in the List changes events are not fired from the IObservableList implementation.

IObservableSet

Interface for observing changes in a Set. Like IObservableList the change events are for items being added and removed from the Set.

IObservableMap

Interface for observing changes in a Map. And you guess it, same type of behavior as the previous interfaces but for a Map.


For all of the above interfaces there are default mutable, or writable, implementations.

  • WritableValue

  • WritableList

  • WritableSet

  • WritableMap


By providing these implementations we, and any consumer, can use the observable implementations for any need that arises. The need doesn't have to be for binding a widget to a model, it can be any use case that you're wanting to provide notifications for a data structure. The idea is that the abstraction should feel common and also provide building blocks, just like the Collections Framework, for application needs.

In order to drive home the idea that the core of data binding doesn't necessarily have anything to do with UI the core interfaces and implementations that are UI unaware will reside in plug-in org.eclipse.core.databinding. The project is currently named org.eclipse.jface.databinding but will soon be renamed, see bug 153630 for details.