Over a million developers have joined DZone.

Galileo: EMF-Databinding – Part 5

· Java Zone

Navigate the Maze of the End-User Experience and pick up this APM Essential guide, brought to you in partnership with CA Technologies

Though the main focus of this article is going to be the usage of the properties API to setup a TableViewer there are some important parts I didn’t explain in Part 4 of this series, so this is Using the Properties API in Master Detail continued.

Showing Validation States

The first interesting thing is how to present validation errors to user. I’m personally not a friend of ControlDecorations but prefer to display the validation error in the form-header.
Detail Area Error
This means the header presents an aggregation of all binding states and because this a quite common thing Eclipse-Databinding has built in support for this kind of thing.

private void addStatusSupport(ObservablesManager mgr,
final DataBindingContext ctx)
{
AggregateValidationStatus aggregateStatus =
new AggregateValidationStatus(
ctx.getValidationStatusProviders(),
AggregateValidationStatus.MAX_SEVERITY
);

aggregateStatus.addValueChangeListener(
new IValueChangeListener()
{
public void handleValueChange(ValueChangeEvent event)
{
handleStateChange(
(IStatus)event.diff.getNewValue(),
ctx
);
}
});
mgr.addObservable(aggregateStatus);
}

The above aggregates the validation states of all validation status providers. By the default there’s a provider for binding states attached to a databinding context but one can attach also custom ones which opens an interesting opportunity e.g. when used inconjunction with the EMF-Validation framework (probably a topic for another Blog post in the next weeks).
The final task is to display the status to the user through the form header control like this

private void handleStateChange(IStatus currentStatus,
DataBindingContext ctx)
{
if (form.isDisposed() || form.getHead().isDisposed())
{
return;
}

if (currentStatus != null
&& currentStatus.getSeverity() != IStatus.OK)
{
int type = convertType(currentStatus.getSeverity());

List<IMessage> list = new ArrayList<IMessage>();
Iterator< ? > it = ctx.getValidationStatusProviders()
.iterator()
;

while (it.hasNext())
{
ValidationStatusProvider validationStatusProvider =
(ValidationStatusProvider)it.next()
;
final IStatus status = (IStatus)
validationStatusProvider.getValidationStatus()
.getValue()
;

if (!status.isOK())
{
list.add(new IMessage()
{
public Control getControl()
{
return null;
}

public Object getData()
{
return null;
}

public Object getKey()
{
return null;
}

public String getPrefix()
{
return null;
}

public String getMessage()
{
return status.getMessage();
}

public int getMessageType()
{
return convertType(status.getSeverity());
}
});
}
}
form.setMessage("Data invalid",
type,
list.toArray(new IMessage [0])
);
}
else
{
form.setMessage(null);
}
}

Master-Detail, Validation and null

That’s kind of a strange title but and not easy to track down but an undiscovered regression in Eclipse-Databinding since about 2 years and was discovered too late to get fixed in 3.5 timeframe.

In short the problem is that if you are producing an validation error in for a model attribute which initially had the value null the wrong value on the target (=UI-Widget) is not cleared when modifing master. Believe this sounds more complex than it is reality and bug 278301 holds an nice example to reproduce the behaviour.

Nonetheless we need a working solution for now because of the location of the problem we won’t see a fix until 3.6 ships which is 1 year from now. I’m demonstrating in the following code a possible work-around but there are also other solutions. My solution uses the AggregateValidationStatus-support and forces an target update when the master observable is changed and the current validation status is NOT OK.

public class Util
{
public static void masterDetailFixup(
final DataBindingContext ctx,
IObservableValue master)
{
final AggregateValidationStatus s =
new AggregateValidationStatus(
ctx,
AggregateValidationStatus.MAX_SEVERITY);

master.addChangeListener(new IChangeListener()
{

public void handleChange(ChangeEvent event)
{
IStatus status = (IStatus)s.getValue();
if (status != null && !status.isOK())
{
ctx.updateTargets();
}
}
});
}
}

Setting up a TableViewer with EMF-Databinding 

Let’s now take a look a the next topic for this post where we are going to set up a TableViewer using Eclipse-Databinding for JFace/SWT and EMF. The TableViewer is taking up the complete lower right part of the application
Table Viewer

In part 3 we saw how to setup a TreeViewer, in part 4 we saw how we are setting up a TableViewer without columns and as you’ll see setting up a TableViewer with multiple columns is very similar. Let’s take a look at the code first

