Tom has posted 7 posts at DZone. View Full User Profile

Setting up a TreeViewer with EMF-Databinding

06.10.2009
| 10869 views |
  • submit to reddit
With this article we are starting to write our RCP-Application which uses Eclipse-Databinding. I’m not going into detail how you create an RCP-Application (I simply use the PDE-Wizard) but assume that you are familiar with this kind of thing.

Still I’d like to describe the application design and the reason behind decisions I made a bit. The first and most important thing is that I’m not a fan of EditorPart and I think for none-file resources the IEditorInput stuff simply doesn’t make a lot of sense so all applications I’ve written since I develop RCP are ViewParts.

A misconception I often see when I say that my RCP-Applications are free from editors is that people ask: “How are you going to be part of Workbench-Dirty/Save-Lifecyle if you use a ViewPart?”.

The answer is quite simple the only thing you have to do is to make your ViewPart implement ISaveablePart2 and then your view is part of the workbench-dirty state lifecycle.

Application Parts

The screen shot from above shows that how the applications UI code is broken up in smaller logical parts:

  • ProjectAdminViewPart:
    This is the main UI area which is subclassing ViewPart and registered into the workbench usin the views-Extension-Point.
  • ProjectExplorerPart:
    This class makes up the left part of the application showing a TreeViewer to select project, create subprojects, … . The internals of this part are described in more detail in this blog post later on.
  • ProjectFormAreaPart:
    This class makes up the right upper part showing a set of input controls who act as a detail part of the ProjectExplorerPart. The internals of this part are explained in detail in Part 4 of this blog series.
  • ProjectCommittersPart:
    This class makes up the right lower part showing a TableViewer. The internals of this part are explained in detail in Part 5 of this blog series.

Before we dive into the main topic of this blog post I’d like to show some interesting code found inside the ProjectAdminViewPart because it shows some interesting RCP features and introduces a small but often forgotten databinding stuff leading to memory leaks in conjunction with databinding.

Restoring view state information

The first interesting thing is that the content of the main area is created using a SashForm to give the user the possibility to define how much space the left part should occupy. The user naturally expects that when he starts the application the next time that the UI comes up in the same state it’s been when shutting down so we need to store the sash-weights when shutting down and restore them on the next start up.

The Eclipse-RCP-Framework provides a possibility to make this happen by using an IMemento to persist informations across application launches the only thing you to do is to use the API:

public class ProjectAdminViewPart
extends ViewPart implements ISaveablePart2
{
private float divider = 0.2f;
private SashForm sashForm;

@Override
public void init(final IViewSite site, IMemento memento)
throws PartInitException
{
super.init(site, memento);
if (memento != null
&& memento.getFloat(DIVIDER_KEY) != null)
{
divider = memento.getFloat(DIVIDER_KEY);
}

listener = new PartListenerImpl(site);
site.getPage().addPartListener(listener);
}

@Override
public void saveState(IMemento memento)
{
super.saveState(memento);
int total = sashForm.getWeights()[0]
+ sashForm.getWeights()[1];
memento.putFloat(
DIVIDER_KEY,
sashForm.getWeights()[0] * 1.f / total);
}

@Override
public void createPartControl(Composite parent)
{
// ...
sashForm = new SashForm(parent, SWT.HORIZONTAL);
// ...
int left = (int)(100 * divider);
sashForm.setWeights(new int []{ left, 100 - left });
}
}

Avoid listener leaking

There’s something I very often see in databinding code from various people (even those who have a lot of databinding knowledge) is that they are not diposing their observables once not more needed which leads to memory leaks. This means that we have to remember all observables created and dispose them manually when not needed any more.

An exception to this are observables created for SWT-Widgets because they dispose themselves when the SWT-Widget is disposed but for all others the rule is the one who created it has to dispose it. Databinding provides a class named ObservablesManager which helps you with this.

In 3.5 EMF-Databinding added new API to ObservableManager which would make collecting observables very easy but apparently a bug was found very late in the release cycle and so the API is not useable currently. The API in question is used like this:

ObservablesManager mgr = new ObservablesManager();
mgr.runAndCollect(new Runnable()
{
public void run()
{
// your code which creates observables
}
});

