22 min read

(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.

LEAVE A REPLY

Please enter your comment!
Please enter your name here