Interacting with the User

Exclusive offer: get 50% off this eBook here
Eclipse 4 Plug-in Development by Example Beginner's Guide

Eclipse 4 Plug-in Development by Example Beginner's Guide — Save 50%

How to develop, build, test, package, and release Eclipse plug-ins with features for Eclipse 3.x and Eclipse 4.x with this book and ebook

$26.99    $13.50
by Dr Alex Blewitt | August 2013 | Beginner's Guides Java Open Source

We need to interact with users and we can do this in multiple ways, from responding to mouse clicks to processing data-intensive operations in the background.

In this article by Alex Blewitt, the author of Eclipse 4 Plug-in Development by Example Beginner's Guide, we will cover:

  • Creating a menu in response to a user pop up
  • Adding a command and a handler in a menu
  • Using progress managers to report work
  • Adding actions to the progress manager
  • Showing errors and dealing with failure

(For more resources related to this topic, see here.)

Creating actions, commands, and handlers

The first few releases of the Eclipse framework provided Action as a means of contributing to menu items. These were defined declaratively via actionSets in the plugin.xml file, and many tutorials still reference those today. At the programming level, when creating views, Actions are still used to provide context menus programmatically.

They were replaced with commands in Eclipse 3, as a more abstract way of decoupling the operation of a command with its representation of the menu. To connect these two together, a handler is used.

E4: Eclipse 4.x uses the command's model, and decouples it further using the @Execute annotation on the handler class. Commands and views are hooked up with entries on the application's model.

Time for action – adding context menus

A context menu can be added to the TimeZoneTableView class and respond to it dynamically in the view's creation. The typical pattern for Eclipse 3 applications is to create a hookContextMenu() method, which is used to wire up the context menu operation with displaying the menu. A default implementation can be seen by creating an example view, or one can be created from first principles.

Eclipse menus are managed by a MenuManager. This is a specialized subclass of a more general ContributionManager, which looks after a dynamic set of contributions that can be made from other sources. When the menu manager is connected to a control, it responds in the standard ways for the platform for showing the menu (typically a context-sensitive click or short key). Menus can also be displayed in other locations, such as a view's or the workspace's coolbar (toolbar). The same MenuManager approach works in these different locations.

  1. Open the TimeZoneTableView class and go to the createPartControl() method.
  2. At the botom of the method, add a new MenuManager with the ID #PopupMenu and associate it to the viewer's control.

    MenuManager manager = new MenuManager("#PopupMenu");
    Menu menu = manager.createContextMenu(tableViewer.getControl());
    tableViewer.getControl().setMenu(menu);

  3. If the Menu is empty, the MenuManager won't show any content, so this currently has no effect. To demonstrate this, an Action will be added to the Menu. An Action has text (for rendering in the pop-up menu, or the menu at the top of the screen), as well as a state (enabled/disabled, selected) and a behavior. These are typically created as subclasses and (although the Action doesn't strictly require it) an implementaton of the run() method. Add this to the botom of the createPartControl() method.

    Action deprecated = new Action() {
    public void run() {
    MessageDialog.openInformation(null, "Hello", "World");
    }
    };
    deprecated.setText("Hello");
    manager.add(deprecated);

  4. Run the Eclipse instance, open the Time Zone Table View, and right-click on the table. The Hello menu can be seen, and when selected, an informational dialog is shown.

What just happened?

