DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations
The Latest "Software Integration: The Intersection of APIs, Microservices, and Cloud-Based Systems" Trend Report
Get the report
  1. DZone
  2. Data Engineering
  3. Data
  4. Galileo: EMF-Databinding – Using the Properties API in Master Detail

Galileo: EMF-Databinding – Using the Properties API in Master Detail

Tom Schindl user avatar by
Tom Schindl
·
Jun. 17, 09 · Interview
Like (0)
Save
Tweet
Share
6.30K Views

Join the DZone community and get the full member experience.

Join For Free

in the last blog entry we saw how to set up a the left part of our example application which is a jface-treeviewer. this blog article deals with the upper right part of the application


detail_area
who allows the user to edit the data of the selected project in the treeviewer which is a very common use case in databinding and called master-detail-scenario where the treeviewer acts as the master and the upper right area as the detail part .

creation of the master observable

looking at the api for master-detail
master detail api
we recognize that we need to have a master-observablevalue when creating the detail-observablevalue so before we can proceed we need to go back to the projectexplorerpart who is responsible for the treeviewer creation and make it provide us an observablevalue .
most of the time the creation of the master observable is quite easy because the only thing one needs to do is to observe the current selection like this:

private iobservablevalue createmasterobs(viewer viewer) {
return viewerproperties.singleselection().observe(viewer);
}

but in our case it’s a bit more difficult because our tree has different object types ( project and committership ) but our detail can only handle project s which means we can’t use the selection directly but have to add a mediator in between.

the mediator we are useing is a writablevalue which we declare in the class body:


/**
* part responsible for rendering the project tree
*/
public class projectexplorerpart
{
// ...
private iobservablevalue master = new writablevalue();
// ...
}

afterwards we observe the treeviewer-selection and adjust the writablevalue manually:


private treeviewer init(composite parent,
foundation foundation)
{
treeviewer viewer = new treeviewer(parent);

// ...

iobservablevalue treeobs =
viewerproperties.singleselection().observe(viewer);
treeobs.addvaluechangelistener(new ivaluechangelistener()
{
public void handlevaluechange(valuechangeevent event) {
if (event.diff.getnewvalue() instanceof project) {
project pr = (project)event.diff.getnewvalue();
master.setvalue(pr);
} else if (event.diff.getnewvalue() != null) {
committership cm =
(committership)event.diff.getnewvalue();
master.setvalue(cm.getproject());
}
}
});

// ...
}

create the detail part

binding with default conversion

we can now go on at the upper right side and use the obervable to bind our detail controls. all the code shown below resides in projectformareapart . for standard text controls where no conversion is needed this is straight forward:


private void createformarea(
iviewsite site,
final composite parent,
formtoolkit toolkit,
final imodelresource resource,
observablesmanager manager,
final iobservablevalue master)
{
final editingdomain editingdomain =
resource.geteditingdomain();
ctx = new emfdatabindingcontext();

// ...

iwidgetvalueproperty prop =
widgetproperties.text(swt.modify);

final iemfvalueproperty shortprop =
emfeditproperties.value(
editingdomain,
projectpackage.literals.project__shortname
);

toolkit.createlabel(body, "&short name");
text t = toolkit.createtext(body, "");
t.setlayoutdata(
new griddata(swt.fill, swt.default, true, false, 2, 1)
);
ctx.bindvalue(
prop.observedelayed(400, t),
shortprop.observedetail(master)
);
// ...
}

though as mentioned above it is straight forward there are still some things to notice:

  • emfdatabindingcontext : is needed because iemfobservables are exposing the estructuralfeature as their value type but the default converters (e.g. string to integer , integer to string ) who are created by the standard databindingcontext work on top of java.lang.class informations. emfdatabindingcontext uses default emf conversion strageties and applies them instead.
  • iwidgetvalueproperty.observedelayed(widget,int) : this is an important detail to improve the users undo/redo experience because if not used every key stroke would result in an attribute change which would result in an undo/redo command. iwidgetvalueproperty.observedelayed() instructs the created observable to wait for 400 milliseconds before informing syncing the value back to the model and if another change happens within this time to cancel the update process and wait for another 400 milliseconds resulting in far less model changes and an improved undo/redo experience.

binding with customized conversion

now that we learned how to bind a text-widget to a string -property let’s take a look at another slightly more interesting binding use case – binding a text-widget to a date -property which involves data conversion from date to string and string to date . emfdatabindingcontext comes with a standard implementation for this but our users might not expect to see a datetime-value including timezone informations when editing a date. so the first thing we need to do is create an improved date to string and string to date converter:

