Copyright © 2006, 2008 Marc R. Hoffmann
 Eclipse Corner Article

 

Eclipse Workbench: Using the Selection Service

Summary
The selection service provided by the Eclipse workbench allows efficient linking of different parts within the workbench window. Knowing and using the existing selection mechanisms gives your plug-ins a clean design, smoothly integrates them into the workbench and opens them for future extensions.

By Marc R. Hoffmann, Mountainminds GmbH & Co. KG, hoffmann@mountainminds.com
April 14, 2006, last update August 28, 2008


Introduction

The Eclipse workbench is a powerful UI framework for IDEs and also other applications. It provides many services for a highly integrated and extensible user interfaces. One of the integration aspects are view parts that provide additional information for particular objects and update their content automatically whenever such objects are selected somewhere in the workbench window. For example the "Properties" view behaves in this way: Wherever an element is selected in the workbench this view lists the properties of that element.

Other aspects of the workbench like the enablement of global actions may also depend on the current selection.

Instead of implementing "hard-wired communication" plug-ins can rely on the so called selection service. It decouples parts where items can be selected from others reacting on selection changes.

This article outlines the functionality and usage of the selection service. A provided sample plug-in demonstrates the workbench selection behaviour and can also serve for debugging purposes.

The Big Picture

Each workbench window has its own selection service instance. The service keeps track of the selection in the currently active part and propagates selection changes to all registered listeners. Such selection events occur when the selection in the current part is changed or when a different part is activated. Both can be triggered by user interaction or programmatically.

The view where elements or text is selected does not need to know who is interested in the selection. Therefore we may create new views that depend on the selection of an existing view – without changing a single line of code in that view.

The next sections explain who provides what kind of selections to whom.

What can be selected?

From the users point of view a selection is a set of highlighted entries in a viewer like a table or tree widget. A selection can also be a piece of text in an editor. Behind the scene each visual element in the workbench is represented by a Java object. The MVC implementation of JFace maps between the domain model objects and the visual representations.

Internally a selection is a data structure holding the model objects which corresponds to the graphical elements selected in the workbench. As pointed out before there are two fundamental different kinds of selections:

Both can be empty, i.e. a empty list or a text string with zero length. Following the typical Eclipse philosophy these data structures are properly defined by interfaces:

The IStructuredSelection refers to a list of objects. A special form of this is the ITreeSelection where each object in the list is described by a path. A path is a list of objects, for example the objects from the root to the selected object in a tree view. ITextSelection and IMarkSelection describe a piece of selected text.

For convenience there are default implementations for these interfaces:

These implementations are used internally within the viewer when transforming low-level SWT events to ISelection objects. The implementations are also useful when elements should be selected programmatically by some application code, for example:

    ISelection sel = new StructuredSelection(presetElement);
    treeviewer.setSelection(sel);

Install the provided sample plug-in and check what kind of selections occur in different views. The "Workbench Selection" view shows the ISelection implementation class and the selected elements or text itself.

Telling the Workbench Window about Selections

All JFace viewers are so called selection providers. A selection provider implements the interface ISelectionProvider:

The different JFace viewers accept and propagate different kind of selections:

Viewer Selection Type
ComboViewer IStructuredSelection
ListViewer IStructuredSelection
TreeViewer IStructuredSelection, ITreeSelection
 +- CheckboxTreeViewer IStructuredSelection, ITreeSelection
TableViewer IStructuredSelection
 +- CheckboxTableViewer IStructuredSelection
TextViewer ITextSelection, IMarkSelection
 +- SourceViewer ITextSelection, IMarkSelection
     +- ProjectionViewer ITextSelection, IMarkSelection

Also custom viewers may serve as selection providers and implement the ISelectionProvider interface.

Any workbench part that holds a viewer should register this viewer as the selection provider with the respective view site:

    getSite().setSelectionProvider(tableviewer);

Even if you don't see a need for propagating your selection right now, this opens your plug-in for future extensions by you or by other plug-in implementers. If your view defines actions that depend on the current selection, setting a selection provider is also necessary to allow dynamic enablement of these actions.

Use the sample plug-in to check whether your views properly register selection providers.

Tracking the Current Selection

The workbench window is typically assembled in many parts, each coming with at least one viewer. The workbench keeps track about the currently selected part in the window and the selection within this part. Here starts the interesting part of the story: Plug-in implementations can access this information or register for notifications on selection changes.

Each workbench window has an ISelectionService implementation which allows tracking the current selection. A view part can obtain a reference to it through its site:

    getSite().getWorkbenchWindow().getSelectionService()

The selection service knows the current selection of the active part or of a part with a particular id:

    ISelection getSelection() 
    ISelection getSelection(String partId)

Typically views react on selection changes in the workbench window. In this case they better register an ISelectionListener to get notified when the window's current selection changes:

    void addSelectionListener(ISelectionListener listener)
    void removeSelectionListener(ISelectionListener listener)

Listeners registered in this way are notified when the selection in the active part is changed or when a different part is activated. Only selections within the active part are propagated. If the application is only interested in the selection of a particular part (independently of its actual activation) one may register the listener only for the respective part id:

    void addSelectionListener(String partId, ISelectionListener listener) 
    void removeSelectionListener(String partId, ISelectionListener listener) 

This works even if there is currently no part with such a id. As soon as the part is created its initial selection will be propagated to the listeners registered for it. When the part is disposed, the listener is passed a null selection if the listener implements INullSelectionListener (see section below).

Implementing a Selection Listener