Though the default API is broken for EMF-Databinding we can add an easy fix so that the API at least collects all IEMFObservable so that we only have to deal with things like ComputedValue and such stuff because as stated above SWT-Observables dispose themselves when the SWT-Widget is disposed.

So our code looks like this:

  @Override
public void createPartControl(Composite parent)
{
// ...
/*
* Track the creation of observables so that we
* don't leak listeners when the view part is closed
*/
mgr = new EMFObservablesManager();
defaultMgr = new ObservablesManager();
mgr.runAndCollect(new Runnable()
{

public void run()
{
projectExplorer = new ProjectExplorerPart(
getViewSite(),
sashForm,
toolkit,
resource.getFoundation(),
defaultMgr);

projectDataForm = new ProjectFormAreaPart(
getViewSite(),
sashForm,
toolkit,
resource,
defaultMgr,
projectExplorer.getProjectObservable());
}
});
// ...
}

Setting up a TreeViewer
Enough about the generic stuff and let’s now concentrate on the main topic of this blog.
Project Explorer
Though I guess most of you know how to set up a standard TreeViewer here a short description:

  1. Set a CellLabelProvider: Responsible to translate the model object into a visual representation (text,colors,image,…). The CellLabelProvider-API was introduced in 3.3 and on top it JFace provides many cool new features like StyledText-Support (different fonts and colors in one cell) and ToolTip-Support. It’s worth revisiting your old code and replace LabelProvider through CellLabelProvider subclasses
  2. Set an ITreeContentProvider: Responsible to translate the input into a tree-structure.
  3. Set an input: Any input the ITreeContentProvider can handle

Createing the content provider
The Eclipse-RCP-Platform comes with a plugin named org.eclipse.jface.databinding which provides databinding support and implementation classes for SWT and JFace controls. One of those classes is ObservableListTreeContentProvider which is a class implementing the ITreeContentProvider and makes setting up an observed tree quite easily.

The ObservableListTreeContentProvider can’t do all the work on its own but expects us to provide it helper classes:

  • TreeFactory: responsible to create IObservableLists to observe the childitems of a treenode
  • TreeStructureAdvisor: responsible to provide informations on how to access the model parent of an item and helping to make the decision if an item has child-items

Looking at the picture above shows us that our structure is a bit of an virtual one when we compare it to the domain instance where we would only see the project-subprojects relation on the first sight.
Project Class
Our viewer though displays to different multi-value features as children in the Tree:

  • subprojects whose elements are of type Project
  • committerships whose elements are of type CommitterShip

Displaying different Object-Types was not supported in 3.4 and is a new feature in the new Databinding implementation as well as the possibility to combine 2 multi-value features into a single observable list which is for me personally one of the coolest features in the Properties-API.

  private static class TreeFactoryImpl
implements IObservableFactory
{
private IEMFListProperty multi = EMFProperties.multiList(
ProjectPackage.Literals.PROJECT__SUBPROJECTS,
ProjectPackage.Literals.PROJECT__COMMITTERS);

public IObservable createObservable(final Object target)
{
if (target instanceof IObservableList)
{
return (IObservable)target;
}
else if (target instanceof Project)
{
return multi.observe(target);
}

return null;
}
}

private static class TreeStructureAdvisorImpl
extends TreeStructureAdvisor
{
@Override
public Object getParent(Object element)
{
if (element instanceof Project)
{
return ((Project)element).getParent();
}

return null;
}

@Override
public Boolean hasChildren(Object element)
{
if (element instanceof Project
&& (
((Project)element).getCommitters().size() > 0
|| ((Project)element).getSubprojects().size() > 0
)
)
{
return Boolean.TRUE;
}
return super.hasChildren(element);
}
}

and used like this

private TreeViewer init(Composite parent,
Foundation foundation)
{
TreeViewer viewer = new TreeViewer(parent);
ObservableListTreeContentProvider cp =
new ObservableListTreeContentProvider(
new TreeFactoryImpl(),
new TreeStructureAdvisorImpl()
);
viewer.setContentProvider(cp);

// rest of viewer setup
}

Creating the CellLabelProvider

To get such a nice looking tree we can’t use a simple CellLabelProvider (e.g. ColumnLabelProvider is a simple implementation of it) but need one with more features. Another CellLabelProvider implementation is the StyledCellLabelProvider which uses owner-draw to draw StyledText-Strings in the tree.

