In this third and final article in the
series, A. O. Van Emmenis completes the file explorer example begun in
Part
1 and Part
2 by adding actions, menu bars, pop-up menus, and toolbars. He shows
how to set menu item properties, how to reuse actions in menus and
toolbars, and how to make actions context sensitive by listening to
events from viewers. The example actions use utilities to launch
programs and access the system clipboard.

IntroductionPart
1 of this series began an example that subclassed the JFace
application window and used a tree viewer and a table viewer to display
folders and files. In Part
2 we did some tidying and added some icons using the JFace image
registry.

This time, we'll look at actions and you can use them in menus and
toolbars. We'll see the use of the Program
class to launch programs and the Clipboard
class to access the system clipboard. We have already used icons to
display files and folders in viewers. We'll see how they can be used in
menus and toolbars as well. Finally, we'll make actions listen to events
from viewers in order to make them context sensitive.

Folders are displayed in the left pane using a tree viewer. When you
select a folder in the left pane, the files that it contains are shown in
a table viewer in the right pane. We are sorting the items in the right
pane so that folders appear first. We are using icons to represent files
and folders in both viewers.

Let's add a simple bar menu to the window.

MenusThe JFace MenuManager
simplifies the creation and updating of SWT menus. A menu manager can
contain items, other menu managers (for sub-menus), and separators. Once
you have created a menu manager, it can be represented by either a bar
menu, a context menu (in other words, a pop-up menu), or a toolbar
drop-down menu.

Just as with viewers, a menu manager is a helper object rather than a
wrapper object, although you will generally not need to access the SWT
menu itself. Before we discuss menus, let's see what can go into a menu
manager.

ActionsYou add
actions to menu managers. Actually, you can add actions to buttons and
toolbars, too. The idea is that you subclass Action,
set the text that you want to appear in the menu/toolbar/button, and
implement the run() method to make it do what you want.

All fairly simple. The & character before the x in
Exit indicates that x is to be the keyboard navigation key
(mnemonic) for this menu item. Note that this is different from a key
accelerator (hotkey). We'll see these soon ...

Subclassing
actionDon't be misled by the fact that Action
defines the method getText(). You are not intended to
override it. Instead, you are meant to use
setText(String), and Action will store it and ensure
that all the SWT controls that are currently using this action are
updated with the new text.

This applies to all the other Action properties like the tool tip
text, its enabled state, etc. -- we'll see these
later.

Remember that we must do this before the SWT shell is created.
And this will call the application window method
createMenuManager(), which returns the menu manager that it
will use later to create the SWT Bar Menu. Here is our implementation in
Listing 2:

We've added an accelerator key (hotkey), a tool tip, and an image (the
tool tip won't show up on menu items, but it will on tool bar items, which
we'll see later). There is a method to set the accelerator key directly,
but it's easier to specify it in the text after the @ character,
since this way, it gets added into the text of the menu item, as shown in
Figure 3:

The eagle-eyed reader may have spotted that we supplied an image
descriptor directly to the action. What we would really like to do is get
the image descriptor from the image registry. The problem is that the
image registry only gives out Images -- you can't ask it for an
ImageDescriptor. This is bug 23555 in
the Eclipse bug database.