The ISelectionListener is a simple interface with just one method. A typical implementation looks like this:

    private ISelectionListener mylistener = new ISelectionListener() {
        public void selectionChanged(IWorkbenchPart sourcepart, ISelection selection) {
        if (sourcepart != MyView.this &&
            selection instanceof IStructuredSelection) {
            doSomething(((IStructuredSelection) selection).toList());
            }
        }
    };

Depending on your requirements your listener implementation probably needs to deal with the following issues as shown in the code snippet above:

Don't mix the ISelectionListener interface up with ISelectionChangedListener used by JFace viewers to notify about selection changes.

Removing the Selection Listener

Don't forget to remove your selection listener when you can't handle events any more, e.g. when your view has been closed. The dispose() method is a good place to remove your listener:

    public void dispose() {
        ISelectionService s = getSite().getWorkbenchWindow().getSelectionService();
     s.removeSelectionListener(mylistener);
        super.dispose();
    }

More Selection Issues

Up to now we focused on the core functionality of the selection service, which covers most of the use cases. There are additional issues that might come into picture in implementation projects.

Post Selection

When navigating views the selection changes frequently - especially when the keyboard is used to scroll through long lists or the mouse is dragged over some text. This will lead to many unnecessary updates of the viewers registered as listeners to the selection service and may make your application less responsive.

So called post selection events will be send-out with a slight delay. All intermediate selections during the delay time are ignored; just the final selection is propagated. The ISelectionService has additional methods to register listeners for the delayed selection events:

    void addPostSelectionListener(ISelectionListener listener) 
    void removePostSelectionListener(ISelectionListener listener) 
    void addPostSelectionListener(String partId,
                                  ISelectionListener listener) 
    void removePostSelectionListener(String partId,
                                     ISelectionListener listener) 

To avoid performance issues viewers should typically register selection listeners this way.

The selection providers are responsible for sending out the delayed events and have to implement the IPostSelectionProvider interface if they support it – all JFace viewers do so.

Change the sample plug-in to listen to post selection events and check when they are sent.

INullSelectionListener

The call-back method selectionChanged() defined in ISelectionListener get the new selection as well as the originating part passed in as parameters:

    public void selectionChanged(IWorkbenchPart part, ISelection selection);

The INullSelectionListener interface extends ISelectionListener but does not declare any additional methods. It is a pure marker interface to indicate that implementers of the selectionChanged() method wants to bet notified even with null parameters. This is usefull when you need to know that there is no current selection simply due to the fact that there is no one who provides a selection. You step into this when:

Which Selection Service: Page or Window?

If you study the workbench API carefully you will find out that there are two selection services: The IWorkbenchPage is a ISelectionService. On the other hand the IWorkbenchWindow has a method getSelectionService(). Therefore e.g. registering a listener within a part implementation is possible in two ways:

    getSite().getWorkbenchWindow().getSelectionService().addSelectionListener(l);

or

    getSite().getPage().addSelectionListener(l);

Actually this is totally equivalent because since Eclipse 2.0 a workbench window is limited to a single page only. Just don't mix both selection services when adding and removing a listener as two different implementations sit behind it.

Multiple Selection Providers Within a Part

Be aware that the part's site accepts a single selection provider only, which should be registered within the createPartControl() method only:

    getSite().setSelectionProvider(provider);

Replacing the selection provider during the lifetime of the part is not properly supported by the workbench. If a part contains multiple viewers providing selections, like the "Java Hierarchy" view does, a intermediate ISelectionProvider implementation has to be provided that allows dynamically delegating to the currently active viewer within the part. As a starting point you may look into SelectionProviderIntermediate.java provided with this article.

What to do With the Selected Objects?

This article claims that the selection service helps to decouple views reacting on each others selection. But the view handling a selection still needs to deal with the selected objects to provide any useful functionality. Check out the adapter pattern provided by the Eclipse runtime core, which allows attaching new functionality to existing model objects or the other way round providing required functionality for newly contributed objects.

Actually our example plug-in does exactly this by using the workbench label provider which in turn relies on the IWorkbenchAdapter to retrieve icons and text labels for the listed objects. The same mechanism is utilized by the "Properties" view, see article "Take control of your properties" for details.

Example Plug-in

This article comes with a small example plug-in that demonstrates the explained techniques. Additionally the plug-in may serve for debugging your selection providers. The example contributes a new view "Workbench Selection" which simply mirrors the current selection in the workbench. It works for element selections as well as for text selections.

Getting and Running the Example

Download the plug-in com.mountainminds.eclipse.selectionsample_1.1.0.jar and import it into your workspace via the "Import..." wizard from the "File" menu. Select "Existing Project into Workspace" on the first wizard page. On the second page simply use the option "Select archive file" to import the downloaded archive. The fastest way to launch the example is right-clicking the plug-in project and select "Run As""Eclipse Application" in the context menu.

If don't want to play with the source code you can also drop the bundle into an Eclipse installation and use it directly.

From the menu of the launched workbench activate "Window""Show View""Other..." and select our view "Workbench Selection" in the category "Other". Now you can play around with selections in various workbench parts and see how our "Workbench Selection" view reflects these selections:

The same works for text selections:

The example is implemented in a single class SelectionView.java applying the techniques discussed in this article. When reading through this short piece of code you may note that:

Conclusion

The mechanisms provided by the Eclipse workbench are simple to use and powerful. Not using this mechanisms result in plug-ins that poorly integrate with the rest of the workbench and will be hard to extend. To avoid such pitfalls simply follow these rules when selections come into picture:

References

To discuss or report problems in this article see bug 112193.

Resources

This article come with the following resources: