Galileo: EMF-Databinding – Part 5
Join the DZone community and get the full member experience.
Join For Freethough 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.
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
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
Opinions expressed by DZone contributors are their own.
Comments