This is my second blog about the Eclipse Commands framework, but actually the first one containing useful (I hope) information. I’m going to focus on the topic from a practical “using it in development” point of view, trying to keep the examples as simple as possible and the theory limited. For an updated outline please refer to the first blog in the series: Eclipse RCP Commands Part One.
Short history: the Commands framework has been around partially since Eclipse 3.0, maturing over the versions until it became quite useful in Eclipse 3.3. Some refinements in Eclipse 3.4 makes it even more appealing. I must admit, before Eclipse 3.3 I saw no reason for using Commands, and I stuck to using IActions and IActionDelegates, trying to work around their issues as much as possible (and falling into the rythm of the copy-and-paste anti-pattern in order to contribute an IActionDelegate to multiple places in a UI). No I’m trying to convince all my clients to “renew” their code to use Commands…
“A command is neither the presentation nor a particular behavior implementation; it is an abstract representation of some semantical behavior.” – Marc R. Hoffmann (see Article on Eclipse Commands Framework)
That’s the best quote I could find as to what a Command is. What does it boil down to? I assume (hope?) you are all familiar with the Model-View-Controller pattern, used especially when developing UI applications. By abstracting a particular piece of functionality into separate Model, View and Controller objects, the code is must less coupled, allowing more (and easier) reuse, flexibility and extensibility.
Well, the Command design is not exactly MVC, but it has the same goals in mind. It abstracts the actual business or application semantics (i.e. what is actually meant to happen from business or application functionality perspective) into this thing called a Command. Worries about how it should appear in the UI (menu contributions), or what exactly must happen on a code implementation level to execute the command (handlers) is left for later. In this way we get the same decoupling of presentation and implementation as with MVC, and thus the gain in reusability of Commands and their handlers, and increase in the flexibility and extensibility of the code, thus leading overall to code that is much easier to maintain.
Add to the above paragraph that code that is easier to maintain leads to huge development cost savings, and you’ll have a convincing argument to offer to your boss for allowing you to refactor away all the old IActionDelegates.
Ok, but what to we do with Commands?
Plain and simple: we use it to implement functions that our application can perform and make it available as menu or toolbar items so the user can invoke it.
On a more practical level
That is about enough theory for one day. Let’s get down to the bits and the bytes.
Firstly, I want to introduce my patent-pending idea for an application. Image this: you have this cross-platform application (obviously implemented using Eclipse RCP) that can actually read electronic versions of books, that we can call “e-books”. “Wow!” you say. “Amazing”. “What a novel idea”.
Let us call our bleeding edge application MyReader. You can download the source code for this blog’s version of the example.
Step 1: Create a RCP application using Eclipse 3.4 (I’m using 3.4.1) and the New Plug-in Project wizard.
Name the project MyReader
On the next wizard page fill in the details you want, but make sure “Would you like to create a rich client application” is set to “Yes“.
On the third wizard page, select the “RCP application with a view” as the template to use.
Run the resulting RCP application to make sure it runs, so you don’t blame my examples later for messing up your pristine code.
Now we have the example application that we are going to enable with Commands. Exciting, isn’t it?
How to define a Command
To have a command that is actually accessible in the UI, and able to do something, we need 3 “components”:
The Intent: the Command declaration
The Presentation: the menu contribution declaration(s)
The Behaviour: the Handler that implements the behaviour of the Command
We declare the Command using the org.eclipse.ui.commands extension point. I’m sure you remember this command we declare indicates the meaning or intent of the functionality we want to add to our application or plugin.
Let us explore this Command declaration in our example, and then I’ll discuss what is going on.
We need to open an ebook file with our application, so we will define a command to do this.
Step 2: Add the org.eclipse.ui.commands extension to our plugin.xml
Open the PDE-Editor (Plugin Editor), if it is not open already, and select the Extensions tab. (You double-click the plugin.xml or MANIFEST.MF files in the project. However, if you did not know that, you must maybe first implement a “Hello RCP World” application before jumping to Commands…)
If, due to random bit-changes caused by unexplained solar activity, you already have the org.eclipse.ui.commands extension in your list of extensions, then do not follow the rest of this step.
Click on the “Add…” button.
Start typing in “org.eclipse.ui.commands“, and select it from the filtered list when you see it. If it is not available uncheck the “Show only extension points from required plug-ins” option.
Select the Finish button. This will add the org.eclipse.ui.commands entry to your plugin.xml file.
Step 3: Declare the Open File command
right-click the org.eclipse.ui.commands extension, and select New -> Command from the context menu.
change the id to something like “mydomain.myreader.commands.OpenEBook”
change the name to “Open eBook”
change description to “Opens an eBook file for reading”
for now, ignore the rest
Steps 2 and 3 updates the plugin.xml file of the plugin project by adding the necessary extension point and declaring a basic command. The plugin.xml file now contains:
<extension
point="org.eclipse.ui.commands">
<command
description="Open an eBook file for reading"
id="com.richclientgui.myreader.commands.OpenEBook"
name="Open eBook">
</command>
</extension>
|
Congratulations! Your first command.
“But if I run my app, I don’t see this command. What’s wrong?” Nothing. We still need to define how this command will be visible in the UI, and what the behavior should be when this command is invoked. As mentioned earlier, these are decoupled from the Command declaration.
However, let us first examine the declaration in the plugin.xml. The org.eclipse.ui.commands extension allows us to declare, among other things, a Command. The Command definition needs the following attributes:
| id |
a unique id that will be used for referring to this command when declaring the UI contributions and Handlers |
| name |
a user-readable name that will be used for the label in the menu contribution(s), if the menu contribution does not define its own label |
| description |
a user-readable name that will be used for the tooltip in the menu contribution(s), if the contribution does not define its own description |
We are still going to have a look at some of the other attributes available. If you can’t wait, go and see org.eclipse.ui.commands for more detailed information about the extension point definition. Go on, don’t mind me.
Note: here we had a look at how to declare commands using the extension point in the plugin.xml file. In a later blog I plan to discuss how to do it programmatically. I will update this section with a link to that blog as soon as it is available.
It is time to get his command to be presented in the UI of the application. I assume you have already done some nice UI design work sessions with your potential end-users and you are ready to build the ultimate user friendly intuitive graphical user interface for your application. For our example we are just going to use the UI-design-by-coincidence approach and just add our stuff and see how it looks.
In order to contribute an item that will invoke a Command to the UI, we need to add the org.eclipse.ui.menus extension to our plugin.xml, and declare a MenuContribution element followed by a Command element.
We first declare where (i.e. the main menubar or toolbar, or a view or editor specific menu and/or toolbar, or a context menu) the command should be added via the MenuContribution. Then we declare the visual representation of our Command using the MenuContribution’s Command element.
Step 4:Add the org.eclipse.ui.menus extension to plugin.xml
Click on the Add… button in the Plugin Editor.
Select the org.eclipse.ui.menus extension from the list of available extensions. If it is not available uncheck the “Show only extension points from required plug-ins” option.
Select the Finish button. This will add the org.eclipse.ui.menus extension to the plugin.xml file.
Step 5: Add the MenuContribution element
Right-click the org.eclipse.ui.menus extension, and select New -> menuContribution from the context menu
Change the locationURI to menu:file?after=additions (don’t worry, we’ll discuss it just now)
Step 6: Add the Command to the MenuContribution
Right-click on the menuContribution created in step 5, and select New -> command from the context menu.
Change commandId to the id you provided to your Command definition in step 3.
Change mnemonic to “O” to underline the first letter of the “Open” as your shortcut mnemonic for your command in the menu.
I’m sure you are eager to run your MyReader app now. Go ahead, please. Do you see it? A grayed-out Open eBook menu-item just underneath Exit in the File menu.
That is not quite what we intended. We’ll get there, though. First a discussion on what we actually did.
We first needed to add the org.eclipse.ui.menus extension to our plugin, and then we declared that we want to contribute an item to a menu via the MenuContribution element. We only had to specify one attribute: the locationURI. More on that in the next section.
We then declared that we are actually contributing a Command item as part of our MenuContribution. This is not the actual definition of a Command, but just an element indicating that we wish to represent the command with the identifier specified in commandId as an item in the menu or toolbar that we are declaring this MenuContribution for. Thus by specifying the locationURI “menu:file?after=additions” and the commandId “com.richclientgui.myreader.commands.OpenEBook” we are saying that we want the OpenEBook command to display in the standard File menu after the additions placeholder.
The mnemonic attribute indicates what character should be underlined and act as a shortcut key for selecting the menu item, if that menu is currently open. Other additional attributes can be specified to manage the presentation of the Command in the menu, e.g. the icon, label (overriding the value set in the Command’s name attribute), tooltip (overriding the value set in the Command’s description attribute), etc. This gives us the flexibility to present the same Command slightly different depending on where it gets contributed, something that is not possible with IActions and IActionDelegates.
The locationURI is an interesting attribute: it allows the developer to use a URI string to specify where a specific contribution (whether it is a command item, a submenu or some other widget that can be contributed) must be added. It is of the form:
[scheme]:[id]?[placement arguments]
| scheme |
The scheme indicates the type of the target component of the contribution, and can be one of:
menu : either main menu or view menu
toolbar : either main toolbar or view toolbar
popup : context menu
|
| id |
The unique identifier of the menu, toolbar or context (popup) menu where the contribution must be added. See the list below for standard platform identifiers that can be used. |
| placement |
The arguments helps to define the placement of the contribution items in relation to another item or placeholder (i.e. GroupMarker) in the target menu. It consists of either the word “before” or “after” followed by an equal sign (=) and then the identifier of a menu contribution item. The word “additions” can also be used in place of an identifier. Contributions added to “additions” will typically be placed last in a menu. |
Talking in detail about the locationURI and menu contribution placement can be a long blog entry on its own, so I want to come to this subject in more detail later in the series.
Note: I usually declare my view’s identifier as the same as the fully qualified Java class name of the ViewPart implementation class, and then use the convention viewID.menu, viewID.toolbar and viewID.popup as identifiers for its various menus. E.g. a View implemented in the class com.richclientgui.myreader.BookView will have the Id com.richclientgui.myreader.BookView and the menu IDs com.richclientgui.myreader.BookView.menu, com.richclientgui.myreader.BookView.toolbar and com.richclientgui.myreader.BookView.popup.
Some standard identifiers are available for commonly used menus and toolbars:
| ID |
Description |
| org.eclipse.ui.main.menu |
the main Eclipse application menubar |
| org.eclipse.ui.main.toolbar |
the main Eclipse application toolbar |
| org.eclipse.ui.popup.any |
contributions using this identifier will be visible in all context menus of the application (well, that’s not the full truth, but that is a topic for another blog) |
| menu:file |
three guesses what menu this one point to… |
| menu:help |
I’m not even going to say its a menu, and you only get one guess |
| menu:window |
Ok, you start to get the picture |
| helpEnd |
name of group-marker at end of Help menu |
| quit |
id of Exit action |
See IIDEActionConstants and IWorkbenchActionConstants for more useful identifiers.
I can’t wait to move that Open eBook item to the top of the menu…
Step 7: Change Command menu-item placement
Select the menuContribution element we specified in step 6.
Change the locationURI to be menu:file?before=quit
Running our latest version gives us a still grayed-out item, but at least where we want it.
And our plugin.xml has grown a bit:
<extension
point="org.eclipse.ui.commands">
<command
description="Open an eBook file for reading"
id="com.richclientgui.myreader.commands.OpenEBook"
name="Open eBook">
</command>
</extension>
<extension
point="org.eclipse.ui.menus">
<menuContribution
locationURI="menu:file?before=quit">
<command
commandId="com.richclientgui.myreader.commands.OpenEBook"
mnemonic="O"
style="push">
</command>
</menuContribution>
</extension>
|
Now to implement the behaviour.
After all this build-up, it is quite simple to actually add behaviour to a Command. You need to
implement a class that extends org.eclipse.core.commands.AbstractHandler,
and update your Command’s defaultHandler attribute that you left empty in step 3.
Step 8: Implement a default Handler
Implement a class called DefaultOpenEBookHandler that extends AbstractHandler, and only override the execute(…) method. I just added a call to the JFace MessageDialog class to show some information. I will discuss these Handler classes more in a future blog in the series.
Select the OpenEBookCommand definition from step 3 and select the DefaultOpenEBookHandler class as the defaultHandler‘s value.
Here is my DefaultOpenEBookHandler class:
package com.richclientgui.myreader.handlers;
import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.widgets.Display;
public class DefaultOpenEBookHandler extends AbstractHandler {
public Object execute(ExecutionEvent event) throws ExecutionException {
MessageDialog.openInformation(Display.getDefault().getActiveShell(),
"O no!", "I'm not ready yet. Go read the hardcopy book.");
return null;
}
}
|
If you run the application now, you will actually see that the command item in the menu is enabled, and you should see a dialog with a message if you select the item.
We saw a basic introduction of how to:
declare a Command
contribute that command to the main menu-bar
define a class that encapsulated the behaviour of the Command
Each of these components on their own does not add value to an application, but together it forms a powerful yet flexible and very maintainable Commands framework, far superior to using IActions and IActionDelegates because of the separation between the intent, presentation and behaviour.
Summary of Steps
| Step 1 |
Create sample RCP application |
| Step 2 |
Add the org.eclipse.ui.commands extension to our plugin.xml |
| Step 3 |
Declare the Open File command |
| Step 4 |
Add the org.eclipse.ui.menus extension to plugin.xml |
| Step 5 |
Add the MenuContribution element |
| Step 6 |
Add the Command to the MenuContribution |
| Step 7 |
Change Command menu-item placement |
| Step 8 |
Implement a default Handler |
You can download the source code for this blog’s version of the example.
Next Episode
Do not miss out on the next exciting episode on Eclipse RCP Commands. There I’ll be tackling mind-blowing topics, like
how to show commands only when you want to
how to disable commands
dynamic menu contributions
handling the Handlers
(Time and weather permitting.)
References
Eclipse.org Commands Framework Article in bugzilla
Article on Eclipse Commands Framework, by Marc R. Hoffmann
Eclipse.org Wiki on Command Framework
Eclipse Commands Tutorial, by Lars Vogel
Enough said for now.