private void init(IViewSite site,
CTabFolder folder,
DataBindingContext ctx,
EditingDomain editingDomain,
IObservableValue master)
{
final TableViewer viewer = new TableViewer(
folder, SWT.FULL_SELECTION
);

viewer.getTable().setHeaderVisible(true);
ObservableListContentProvider cp =
new ObservableListContentProvider()
;

{
IObservableMap[] attributeMap = new IObservableMap [2];
attributeMap[0] = EMFEditProperties.value(
editingDomain,
FeaturePath.fromList(
ProjectPackage.Literals.COMMITTER_SHIP__PERSON,
ProjectPackage.Literals.PERSON__LASTNAME
)
).observeDetail(cp.getKnownElements());

attributeMap[1] = EMFEditProperties.value(
editingDomain,
FeaturePath.fromList(
ProjectPackage.Literals.COMMITTER_SHIP__PERSON,
ProjectPackage.Literals.PERSON__FIRSTNAME
)
).observeDetail(cp.getKnownElements());

TableViewerColumn column = new TableViewerColumn(
viewer, SWT.NONE
);
column.getColumn().setText("Name");
column.getColumn().setWidth(150);
column.setLabelProvider(
new GenericMapCellLabelProvider(
"{0}, {1}",
attributeMap
)
);
}

{
IObservableMap attributeMap = EMFEditProperties.value(
editingDomain,
ProjectPackage.Literals.COMMITTER_SHIP__START
).observeDetail(cp.getKnownElements());

TableViewerColumn column = new TableViewerColumn(
viewer, SWT.NONE
);
column.getColumn().setText("Start");
column.getColumn().setWidth(100);
column.setLabelProvider(
new GenericMapCellLabelProvider(
"{0,date,short}",
attributeMap
)
);
}

{
IObservableMap attributeMap = EMFEditProperties.value(
editingDomain,
ProjectPackage.Literals.COMMITTER_SHIP__END
).observeDetail(cp.getKnownElements());

TableViewerColumn column = new TableViewerColumn(
viewer, SWT.NONE
);
column.getColumn().setText("End");
column.getColumn().setWidth(100);
column.setLabelProvider(
new GenericMapCellLabelProvider(
"{0,date,short}",
attributeMap
)
);
}

IListProperty prop = EMFEditProperties.list(
editingDomain,
ProjectPackage.Literals.PROJECT__COMMITTERS
);
viewer.setContentProvider(cp);
viewer.setInput(prop.observeDetail(master));

MenuManager mgr = new MenuManager();
mgr.add(
new Action(
"Hide historic committers",
IAction.AS_CHECK_BOX
)
{
@Override
public void run()
{
if (isChecked())
{
viewer.addFilter(new ViewerFilterImpl());
}
else
{
viewer.setFilters(new ViewerFilter [0]);
}
}
});

viewer.getControl().setMenu(
mgr.createContextMenu(viewer.getControl())
);
site.registerContextMenu(
Activator.PLUGIN_ID + ".committers", mgr, viewer);
}

The only thing not already explained before is a special type of CellLabelProvider which allows one to use MessageFormat-Syntax to specify how the label is constructed. This label provider is not part of the JFace-Databinding implementation but written by me but it’s quite easy to implement.

public class GenericMapCellLabelProvider
extends ObservableMapCellLabelProvider
{
private IObservableMap[] attributeMaps;
private String messagePattern;

/**
* Create a new label provider
* @param messagePattern the message pattern
* @param attributeMaps the values to observe
*/
public GenericMapCellLabelProvider(
String messagePattern,
IObservableMap... attributeMaps)
{
super(attributeMaps);
this.messagePattern = messagePattern;
this.attributeMaps = attributeMaps;
}

@Override
public void update(ViewerCell cell)
{
Object element = cell.getElement();
Object[] values = new Object [attributeMaps.length];
int i = 0;
for (IObservableMap m : attributeMaps)
{
values[i++] = m.get(element);
if (values[i - 1] == null)
{
cell.setText("");
return;
}
}
cell.setText(
MessageFormat.format(messagePattern, values)
);
}
}

That’s all we need to have a working TableViewer though there’s even more one can do with the Databinding-API e.g. Inline-Editing is supported and there are helpers to setup viewers in even less lines of code.

From http://tomsondev.bestsolution.at

 

 

Thrive in the application economy with an APM model that is strategic. Be E.P.I.C. with CA APM.  Brought to you in partnership with CA Technologies.

Topics:

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}