JFace-Databinding doesn’t provide an implementation for StyledCellLabelProvider out of the box so I had to write my own one but that’s not too hard. The only thing which has to be done is to attach a listener to IObservableMap(s) to observe attributes of all tree elements and update the viewer if one of them changes.

private class TreeLabelProviderImpl
extends StyledCellLabelProvider
{
private IMapChangeListener mapChangeListener =
new IMapChangeListener()
{
public void handleMapChange(MapChangeEvent event)
{
Set<?> affectedElements =
event.diff.getChangedKeys();
if (!affectedElements.isEmpty())
{
LabelProviderChangedEvent newEvent =
new LabelProviderChangedEvent(
TreeLabelProviderImpl.this,
affectedElements.toArray()
);
fireLabelProviderChanged(newEvent);
}
}
};

public TreeLabelProviderImpl(
IObservableMap... attributeMaps)
{
for (int i = 0; i < attributeMaps.length; i++)
{
attributeMaps[i].addMapChangeListener(
mapChangeListener
);
}
}

@Override
public String getToolTipText(Object element)
{
return "#dummy#";
}

@Override
public void update(ViewerCell cell)
{
if (cell.getElement() instanceof Project)
{
Project p = (Project)cell.getElement();

StyledString styledString = new StyledString(
p.getShortname()!=null ? p.getShortname():"*noname*",
null
);
String decoration = " (" +
p.getCommitters().size() + " Committers)";
styledString.append(
decoration,
StyledString.COUNTER_STYLER
);
cell.setText(styledString.getString());
cell.setImage(projectImage);
cell.setStyleRanges(styledString.getStyleRanges());
}
else if (cell.getElement() instanceof CommitterShip)
{
Person p = (
(CommitterShip)cell.getElement()
).getPerson();
String value = "*noname*";
if (p != null)
{
value = p.getLastname() + ", " + p.getFirstname();
}
StyledString styledString = new StyledString(
value, null);
cell.setText(styledString.getString());
cell.setForeground(
cell.getControl().getDisplay().getSystemColor(
SWT.COLOR_DARK_GRAY
)
);
cell.setImage(committerImage);
cell.setStyleRanges(styledString.getStyleRanges());
}
}
}

and used like this

private TreeViewer init(Composite parent,
Foundation foundation)
{
TreeViewer viewer = new TreeViewer(parent);
ObservableListTreeContentProvider cp =
new ObservableListTreeContentProvider(
new TreeFactoryImpl(),
new TreeStructureAdvisorImpl()
);
viewer.setContentProvider(cp);

IObservableSet set = cp.getKnownElements();
IObservableMap[] map = new IObservableMap [4];

map[0] = EMFProperties.value(
ProjectPackage.Literals.PROJECT__SHORTNAME
).observeDetail(set);

map[1] = EMFProperties.value(
ProjectPackage.Literals.PROJECT__COMMITTERS
).observeDetail(set);

map[2] = EMFProperties.value(
FeaturePath.fromList(
ProjectPackage.Literals.COMMITTER_SHIP__PERSON,
ProjectPackage.Literals.PERSON__FIRSTNAME)
).observeDetail(set);

map[3] = EMFProperties.value(
FeaturePath.fromList(
ProjectPackage.Literals.COMMITTER_SHIP__PERSON,
ProjectPackage.Literals.PERSON__LASTNAME)
).observeDetail(set);

viewer.setLabelProvider(new TreeLabelProviderImpl(map));
// Further viewer setup

The last step is to set the input of the viewer which has to be an IObservableList which is in our example all top-level projects who are restored in the foundation instance.

IEMFListProperty projects = EMFProperties.list(
ProjectPackage.Literals.FOUNDATION__PROJECTS
);
viewer.setInput(projects.observe(foundation));

There is even more code in the ProjectExplorerPart e.g. a customized ColumnViewerToolTipSupport to create the nice looking ToolTips when hovering items of the tree as well as some RCP-Code to allow plugin.xml to contribute a context menu but that’s standard JFace and RCP code.

From http://tomsondev.bestsolution.at

 

 

d

Published at DZone with permission of its author, Tom Schindl.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Tags:

Comments

Ayo Bakare replied on Thu, 2009/07/30 - 7:05pm

Thanks for this info Free Macbook | Free iPhone 3GS

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.