We get hold of the selected elements from the table using
getTableSelection() (we'll see that method in a moment) and
then check that there is exactly one element selected -- remember that the
table is now a multi-select style table -- and then get the element
itself, check it is really a file rather than a folder, and launch it.

The Program launch() method does all the work looking up
the associated program based on the file extension, and then it runs the
appropriate executable, supplying the absolute file name as the
argument.

Before we try this out, let's do one last action.

Using the
system clipboardHere's a neat little use of the system
clipboard. The action CopyFileNamesToClipboardAction copies
the absolute file names of all the selected files to the clipboard.

Each array slot in data is associated with a transfer object that tells
the clipboard what type of data is in data. In our case we'll use a TextTransfer
object, which tells the clipboard that we are transferring plain text
(rather than, say, RTF).

The arguments to public void setContents(Object[] data,
Transfer[] dataTypes) are arrays, so that you can transfer data in
several formats at once. For example, a word processing application might
want to transfer text in RTF and plain text.

Avoiding spaghetti in GUI instance
hierarchiesIn my experience, when designing GUI objects,
you tend to end up building fairly large instance hierarchies. Objects at
low levels in different hierarchies need to know about each other, so
there is a temptation to create ad-hoc cross references between them,
which can lead to code resembling that famous pasta.

Even in our small example, we can see this just beginning to happen.
The action objects need to know about the currently selected item in the
table viewer. The question is: what object does the action store? Is it
the table viewer, the SWT table itself, the Window?

One solution is to funnel all the access to widgets/selection objects
through the window. Have each action store the window that created it, and
then it can use the accessor methods on that window to get to the objects
it needs.

If you want to share actions between different windows where the
widgets are accessed using different method names, then you can wrap the
access to the widgets from the action inside a method, which could be
reimplemented in subclasses of the action to fetch the correct widget.

For example, to share an action that needed to access a selection, the
method getSelection()in the superclass could be implemented
as:

window.getTableSelection()

in one subclass and (say)

window.getThirdListViewerSelection()

in another subclass.

Making
actions context sensitiveAnother thing we can do with
actions is to make them aware of what is happening in the rest of the
window and adapt themselves accordingly. We're going to make
OpenAction listen for any changes to the current selection in
the table viewer. When it notices a change, it can look at the new
selection and change its text, tool tip, and enabled state to reflect
that.

OpenAction launches the "associated" program on the
currently selected file. If nothing is selected then, rather than letting
it run and report an error, or just mysteriously do nothing, why don't we
disable the action?

While we're thinking about being nice to the user, I often get confused
when I see menu items that are disabled and I don't understand why they
are disabled. So, here's an idea: How about changing the tool tip so that
we even tell the user why the action is disabled?

We also have to decide what to do if the selection includes several
files. Well, we could just run them all, one after another, but, since I
have painful memories of selecting 300 files and choosing the Open option
from the Windows File Explorer, let's just disable it for now.

Finally, we'll check if the selected item is a folder and disable the
open action in this case as well.

We need to make OpenAction a listener for the
SelectionChanged event from the table viewer, so we'll make it implement
ISelectionChangedListener
(Listing 8).

We'll do this in selectionChanged():

Set the text and tool tip text to the default

Check the selection

If we don't have exactly one item selected, we disable the action,
adjust the tool tip to say why it is disabled, and return.

If the selection is a file (rather than a folder), then we adjust
the text and tool tip to reflect that file name and enable it.

Tool bars and
pop-up menusFinally we'll add a toolbar and pop-up
(context) menu. Happily, there isn't really much to do since we've done
all the hard work already in our actions. The toolbar and the pop-up menu
are merely going to share those actions.

Just as with the status line and the bar menu, we configure the window
to have a tool bar, and we implement the
createToolBarManager() method to create it.

It's slightly different for the pop-up menu. We create it in the
createContents() method and add it directly to the table
widget.

We also refactor the code to have all three actions as fields (rather
than local variables) so that we can access them from three methods. Let's
see the final version of Explorer (Listing 10):

ConclusionWe've
covered quite a lot of JFace in these three articles. We've seen how the
pluggable JFace window, viewer, and menu framework can generate
great-looking user interfaces using relatively small amounts of code.

I hope that you have learned:

How to subclass application windows

How to use the pluggable viewers and content providers

How to add icons using images and the image registry

How to use the system clipboard

How to launch programs

How to use menus and actions

How actions can work with different menu containers and listeners to
produce context-sensitive applications

But, of course, there's more. Check out the Resources
for more information.

About the
authorA. O. Van Emmenis is an independent
consultant, specializing in Java/J2EE training and consulting, based
in Cambridge, UK. Van has worked in the software industry for 20
years or so. He first started working with objects using Smalltalk
in the CAD industry and now works mainly in Java. He is particularly
interested in Agile methods and GUI design. You can contact Van at
van@vanemmenis.com.