/**
* convert a string to a date
*/
public class stringtodateconverter extends converter
{
private list<dateformat> formats =
new arraylist<dateformat>();

private string message;

/**
* new converter
* @param message message when conversion fails
*/
public stringtodateconverter(string message)
{
super(string.class, date.class);
this.message = message;
formats.add(
dateformat.getdateinstance(dateformat.short)
);
formats.add(new simpledateformat("yyyy-mm-dd"));
}

public object convert(object fromobject)
{
if (fromobject != null
&& fromobject.tostring().trim().length() == 0)
{
return null;
}

for (dateformat f : formats)
{
try
{
return f.parse(fromobject.tostring());
}
catch (parseexception e)
{
// ignore
}
}

throw new runtimeexception(message);
}
}

to implement such a converter one normally inherits from a given base class named converter and implements the convert -method. in this case the value conversion is done useing java’s dateformat -classes. as a special addon an empty string is converted into a null value. this converter is used as at the target to model side converting the value entered into the ui into the model type. the converter for the other side looks like this:

/**
* convert a date to a string
*/
public class datetostringconverter extends converter
{
private dateformat format =
dateformat.getdateinstance(dateformat.short);

/**
* new converter
*/
public datetostringconverter()
{
super(date.class, string.class);
}

public object convert(object fromobject)
{
if (fromobject == null)
{
return "";
}
return format.format(fromobject);
}
}

to teach eclipse-databinding to use our custom converters instead of the default ones we simply need to set them on the updatevaluestrategy s passed to databindingcontext.bindvalue(iobservablevalue,iobservablevalue,updatevaluestrategy,updatevaluestrategy) . setting such a custom conversion strategy though leads to many lines of code you repeat in many places. what i do in my project to reduce this amout of code is to move the creation of a common updatevaluestrategy like string-date-string into a factory like this:

public class updatestrategyfactory
{
public static emfupdatevaluestrategy stringtodate(
string message)
{
emfupdatevaluestrategy strat =
new emfupdatevaluestrategy();

stringtodateconverter c =
new stringtodateconverter(message);

strat.setconverter(c);
return strat;
}

/**
* create an update strategy which converts a date
* to a string
* @return the update strategy
*/
public static emfupdatevaluestrategy datetostring()
{
emfupdatevaluestrategy strat =
new emfupdatevaluestrategy();
datetostringconverter c = new datetostringconverter();
strat.setconverter(c);
return strat;
}
}

resulting into binding code like this:

iemfvalueproperty mprop = emfeditproperties.value(
editingdomain,
projectpackage.literals.project__end
);

toolkit.createlabel(body, "end date");
text t = toolkit.createtext(body, "");
t.setlayoutdata(
new griddata(swt.fill, swt.default, true, false, 2, 1)
);
ctx.bindvalue(
prop.observedelayed(400, t),
mprop.observedetail(master),
updatestrategyfactory.stringtodate(
nlsmessages.projectadminviewpart_enddatenotparseable
),
updatestrategyfactory.datetostring());

binding with customized conversion and validation

but there’s even more you can do with the updatevaluestrategy – you ensure for example that an a project is not allowed to have an empty == null start date by setting an ivalidator on the target to model updatevaluestrategy . once more this method is part of the updatestrategyfactory introduced above.

/**
* create an update strategy which converts a string to
* a date and ensures that the date value on the
* destinations is not set to null
* @param convertmessage the message shown when
* the conversion fails
* @param validationmessage the message when the
* value is set to null
* @return the update strategy
*/
public static emfupdatevaluestrategy stringtodatenotnull(
string convertmessage, final string validationmessage)
{
emfupdatevaluestrategy strat =
stringtodate(convertmessage);

strat.setbeforesetvalidator(new ivalidator()
{
public istatus validate(object value)
{
if (value == null)
{
return new status(
istatus.error,
activator.plugin_id,
validationmessage
);
}
return status.ok_status;
}
});
return strat;
}

and is used like this

iemfvalueproperty mprop = emfeditproperties.value(
editingdomain,
projectpackage.literals.project__start
);
toolkit.createlabel(body, "start date");
text t = toolkit.createtext(body, "");
t.setlayoutdata(
new griddata(swt.fill, swt.default, true, false, 2, 1)
);
ctx.bindvalue(
prop.observedelayed(400, t),
mprop.observedetail(master),
// custom conversion
updatestrategyfactory.stringtodatenotnull(
nlsmessages.projectadminviewpart_startdatenotparseable,
"start date must not be null"
),
updatestrategyfactory.datetostring()
);