The MenuManager(with the id #PopupMenu) was bound to the control, which means when that particular control's context sensitive menu is invoked, the manager will be able to ask to display a menu. The manager is associated with a single Menu object (which is also stamped on the underlying control itself) and is responsible for updating the status of the menu.

Actions are deprecated. They are included here since examples on the Internet may have preferred references to them, but it's important to note that while they still work, the way of building user interfaces are with the commands and handlers, shown in the next section.

When the menu is shown, the actions that the menu contains are rendered in the order in which they are added. Action are usually subclasses that implement a run() method, which performs a certain operation, and have text which is displayed.

Action instances also have other metadata, such as whether they are enabled or disabled. Although it is tempting to override the access or methods, this behavior doesn't work—the setters cause an event to be sent out to registered listeners, which causes side effects, such as updating any displayed controls.

Time for action – creating commands and handlers

Since the Action class is deprecated, the supported mechanism is to create a command, a handler, and a menu to display the command in the menu bar.

  1. Open the plug-in manifest for the project, or double-click on the plugin.xml file.
  2. Edit the source on the plugin.xml tab, and add a definition of a Hello command as follows:

    <extension point="org.eclipse.ui.commands">
    <command name="Hello"
    description="Says Hello World"
    id="com.packtpub.e4.clock.ui.command.hello"/>
    </extension>

  3. This creates a command, which is just an identifier and a name. To specify what it does, it must be connected to a handler, which is done by adding the following extension:

    <extension point="org.eclipse.ui.handlers">
    <handler class=
    "com.packtpub.e4.clock.ui.handlers.HelloHandler"
    commandId="com.packtpub.e4.clock.ui.command.hello"/>
    </extension>

  4. The handler joins the processing of the command to a class that implements IHandler, typically AbstractHandler. Create a class HelloHandler in a new com.packtpub.e4.clock.ui.handlers package, which implements AbstractHandler(from the org.eclipse.core.commands package).

    public class HelloHandler extends AbstractHandler {
    public Object execute(ExecutionEvent event) {
    MessageDialog.openInformation(null, "Hello", "World");
    return null;
    }
    }

  5. The command's ID com.packtpub.e4.clock.ui.command.hello is used to refer to it from menus or other locations. To place the contribution in an existing menu structure, it needs to be specified by its locationURI, which is a URL that begins with menu:such as menu:window?after=additionsor menu:file?after=additions. To place it in the Help menu, add this to the plugin.xml file.

    <extension point="org.eclipse.ui.menus">
    <menuContribution allPopups="false"
    locationURI="menu:help?after=additions">
    <command commandId="com.packtpub.e4.clock.ui.command.hello"
    label="Hello"
    style="push">
    </command>
    </menuContribution>
    </extension>

  6. Run the Eclipse instance, and there will be a Hello menu item under the Help menu. When selected, it will pop up the Hello World message. If the Hello menu is disabled, verify that the handler extension point is defined, which connects the command to the handler class.

What just happened?

The main issue with the actions framework was that it tightly coupled the state of the command with the user interface. Although an action could be used uniformly between different menu locations, the Action superclass still lives in the JFace package, which has dependencies on both SWT and other UI components. As a result, Action cannot be used in a headless environment.

Eclipse 3.x introduced the concept of commands and handlers, as a means of separating their interface from their implementation. This allows a generic command (such as Copy) to be overridden by specific views. Unlike the traditional command design pattern, which provides implementation as subclasses, the command in Eclipse 3.x uses a final class and then a retargetable IHandler to perform the actual execution.

E4: In Eclipse 4.x, the concepts of commands and handlers are used extensively to provide the components of the user interface. The key difference is in their definition; for Eclipse 3.x, this typically occurs in the plugin.xml file, whereas in E4 it is part of the application model.

In the example, a specific handler was defined for the command, which is valid in all contexts. The handler's class is the implementation; the command ID is the reference.

The org.eclipse.ui.menus extension point allows menuContributions to be added anywhere in the user interface. To address where the menu can be contributed to, the location URIobject defines where the menu item can be created. The syntax for the URI is as follows:

  • menu: Menus begin with the menu: protocol (can also be toolbar:or popup:)
  • identifier: This can be a known short name (such as file, window, and help), the global menu (org.eclipse.ui.main.menu), the global toolbar (org.eclipse.ui.main.toolbar), a view identifier (org.eclipse. ui.views.ContentOutline), or an ID explicitly defined in a pop-up menu's registerContextMenu()call.
  • ?after(or before)=key: This is the placement instruction to put this after or before other items; typically additions is used as an extensible location for others to contribute to.

The locationURIallows plug-ins to contribute to other menus, regardless of where they are ultimately located.

Note, that if the handler implements the IHandler interface directly instead of subclassing AbstractHandler, the isEnabled() method will need to be overridden as otherwise the command won't be enabled, and the menu won't have any effect.

Time for action – binding commands to keys

To hook up the command to a keystroke a binding is used. This allows a key (or series of keys) to be used to invoke the command, instead of only via the menu. Bindings are set up via an extension point org.eclipse.ui.bindings, and connect a sequence of keystrokes to a command ID.

  1. Open the plugin.xml in the clock.uiproject.
  2. In the plugin.xml tab, add the following:

    <extension point="org.eclipse.ui.bindings">
    <key commandId="com.packtpub.e4.clock.ui.command.hello"
    sequence="M1+9"
    contextId="org.eclipse.ui.contexts.window"
    schemeId=
    "org.eclipse.ui.defaultAcceleratorConfiguration"/>
    </extension>

  3. Run the Eclipse instance, and press Cmd+ 9(for OS X) or Ctrl+ 9 (for Windows/Linux). The same Hello dialog should be displayed, as if it was shown from the menu. The same keystroke should be displayed in the Help menu.

What just happened?

The M1 key is the primary meta key, which is Cmd on OS X and Ctrl on Windows/Linux. This is typically used for the main operations; for example M1+ C is copy and M1+ V is paste on all systems. The sequence notation M1+ 9 is used to indicate pressing both keys at the same time.

The command that gets invoked is referenced by its commandId. This may be defined in the same plug-in, but does not have to be; it is possible for one application to provide a set of commands and another plug-in to provide keystrokes that bind them.

It is also possible to set up a sequence of key presses; for example, M1+ 9 8 7would require pressing Cmd+ 9 or Ctrl+ 9 followed by 8 and then 7 before the command is executed. This allows a set of keystrokes to be used to invoke a command; for example, it's possible to emulate an Emacs quit operation with the keybinding Ctrl + X Ctrl + Cto the quit command.

Other modifier keys include M2(Shift), M3(Alt/Option), and M4(Ctrl on OS X). It is possible to use CTRL, SHIFT, or ALT as long names, but the meta names are preferred, since M1tends to be bound to different keys on different operating systems.

The non-modifier keys themselves can either be single characters (A to Z), numbers (0to 9), or one of a set of longer name key-codes, such as F12, ARROW_UP, TAB, and PAGE_UP. Certain common variations are allowed; for example, ESC/ESCAPE, ENTER/RETURN, and so on.

Finally, bindings are associated with a scheme, which in the default case should be org.eclipse.ui.defaultAcceleratorConfiguration. Schemes exist to allow the user to switch in and out of keybindings and replace them with others, which is how tools like "vrapper" (a vi emulator) and the Emacs bindings that come with Eclipse by default can be used. (This can be changed via Window | Preferences | Keys menu in Eclipse.)

Time for action – changing contexts

The context is the location in which this binding is valid. For commands that are visible everywhere—typically the kind of options in the default menu—they can be associated with the org.eclipse.ui.contexts.windowcontext. If the command should also be invoked from dialogs as well, then the org.eclipse.ui.context.dialogAndWindowcontext would be used instead.

  1. Open the plugin.xml file of the clock.ui project.
  2. To enable the command only for Java editors, go to the plugin.xml tab, and modify the contextId as follows:

    <extension point="org.eclipse.ui.bindings">
    <key commandId="com.packtpub.e4.clock.ui.command.hello"
    sequence="M1+9"
    contextId="org.eclipse.ui.contexts.window"
    contextId="org.eclipse.jdt.ui.javaEditorScope"
    schemeId="org.eclipse.ui.defaultAcceleratorConfiguration"/>
    </extension>

  3. Run the Eclipse instance, and create a Java project, a test Java class, and an empty text file.
  4. Open both of these in editors. When the focus is on the Java editor, the Cmd + 9 or Ctrl+ 9 operation will run the command, but when the focus is on the text editor, the keybinding will have no effect.

Unfortunately, it also highlights the fact that just because the keybinding is disabled when in the Java scope, it doesn't disable the underlying command.

If there is no change in behavior, try cleaning the workspace of the test instance at launch, by going to the Run | Run... menu, and choosing Clear on the workspace. This is sometimes necessary when making changes to the plugin.xml file, as some extensions are cached and may lead to strange behavior.

What just happened?

Context scopes allow bindings to be valid for certain situations, such as when a Java editor is open. This allows the same keybinding to be used for different situations, such as a Format operation—which may have a different effect in a Java editor than an XML editor, for instance.

Since scopes are hierarchical, they can be specifically targeted for the contexts in which they may be used. The Java editor context is a subcontext of the general text editor, which in turn is a subcontext of the window context, which in turn is a subcontext of the windowAndDialogcontext.

The available contexts can be seen by editing the plugin.xml file in the plug-in editor; in the extensions tab the binding shows an editor window with a form:

Clicking on the Browse… button next to the contextId brings up a dialog, which presents the available contexts:

It's also possible to find out all the contexts programmatically or via the running OSGi instance, by navigating to Window | Show View | Console, and then using New Host OSGi Console in the drop-down menu, and then running the following code snippet:

osgi> pt -v org.eclipse.ui.contexts
Extension point: org.eclipse.ui.contexts [from org.eclipse.ui]
Extension(s):
-------------------
null [from org.eclipse.ant.ui]
<context>
name = Editing Ant Buildfiles
description = Editing Ant Buildfiles Context
parentId = org.eclipse.ui.textEditorScope
id = org.eclipse.ant.ui.AntEditorScope
</context>

null [from org.eclipse.compare]
<context>
name = Comparing in an Editor
description = Comparing in an Editor
parentId = org.eclipse.ui.contexts.window
id = org.eclipse.compare.compareEditorScope
</context>

Time for action – enabling and disabling the menu's items

The previous section showed how to hide or show a specific keybinding depending on the open editor type. However, it doesn't stop the command being called via the menu, or from it showing up in the menu itself. Instead of just hiding the keybinding, the menu can be hidden as well by adding a visibleWhenblock to the command.

The expressions framework provides a number of variables, including activeContexts, which contains a list of the active contexts at the time. Since many contexts can be active simultaneously, the active contexts is a list (for example, [dialogAndWindows,windows, textEditor, javaEditor]). So, to find an entry (in effect, a contains operation) an iterate operator with the equals expression is used.

  1. Open up the plugin.xml file, and update the the Hello command by adding a visibleWhen expression.

    <extension point="org.eclipse.ui.menus">
    <menuContribution allPopups="false"
    locationURI="menu:help?after=additions">
    <command commandId="com.packtpub.e4.clock.ui.command.hello"
    label="Hello" style="push">
    <visibleWhen>
    <with variable="activeContexts">
    <iterate operator="or">
    <equals value="org.eclipse.jdt.ui.javaEditorScope"/>
    </iterate>
    </with>
    </visibleWhen>
    </command>
    </menuContribution>
    </extension>

  2. Run the Eclipse instance, and verify that the menu is hidden until a Java editor is opened. If this behavior is not seen, run the Eclipse application with the clean argument to clear the workspace. After clearing, it will be necessary to create a new Java project with a Java class, as well as an empty text file, to verify that the menu's visibility is correct.

What just happened?

Menus have a visibleWhen guard that is evaluated when the menu is shown. If it is false, he menu is hidden.

The expressions syntax is based on nested XML elements with certain conditions. For example, an <and> block is true if all of its children are true, whereas an <or> block is true if one of its children is true. Variables can also be used with a property test using a combination of a <with> block (which binds the specified variable to the stack) and an <equals> block or other comparison.

In the case of variables that have lists, an <iterate> can be used to step through elements using either operator="or" or operator="and" to dynamically calculate enablement.

To find out if a list contains an element, a combination of <iterate> and <equals> operators is the standard pattern.

There are a number of variables that can be used in tests; they include the following variables:

  • activeContexts: List of context IDs that are active at the time
  • activeShell: The active shell (dialog or window)
  • activeWorkbenchWindow: The active window
  • activeEditor: The current or last active editor
  • activePart: The active part (editor or view)
  • selection: The current selection
  • org.eclipse.core.runtime.Platform: The Platform object

The Platform object is useful for performing dynamic tests using test, such as the following:

<test value="ACTIVE"
property="org.eclipse.core.runtime.bundleState"
args="org.eclipse.core.expressions"/>
<test
property="org.eclipse.core.runtime.isBundleInstalled"
args="org.eclipse.core.expressions"/>

Knowing if a bundle is installed is often useful; it's better to only enable functionality if a bundle is started (or in OSGi terminology, ACTIVE). As a result, use of isBundleInstalled has been replaced by the bundleState=ACTIVE tests.

Time for action – reusing expressions

Although it's possible to copy and paste expressions between places where they are used, it is preferable to re-use an identical expression.

  1. Declare an expression using the expression's extension point, by opening the plugin.xml file of the clock.uiproject.

    <extension point="org.eclipse.core.expressions.definitions">
    <definition id="when.hello.is.active">
    <with variable="activeContexts">
    <iterate operator="or">
    <equals value="org.eclipse.jdt.ui.javaEditorScope"/>
    </iterate>
    </with>
    </definition>
    </extension>

    If defined via the extension wizard, it will prompt to add dependency on the org.eclipse.core.expressions bundle. This isn't strictly necessary for this example to work.

  2. To use the definition, the enablement expressions needs to use the reference.

    <extension point="org.eclipse.ui.menus">
    <menuContribution allPopups="false"
    locationURI="menu:help?after=additions">
    <command commandId="com.packtpub.e4.clock.ui.command.hello"
    label="Hello" style="push">
    <visibleWhen>
    <with variable="activeContexts">
    <iterate operator="or">
    <equals value="org.eclipse.jdt.ui.javaEditorScope"/>
    </iterate>
    </with>
    <reference definitionId="when.hello.is.active"/>
    </visibleWhen>
    </command>
    </menuContribution>
    </extension>

  3. Now that the reference has been defined, it can be used to modify the handler as well, so that the handler and menu become active and visible together. Add the following to the Hellohandler in the plugin.xml file:

    <extension point="org.eclipse.ui.handlers">
    <handler class="com.packtpub.e4.clock.ui.handlers.Hello"
    commandId="com.packtpub.e4.clock.ui.command.hello">
    <enabledWhen>
    <reference definitionId="when.hello.is.active"/>
    </enabledWhen>
    </handler>
    </extension>

  4. Run the Eclipse application and exactly the same behavior will occur; but should the enablement change, it can be done in one place.

What just happened?

The org.eclipse.core.expressions extension point defined a virtual condition that could be evaluated when the user's context changes, so both the menu and the handler can be made visible and enabled at the same time. The reference was bound in the enabledWhen condition for the handler, and the visibleWhencondition for the menu.

Since references can be used anywhere, expressions can also be defined in terms of other expressions. As long as the expressions aren't recursive, they can be built up in any manner.

Time for action – contributing commands to pop-up menus

It's useful to be able to add contributions to pop-up menus so that they can be used by different places. Fortunately, this can be done fairly easily with the menuContribution element and a combination of enablement tests. This allows the removal of the Action introduced in the first part of this article with a more generic command and handler pairing.

There is a deprecated extension point—which still works in Eclipse 4.2 today—called objectContribution, which is a single specialized hook for contributing a pop-up menu to an object. This has been deprecated for some time, but often older tutorials or examples may refer to it.

  1. Open the TimeZoneTableView class and add the hookContextMenu()method as follows:

    private void hookContextMenu(Viewer viewer) {
    MenuManager manager = new MenuManager("#PopupMenu");
    Menu menu = manager.createContextMenu(viewer.getControl());
    viewer.getControl().setMenu(menu);
    getSite().registerContextMenu(manager, viewer);
    }

  2. Add the same hookContextMenu() method to the TimeZoneTreeView class.
  3. In the TimeZoneTreeView class, at the end of the createPartControl() method, call hookContextMenu(tableViewer).
  4. In the TimeZoneTableViewclass, at the end of the createPartControl() method, replace the call to the action with a call to hookContextMenu()instead:

    hookContextMenu(tableViewer);
    MenuManager manager = new MenuManager("#PopupMenu");
    Menu menu = manager.createContextMenu(tableViewer.getControl());

    tableViewer.getControl().setMenu(menu);
    Action deprecated = new Action() {
    public void run() {
    MessageDialog.openInformation(null, "Hello", "World");
    }
    };
    deprecated.setText("Hello");
    manager.add(deprecated);

  5. Running the Eclipse instance now and showing the menu results in nothing being displayed, because no menu items have been added to it yet.
  6. Create a command and a handler Show the Time.

    <extension point="org.eclipse.ui.commands">
    <command name="Show the Time" description="Shows the Time"
    id="com.packtpub.e4.clock.ui.command.showTheTime"/>
    </extension>
    <extension point="org.eclipse.ui.handlers">
    <handler class=
    "com.packtpub.e4.clock.ui.handlers.ShowTheTime"
    commandId="com.packtpub.e4.clock.ui.command.showTheTime"/>
    </extension>

  7. Create a class ShowTheTime, in the com.packtpub.e4.clock.ui.handlers package, which extends org.eclipse.core.commands.AbstractHandler, to show the time in a specific time zone.

    public class ShowTheTime extends AbstractHandler {
    public Object execute(ExecutionEvent event) {
    ISelection sel = HandlerUtil.getActiveWorkbenchWindow(event)
    .getSelectionService().getSelection();
    if (sel instanceof IStructuredSelection && !sel.isEmpty()) {
    Object value =
    ((IStructuredSelection)sel).getFirstElement();
    if (value instanceof TimeZone) {
    SimpleDateFormat sdf = new SimpleDateFormat();
    sdf.setTimeZone((TimeZone) value);
    MessageDialog.openInformation(null, "The time is",
    sdf.format(new Date()));
    }
    }
    return null;
    }
    }

  8. Finally, to hook it up, a menu needs to be added to the special locationURI popup:org.eclipse.ui.popup.any.

    <extension point="org.eclipse.ui.menus">
    <menuContribution allPopups="false"
    locationURI="popup:org.eclipse.ui.popup.any">
    <command label="Show the Time" style="push"
    commandId="com.packtpub.e4.clock.ui.command.showTheTime">
    <visibleWhen checkEnabled="false">
    <with variable="selection">
    <iterate ifEmpty="false">
    <adapt type="java.util.TimeZone"/>
    </iterate>
    </with>
    </visibleWhen
    </command>
    </menuContribution>
    </extension>

  9. Run the Eclipse instance, and open the Time Zone Table view or Time Zone Table view. Right-click on a TimeZone, and the command Show the Time will be displayed (that is, one of the leaves of the tree or one of the rows of the table). Select the command and a dialog should show the time.

What just happened?

The views and the knowledge of how to wire up commands in this article provided a unified means of adding commands, based on the selected object type. This approach of registering commands is powerful, because any time a time zone is exposed as a selection in the future it will now have a Show the Time menu added to it automatically.

The commands define a generic operation, and handlers bind those commands to implementations. The context-sensitive menu is provided by the pop-up menu extension point using the locationURI popup:org.eclipse.ui.popup.any. This allows the menu to be added to any pop-up menu that uses a MenuManager and when the selection contains a TimeZone. The MenuManager is responsible for listening to the mouse gestures to show a menu, and filling it with details when it is shown.

In the example, the command was enabled when the object was an instance of a TimeZone, and also if it could be adapted to a TimeZone. This would allow another object type (say, a contact card) to have an adapter to convert it to a TimeZone, and thus show the time in that contact's location.

Have a go hero – using view menus and toolbars

The way to add a view menu is similar to adding a pop-up menu; the locationURI used is the view's ID rather than the menu item itself. Add a Show the Time menu to the TimeZone view as a view menu.

Another way of adding the menu is to add it as a toolbar, which is an icon in the main Eclipse window. Add the Show the Time icon by adding it to the global toolbar instead.

To facilitate testing of views, add a menu item that allows you to show the TimeZone views with PlatformUI.getActiveWorkbenchWindow().getActivePage().showView(id).

Jobs and progress

Since the user interface is single threaded, if a command takes a long amount of time it will block the user interface from being redrawn or processed. As a result, it is necessary to run long-running operations in a background thread to prevent the UI from hanging.

Although the core Java library contains java.util.Timer, the Eclipse Jobs API provides a mechanism to both run jobs and report progress. It also allows jobs to be grouped together and paused or joined as a whole.

Eclipse 4 Plug-in Development by Example Beginner's Guide How to develop, build, test, package, and release Eclipse plug-ins with features for Eclipse 3.x and Eclipse 4.x with this book and ebook
Published: June 2013
eBook Price: $26.99
Book Price: $44.99
See more
Select your format and quantity:

Time for action – running operations in the background

If the command takes a long time to execute, the user interface can be blocked. This happens because there is only one user interface thread, and because the command is launched from the UI, it will run in the UI thread. Instead, long running operations should run in a background thread, and then once finished, be able to display the results instead. Clearly creating a new Thread(like the clock updates initially) or other techniques like a Timer would work. However, the Eclipse system has a mechanism to provide a Jobto do the work instead, or a UIJob to run in the context of the UI thread.

  1. Open the HelloHandler and go to the execute() method. Replace its contents with the following code snippet:

    public Object execute(ExecutionEvent event) {
    Job job = new Job("About to say hello") {
    protected IStatus run(IProgressMonitor monitor) {
    try {
    Thread.sleep(5000);
    } catch (InterruptedException e) {
    }
    MessageDialog.openInformation(null, "Hello", "World");
    return Status.OK_STATUS;
    }
    };
    job.schedule();
    return null;
    }

  2. Run the Eclipse instance, and click on the Help | Hello menu item. (It may be necessary to open a Java file to enable the menu). Open the Progress view, and a Job will be listed with About to say hello running. Unfortunately, an error dialog is then shown:

  3. This occurs because the Job runs on a non-UI background thread, so when the MessageDialog is shown an exception occurs. To fix this, instead of showing the MessageDialog directly, a second Job or Runnable can be created to run specifically on the UI thread. Replace the call to the MessageDialog with the following code snippet:

    MessageDialog.openInformation(null, "Hello", "World");
    Display.getDefault().asyncExec(new Runnable() {
    public void run() {
    MessageDialog.openInformation(null, "Hello", "World");
    }
    });

    This example uses the asyncExec()to run a runnable on the UI thread (similar to the SwingUtilities.invokeLater() method in Swing).

  4. Run the Eclipse instance, select the Hello menu, and after a five second pause, the dialog should be shown.

What just happened?

Every action in the Eclipse UI must run on the UI thread, so if the action takes a significant time to run, it will give the impression that the user interface is blocked or hung. The way to avoid this is to drop out of the UI thread before doing any long term work. Any updates that need to be done involving the UI should be scheduled back on the UI thread.

The example used both Job(a mechanism for scheduling named processes that can be monitored via the Progress view), as well as the display's asyncExec() method to launch the resulting message dialog.

Both of these situations require the use of inner classes, which can increase the verbosity of the code. With future versions of Java and use of lambda expressions this will be significantly reduced.

E4: Since E4 can use different renderers, the Display is not a good target for submitting background UIJob. Instead, use a UISynchronize instance to acquire the asyncExec()or syncExec()method.

Have a go hero – using a UI job

Instead of scheduling the UI notification piece as a Display.asyncExec() method, create it as a UIJob instead. This works in exactly the same way as a Job does, but you need to override the runInUIThread() method instead of the run()method. This may be useful when there is more UI interaction required, such as asking the user for more information.

Time for action – reporting progress

Normally when a Job is running, it is necessary to periodically update the user to let them know the state of progress. By default, if a Job provides no information, a generic busy indicator is shown. When a Job is executed, it is passed an IProgressMonitor object, which can be used to notify the user of progress (and provide a way to cancel the operation). A progress monitor has a number of tasks, each of which has a total unit of work that it can do. For jobs that don't have a known amount of work, UNKNOWN can be specified and it will be displayed in a generic busy indicator.

  1. Open the HelloHandler and go to the execute() method. In the run() method of the inner Job, add a beginTask() at the beginning, and a worked() method in the loop after each second's sleep, for five iterations. The code will look like the following:

    protected IStatus run(IProgressMonitor monitor) {
    try {
    monitor.beginTask("Preparing", 5000);
    for(int i=0;i<5;i++) {
    Thread.sleep(1000);
    monitor.worked(1000);
    }
    } catch (InterruptedException e) {
    } finally {
    monitor.done();
    }
    MessageDialog.openInformation(null, "Hello", "World");
    return Status.OK_STATUS;
    }

  2. Run the Eclipse instance, and open the Progress view by navigating to Windows | Show View | Other | General | Progress. Now, go to Help | Hello; the Progress view should show the progress as the sleep occurs.
  3. To make the reporting more accurate, report the status more frequently.

    for(int i=0;i<50;i++) {
    Thread.sleep(100);
    monitor.worked(100);
    }

  4. Run the Eclipse instance again. When the job is run via the Help | Hello menu, the status will be updated in the Progress view more frequently.

What just happened?

When running a Job, the progress monitor can be used to indicate how much work has been done. It must start with a beginTask() method—this gives both the total number of work units as well as a textual name that can be used to identify what's happening.

If the amount of work is unknown, use IProgressMonitor.UNKNOWN.

The unit scale doesn't really matter; it could have been 50 or 50,000. As long as the total number of work units add up, and they're appropriately used, it will give the user a good idea of the operation.

Don't just report based on the number of lines (or tasks). If there are four work items but the fifth one takes as long as the previous four, then the amount of work reported needs to be balanced; for example, provide a total of 8 units, with 1 unit for each of the first four and then the remaining four for the fifth item.

Finally, done()was called on the progress monitor. This signifies that the Job has been completed, and can be removed from any views that are reporting the status. This is wrapped inside a finally block to ensure that the monitor is completed even if the Job finishes abnormally (for example, if an exception occurs).

Time for action – dealing with cancellation

Sometimes the user will change their mind; they may have selected the wrong option, or something more important may have come up. The progress monitor allows for two-way communication; the user can signify when they want to cancel as well. There is a method, isCancelled(), which returns true if the user has signified in some way that he/she wishes the Job to finish early. Periodically checking this during the operation of the Job allows the user to cancel a long-running Job before it reaches the end.

  1. Modify the for loop in the HelloHandler to check on each iteration whether the monitor is cancelled or not.

    for(int i=0;i<50 && !monitor.isCanceled(); i++) {
    ...
    }
    if(!monitor.isCancelled()) {
    Display.getDefault().asyncExec(new Runnable() {…});
    }

  2. Run the Eclipse instance and click on the Hello command. This time, go into the Progress view and click on the red stop square next to the job. The job should cancel and the dialog showing the message shouldn't be shown:

What just happened?

Being responsive to the user is a key point in implementing plug-ins. If there are long running operations, make sure to check to see if the user has cancelled the operation—there's no point in tying up the CPU if the user doesn't want it to continue.

The monitor.isCancelled() method is generally implemented with a single field access, so calling it frequently often has no negative performance implications. Calling the isCancelled() method too many times is never noticed by users, however, not calling it enough certainly is noticed.

Time for action – using subtasks and subprogress monitors

When performing a set of operations, subtasks can give the user additional details about the state of the operation. Asubtask is merely a named message, which is displayed along with the task name in the Progress view.

  1. Add monitor.subTask()during the operation to give feedback.

    for (int i=0; i<50 && !monitor.isCanceled(); i++) {
    if(i==10) {
    monitor.subTask("Doing something");
    } else if (i==25) {
    monitor.subTask("Doing something else");
    } else if (i==40) {
    monitor.subTask("Nearly there");
    }
    Thread.sleep(100);
    monitor.worked(100);
    }

  2. Run the Eclipse instance, and look at the Progress view. The subtask should be shown underneath the status bar:

  3. When calling another method with a progress monitor, if the monitor is passed as it is, it can have undesirable effects. Add a new method, checkDozen(), to the Job of HelloHandler and add a condition in the for loop that breaks out if the number of execution reaches 12.

    protected IStatus run(IProgressMonitor monitor) {
    ...
    } else if (i == 12) {
    checkDozen(monitor);
    }
    ...
    }
    private void checkDozen(IProgressMonitor monitor) {
    try {
    monitor.beginTask("Checking a dozen", 12);
    for (int i = 0; i < 12; i++) {
    Thread.sleep(10);
    monitor.worked(1);
    }
    } catch (InterruptedException e) {
    } finally {
    monitor.done();
    }
    }

  4. Run the Eclipse instance, select the Hello menu and open the Progress view and the progress status completely disappears after it reaches that point:

  5. To solve this problem, create another IProgressMonitor instance and pass that into the method call using a SubProgressMonitorinstance.

    } else if (i == 12) {
    checkDozen(new SubProgressMonitor(monitor, 100));
    continue;
    }

  6. Now, run the action, and the progress will update as expected. Note, that the continue statement is used here to avoid calling monitor.worked(100).

What just happened?

The checkDozen()method took an IProgressMonitor instance, and simulated a set of different tasks (with different units of work). Passing the same monitor instance causes problems as the work gets missed between the two.

To fix this behavior, a SubProgressMonitor instance was passed in. Because the SubProgressMonitor got 100 units of work from its parent, when the done() method was called on the SubProgressMonitor, the parent saw the completion of the 100 units of work.

Importantly, this also allows the child to use a completely different scale of work units and be completely decoupled from the parent's use of work units.

Time for action – using null progress monitors and submonitors

When a method uses progress monitors extensively, it is inelegant to keep checking whether the monitor is null or not. Instead, the progress monitor can be replaced with a NullProgressMonitor, which acts as a no-op for all monitor calls.

  1. Update the checkDozen() method to use a NullProgressMonitor, if null is passed.

    private void checkDozen(IProgressMonitor monitor) {
    if(monitor == null)
    monitor = new NullProgressMonitor();

    This allows the remainder of the method to run without modification, and saves any NullPointerExceptions that may result.

  2. A similar result is obtained for both the NullProgressMonitor and SubProgressMonitor with a wrapper/factory class called SubMonitor. This provides factory methods to wrap the monitor and creates child progress monitors.

    protected IStatus run(IProgressMonitor monitor) {
    try {
    SubMonitor subMonitor =
    SubMonitor.convert(monitor,"Preparing", 5000);
    for (int i = 0; i < 50 && !subMonitor.isCanceled(); i++) {
    if (i == 10) {
    subMonitor.subTask("Doing something");
    } else if (i == 25) {
    subMonitor.subTask("Doing something else");
    } else if (i == 40) {
    subMonitor.subTask("Nearly there");
    } else if (i == 12) {
    checkDozen(subMonitor.newChild(100));
    continue;
    }
    Thread.sleep(100);
    subMonitor.worked(100);
    }
    } catch (InterruptedException e) {
    } finally {
    if(monitor != null)
    monitor.done();
    }
    }

  3. Running the code has the same effect as the previous one, but it's more efficient. Note that the subMonitor object is used everywhere in the method until the end, where monitor is used to invoke done(). Since monitor may be null, it is guarded with a test.

What just happened?

The NullProgressMonitor was replaced with a SubProgressMonitor with a SubMonitor. To convert an arbitrary IProgessMonitor into a SubMonitor, use the convert() factory method. This has the advantage of testing for null (and using an embedded NullProgressMonitor if necessary) as well as facilitating the construction of SubProgressMonitor instances with the newChild() call.

Note that the contract of SubMonitor requires the caller to invoke done() on the underlying progress monitor at the end of the method, so it gives an error when it's done an assignment such as monitor = SubMonitor.convert (monitor)in code.

Since the isCancelled() check will ultimately call the parent monitor, it doesn't strictly matter whether it is called on the submonitor or the parent monitor. However, if the parent monitor is null, invoking it on the parent will result in a NullPointerException, whereas the SubProgressMonitor will never be null.

In situations where there will be lots of recursive tasks, the SubProgessMonitor will handle nesting better than instantiating a SubProgressMonitor each time. That's because the implementation of the newChild()method doesn't necessarily need to create a new SubMonitor instance each time; it can keep track of how many times it has been called recursively.

The SubMonitor also has a setWorkRemaining() call, which can be used to reset the amount of work for the outstanding progress monitor. This can be useful if the job doesn't know at the start how much work there is to be done, but it does become known later in the process.

Time for action – setting job properties

It is possible to associate arbitrary properties with a Job, which can be used to present its progress in different ways. For example, by specifying a command it's possible to click on a running Job and then execute something in the user interface, such as a detailed job description. Job properties are set with setProperty(), and can include any key/value combination. The keys use a QualifiedName, which is like a pair of strings for namespace/value. In the case of the Progress view, there is an IProgressConstants2 interface, which defines values that can be set, including COMMAND_PROPERTY, which can be used to invoke a command.

  1. Open the HelloHandler and go to the end of the execute()method. Just before the Job is scheduled, acquire the Command from the ICommandService and then stamp it on the Job as a property.

    ICommandService service = (ICommandService)
    PlatformUI.getWorkbench().getService(ICommandService.class);
    Command command = service == null ? null :
    service.getCommand("com.packtpub.e4.clock.ui.command.hello");
    if(command != null) {
    job.setProperty(IProgressConstants2.COMMAND_PROPERTY,command);
    }
    job.schedule()
    return null;

    E4: In E4, the ICommandService can be obtained via injection, using @Inject ICommandService service.

  2. Run the Eclipse instance, open the Hello command and go to the Progress view. Nothing will be shown, because the Job expects a ParameterizedCommand instead. Modify the property value, and using the generateCommand() factory of ParameterizedCommand.

    if(command != null) {
    job.setProperty(IProgressConstants2.COMMAND_PROPERTY,command);
    job.setProperty(IProgressConstants2.COMMAND_PROPERTY,
    ParameterizedCommand.generateCommand(command, null));

    }

  3. Now run the Eclipse instance, go to the Progress view, and click on the Hello command. Underneath the Progress view, a hyperlink will be provided to allow firing off another Hello command:

  4. If the command has no handler (or the handler is disabled), a pop-up error message will be shown:

  5. If the command is handled, clicking on the link will run the command, which in this case runs the HelloHandler and launches another job instance. Each click will spawn off a new Job:

  6. It's possible to change the icon shown in the view by specifying an ImageDescriptor as a Job property with the key ICON_PROPERTY. The image descriptor can be loaded from the createFromURL() method of ImageDescriptor and set as a property.

    job.setProperty(IProgressConstants2.ICON_PROPERTY,
    ImageDescriptor.createFromURL(
    HelloHandler.class.getResource("/icons/sample.gif")));

  7. Run the Eclipse instance, go to the Progress view, and then click on the Hello menu. The icon should be shown against the job:

What just happened?

Setting properties on the running Job allows viewers to extract information and present it in different ways. Properties are specified with a QualifiedName key and the value is passed in as an object, which is property specific.

The purpose of the QualifiedName key is to act as a string identifier, but partitioned into different namespaces. For example, the properties used by the IProgressConstants use org.eclipse.ui.workbench.progress as the namespace qualifier, and shorter strings such as command and icon for individual properties. The benefit of this (instead of org.eclipse.ui.workbench.properties.command) is that the long prefix string is stored once in memory and so doesn't take up repeated space in either the class file or the PermGen space, which can be limited on JDK 7. (JDK 8 will remove the PermGen, so it should be less of an issue in the future.)

Valid values for the Job in the Progress view can be found in the IProgressConstants and IProgressContstants2interfaces. Note that this is not a fixed set; additional Job properties can be added for use both elsewhere in the Eclipse platform and by independent extensions.

To associate a Command with a Job, set a property which contains a ParameterizedCommand. The factory method generateCommand() on the ParameterizedCommand class can be used to convert a command into a ParameterizedCommand.

The Command can be acquired from the ICommandService, which is acquired via the PlatformUIworkbench or through injection in E4.

Have a go hero – displaying in the taskbar

The IProgressConstants2interface also defines a property name SHOW_IN_TASKBAR_ICON_PROPERTY, which shows whether the progress of the Job is exposed to those operating systems that support it. On OS X, a bar will be shown over the Eclipse application icon. Set the property to the value Boolean.TRUE and see the effect it has on the Job.

The Job can also indicate if it is running in the foreground or the background, and can query its state via the Job property PROPERTY_IN_DIALOG. This is not intended to be set by clients, but can be read and displayed (or different actions be taken).

Reporting errors

As long as everything works as expected, the IDE won't need to tell the user that something has gone wrong. Unfortunately, even the most optimistic programmer won't believe that the code will work in every situation. Bad data, threading issues, simple bugs and environmental issues can result in operations failing, and when it fails, the user needs to be notified.

Eclipse has built-in mechanisms to report problems, and these should be used in response to a user interaction that has failed.

Time for action – showing errors

So far, the code has been using an information dialog as the demonstration of the handler. There's an equivalent method that can be used to create an error message instead. Instead of calling MessageDialog.openInformation(), there's an openError() method, which presents the same kind of dialog, but with an error message instead:

Using dialogs to report errors may be useful for certain environments, but unless the user has just invoked something (and the UI is blocked while doing it), reporting errors via a dialog is not a very useful thing to do. Instead, Eclipse offers a standard way to encapsulate both success and failure, in the Statusobject and the interface IStatus that it implements. When a Job completes, it returns an IStatus object to denote success or failure of the Job execution.

  1. Introduce an error into the run()method of HelloHandler, which will generate a NullPointerException. Add a catch to the existing try block and use that to return an error status. Since the OK_STATUS is a singleton instance of Status that can be used for all successful operations, it is necessary to instantiate a new Status object with the error information enclosed.

    protected IStatus run(IProgressMonitor monitor) {
    try {
    SubMonitor subMonitor =
    SubMonitor.convert(monitor,"Preparing", 5000);
    subMonitor = null; // the bug
    ...
    } catch (NullPointerException e) {
    return new Status(IStatus.ERROR,
    Activator.PLUGIN_ID, "Programming bug?", e);

    } finally {

  2. Run the Eclipse instance, and invoke the Hello command. An exception will be generated, and the status object containing the information needed will be passed to Eclipse. If the Job is running in the foreground, the job scheduler will present the status message if it is an error:

  3. Go to Window | Other | Error Log in the target Eclipse instance to see the error, which has also been written into the workspace/.metadata/.log file:

  4. Double-click on the entry in the error log to bring up specific details:

  5. Instead of returning an error status, it's also possible to log the error message programmatically, using StatusManager. The StatusManager is a service that can be acquired from a static factory or injected from a service. To just log the information but keep going, execute the following code snippet:

    } catch (NullPointerException e) {
    return new Status(IStatus.ERROR,
    Activator.PLUGIN_ID, "Programming bug?", e);
    StatusManager statusManager = StatusManager.getManager();
    Status status = new Status(IStatus.ERROR,
    Activator.PLUGIN_ID, "Programming bug?", e);
    statusManager.handle(status,
    StatusManager.LOG);
    } ...

    E4: StatusManager has been replaced with StatusReporter, which has a report() method that is equivalent to the handle() method.

  6. Run the Eclipse instance, invoke the Hello action and see the error being logged without displaying a dialog.
  7. Modify the status flags to add SHOW as well:

    statusManager.handle(status,
    StatusManager.LOG | StatusManager.SHOW );

  8. Re-run the Eclipse instance, invoke the Hello action and the error will be shown as well as logged.
  9. Finally, remove the bug from the HelloHandler so that it doesn't cause errors.

    subMonitor = null; // the bug

What just happened?

First, openError() was used to show an error, which is useful for specific cases—such as when the user is interacting with the UI and has just done an operation that is problematic.

The next step looked at status reporting and handling in Eclipse, including how exceptions are captured and associated with a specific plug-in. A Status object was used to indicate a single issue—though there's a MultiStatus, which can be used to add additional Status instances, if required. Generally, the status should be logged but not shown as dialogs popping up in the user's screen (especially from a background job) are a UX anti-pattern.

These flags can be combined, so LOG indicates that the status message should be logged but not otherwise displayed to the user, while SHOW indicates that the message should be shown in the standard dialog. Both of these happen asynchronously; the code will continue executing after invoking these calls, regardless of how the messages are shown to the user. There is a BLOCK flag as well, which prevents continued execution of the thread, but this should not be used as it may lead to inadvertent deadlocks.

Summary

This article covered how the user interfaces respond to user input by defining menus associated with abstract commands that are associated with handlers to execute code. It also covered how to run code in the background with jobs, and report the errors via the standard error reporting mechanism.

The next article will look at how to store preferences so that configuration items can be kept between restarts of the Eclipse platform.

Resources for Article:


Further resources on this subject:


Eclipse 4 Plug-in Development by Example Beginner's Guide How to develop, build, test, package, and release Eclipse plug-ins with features for Eclipse 3.x and Eclipse 4.x with this book and ebook
Published: June 2013
eBook Price: $26.99
Book Price: $44.99
See more
Select your format and quantity:

About the Author :


Dr Alex Blewitt

Dr Alex Blewitt has been developing Java applications since Version 1.0 was released in 1996, and has been using the Eclipse platform since its first release as part of the IBM WebSphere Studio product suite. He even migrated some plugins from Visual Age for Java to WebSphere Studio/Eclipse as part of his PhD on Automated Verification of Design Patterns. He got involved in the open source community as a tester when Eclipse 2.1 was being released for Mac OS X, and then subsequently as an editor for EclipseZone, including being a finalist for Eclipse Ambassador in 2007.

More recently, Alex has been writing for InfoQ, covering generic Java and specifically, Eclipse and OSGi subjects. He keynoted the 2011 OSGi Community Event on the past, present, and future of OSGi. The coverage of both new releases of the Eclipse platform and its projects, as well as video interviews with some of the Eclipse project leads can be found via the InfoQ home page, for which he was nominated and won the Eclipse Top Contributor 2012 award.

Alex currently works for an investment bank in London. He also has a number of apps on the Apple AppStore through Bandlem Limited. When he's not working on technology, and if the weather is nice, he likes to go flying from the nearby Cranfield airport.

Alex writes regularly at his blog, http://alblue.bandlem.com, as well as tweets regularly from Twitter and App.Net as @alblue.

Books From Packt


 Instant Eclipse 4 RCP Development How-to [Instant]
Instant Eclipse 4 RCP Development How-to [Instant]

Java EE Development with Eclipse
Java EE Development with Eclipse

Getting Started with Eclipse Juno
Getting Started with Eclipse Juno

 Instant Eclipse Application Testing How-to [Instant]
Instant Eclipse Application Testing How-to [Instant]

 PHPEclipse: A User Guide
PHPEclipse: A User Guide

WordPress Plugin Development: Beginner's Guide
WordPress Plugin Development: Beginner's Guide

JBoss Drools Business Rules
JBoss Drools Business Rules

BlackBerry Java Application Development
BlackBerry Java Application Development


Your rating: None Average: 1.6 (9 votes)

Post new comment

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
c
t
Y
y
f
7
Enter the code without spaces and pay attention to upper/lower case.
Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software