ensuring that the start date is not set to null .

binding to a list of values

the last binding presented is how to bind the value of a multi value attribute to to a table-widget which used as a list in this case.

iemflistproperty mprop = emfeditproperties.list(
editingdomain,
projectpackage.literals.project__projectleads);

toolkit.createlabel(body, "project leads").setlayoutdata(
new griddata(swt.top, swt.default, false, false)
);

table c = toolkit.createtable(
body,
swt.multi | swt.full_selection
| swt.h_scroll | swt.v_scroll | swt.border
);

final tableviewer tv = new tableviewer(c);
tv.setlabelprovider(new columnlabelprovider()
{
@override
public string gettext(object element)
{
person p = (person)element;
return p.getlastname() + ", " + p.getfirstname();
}
});
tv.setcontentprovider(new observablelistcontentprovider());
tv.setinput(mprop.observedetail(master));

the code shown here is not much different from the one showing how to setup a treeviewer though much simpler for a none hierarchical widget. we are also using a default labelprovider here instead of one who observes the attributes shown. now let’s take a look at the dialog which gets opened when the add … button is pressed.
filter dialog
the eclipse-rcp-framework provides a dialog implementation named filtereditemsselectiondialog we can subclass and customize it

/**
* dialog to find persons
*/
public class personfilterdialog extends
filtereditemsselectiondialog
{
private final imodelresource resource;

/**
* create a new dialog
* @param shell the parent shell
* @param resource the model resource
*/
public personfilterdialog(shell shell,
imodelresource resource)
{
super(shell);
this.resource = resource;

setlistlabelprovider(new labelprovider()
{
@override
public string gettext(object element)
{
if (element == null)
{
return "";
}
return personfilterdialog.this.gettext(
(person)element
);
}
});

setdetailslabelprovider(new labelprovider()
{
@override
public string gettext(object element)
{
if (element == null)
{
return "";
}
return personfilterdialog.this.gettext(
(person)element
);
}
});
}

private string gettext(person p)
{
return p.getlastname() + " " + p.getfirstname();
}

@override
protected istatus validateitem(object item)
{
return status.ok_status;
}

@override
protected comparator< ? > getitemscomparator()
{
return new comparator<person>()
{

public int compare(person o1, person o2)
{
return gettext(o1).compareto(gettext(o2));
}
};
}

@override
public string getelementname(object item)
{
person p = (person)item;
return gettext(p);
}

@override
protected idialogsettings getdialogsettings()
{
idialogsettings settings = activator.getdefault()
.getdialogsettings().getsection("committerdialog");

if (settings == null)
{
settings = activator.getdefault()
.getdialogsettings()
.addnewsection("committerdialog");
}
return settings;
}

@override
protected void fillcontentprovider(
abstractcontentprovider contentprovider,
itemsfilter itemsfilter,
iprogressmonitor progressmonitor) throws coreexception
{
for (person p : resource.getfoundation().getpersons())
{
if (progressmonitor.iscanceled())
{
return;
}

contentprovider.add(p, itemsfilter);
}
}

@override
protected itemsfilter createfilter()
{
return new itemsfilter()
{

@override
public boolean isconsistentitem(object item)
{
return true;
}

@override
public boolean matchitem(object item)
{
person p = (person)item;
return matches(p.getlastname() + " "
+ p.getfirstname()
);
}

};
}

@override
protected control createextendedcontentarea(
composite parent)
{
return null;
}
}

and use it in our code like this

button b = toolkit.createbutton(
buttoncontainer,
"add ...",
swt.flat);

gd = new griddata(swt.default, swt.default);
gd.horizontalalignment = swt.fill;
b.setlayoutdata(gd);
b.addselectionlistener(new selectionadapter()
{
@override
public void widgetselected(selectionevent e)
{
personfilterdialog dialog = new personfilterdialog(
parent.getshell(),
resource
);

if (dialog.open() == idialogconstants.ok_id)
{
command cmd = addcommand.create(
editingdomain,
master.getvalue(),
projectpackage.literals.project__projectleads,
dialog.getfirstresult());
if (cmd.canexecute())
{
resource.executecmd(cmd);
}
}
}
});

that’s it for today in the next part we’ll take a look at setting up of tableviewer with different columns and how the nice validation stuff is working.

from http://tomsondev.bestsolution.at

API master Data Types Strings Binding (linguistics) Galileo (operating system)

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • Monolithic First
  • What Are the Benefits of Java Module With Example
  • DevOps for Developers: Continuous Integration, GitHub Actions, and Sonar Cloud
  • Solving the Kubernetes Security Puzzle

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: