站内搜索: 请输入搜索关键词
当前页面: 图书首页 > Eclipse Rich Client Platform: Designing, Coding, and Packaging Java Applications

Section 6.1.  Adding to the Menus and Toolbar - Eclipse Rich Client Platform: Designing, Coding, and Packaging Java Applications

Previous Page
Next Page

6.1. Adding to the Menus and Toolbar

Actions are everywhere: in toolbars, top-level menus, context menus, status lines, and so on. In most applications, the menu and toolbar play a supporting role for the main content area. When running an application for the first time, most users browse the top-level menu structure to find out what the application can do. This makes the top-level menu structure of your application very important.

Take a look at Hyperbola as it is now. Notice that it does not have a top-level menu. Let's fix that. The general pattern for making the toolbar, status line, or menu bar available is to configure the window before it is opened. This is done in ApplicationWorkbenchWindowAdvisor.preWindowOpen() by adding a call to methods such as setShowMenuBar(boolean), as shown below:

org.eclipsercp.hyperbola/ApplicationWorkbenchWindowAdvisor
public void preWindowOpen() {
  IWorkbenchWindowConfigurer configurer = getWindowConfigurer();
  configurer.setInitialSize(new Point(250, 350));
  configurer.setShowMenuBar(true);
  ...
}

You should be familiar with this methodit's what you used to change the size of the window in a previous chapter. The preWindowOpen() method is called before the window's controls have been created, and it's the primary place for you to control which WorkbenchWindow parts are visible.

Note

For more advanced window customizations, refer to Chapter 18, "Customizing Workbench Windows," which explains how to customize the layout of WorkbenchWindows and how to let the user toggle the toolbar and status line.


The code above enables the menu bar, but does not force it to be shown. It is only shown if it contains menu items. If you run Hyperbola now, the menu bar does not appear because it's empty.

6.1.1. Create Top-level Menu

Now that the top-level menu has been enabled, the next step is to create actions and add them to the menu. RCP applications have a dedicated advisor, called the ActionBarAdvisor, whose job is to create the actions for a window and populate the menu, toolbar, and status line. The ActionBarAdvisor is separate from the WorkbenchWindowAdvisor since an application often has hundreds of actionsthis more clearly separates the concerns.

Figure 6-2 shows the call sequence between the WorkbenchWindow, the WorkbenchWindowAdvisor, and the ActionBarAdvisor. Notice that ActionBarAdvisor.makeActions() is called before the WorkbenchWindow's controls are created in createWindowContents(). This means that you cannot access any of the window's widgets when creating the actionsyou cannot link the actions to any menus or other window parts.

Figure 6-2. ActionBarAdvisor method sequencing


In Hyperbola, we need two top-level menus: Hyperbola and Help. The Hyperbola menu should have general application actions and the Help menu some higher level information related to Hyperbola itself (e.g., Help, About information, etc.). For now, let's just add Exit and About items to these menus.

Figure 6-2 shows that the steps for creating actions, makeActions(), and placing actions, fill*(), are separated. This allows you to create the actions just once and then place the same action objects in several locations, for example, in both the toolbar and a menu. This very useful coding pattern is shown in the following code snippet:

org.eclipsercp.hyperbola/ApplicationActionBarAdvisor
public class ApplicationActionBarAdvisor extends ActionBarAdvisor {
  private IWorkbenchAction exitAction;
  private IWorkbenchAction aboutAction;
  protected void makeActions(IWorkbenchWindow window) {
    exitAction = ActionFactory.QUIT.create(window);
    register(exitAction);
    aboutAction = ActionFactory.ABOUT.create(window);
    register(aboutAction);
  }
  protected void fillMenuBar(IMenuManager menuBar) {
    MenuManager hyperbolaMenu = new MenuManager(
        "&Hyperbola", "hyperbola");
    hyperbolaMenu.add(exitAction);
    MenuManager helpMenu = new MenuManager("&Help", "help");
    helpMenu.add(aboutAction);
    menuBar.add(hyperbolaMenu);
    menuBar.add(helpMenu);
  }
}

When you created the skeleton RCP application, it included an ActionBarAdvisor implementation with empty makeActions() and fillMenuBar() methods. Now, modify the generate code as shown above to get top-level Hyperbola and Help menus with associated Exit and About actions.

Notice that makeActions() creates each action and saves it in a field. Each action is also registered. Registering actions ensures that they are deleted when the related Workbench window is closeda very important characteristic. Registering actions also enables key bindings, as discussed in Chapter 12, "Adding Key Bindings."

Creating the Exit and About actions was easy because they already existed. The Workbench defines a set of common actions that are reusable in all RCP applications. Each of these actions is defined as an inner class of org.eclipse.iu.actions.ActionFactory. You instantiate them and use them as regular actions. They are preconfigured with a standard name, icon, and id. The code snippet just shown demonstrates how this is done.

Run Hyperbola now and you should see the top-level menus and the new Exit and About actions, as shown in Figure 6-1. If you run the Help > About action, it displays an empty dialog, as shown in Figure 6-3. The Plug-in Details button shows the list of installed plug-ins, and Configuration Details shows information about your environment (e.g., the command line arguments used to start Hyperbola, system properties, and user preferences). Chapter 8, "Branding Hyperbola," shows you how to brand this dialog with an about image and some descriptive text.

Figure 6-3. Empty About dialog


Note

The Plug-in details list is very handy for confirming which plug-ins are running as part of your application.


6.1.2. Menu Managers

Earlier, the Exit and About actions were added to a menu manager. A menu manager is responsible for keeping track of actions and sub-menus and allowing you to create logical structures of actions by grouping. It allows you to organize the actions you want to show without concerning yourself with how the menu is created. For example, the following modified version of the earlier code adds placeholders for actions. The real actions can then be added after the menu manager is created. This flexibility is quite powerful. For example, it lets you create menus that are filled in by other plug-ins.

org.eclipsercp.hyperbola/ApplicationActionBarAdvisor
protected void fillMenuBar(IMenuManager menuBar) {
  MenuManager hyperbolaMenu = new MenuManager(
      "&Hyperbola", "hyperbola");
  hyperbolaMenu.add(exitAction);
  hyperbolaMenu.add(new GroupMarker("other-actions"));
  ...
  hyperbolaMenu.appendToGroup("other-actions", aboutAction);
}

Menu managers can also be nested. This allows you to create multidimensional action structures such as cascading menus. Let's experiment with menu managers a bit. Change the ActionBarAdvisor with the following code snippet to add a Help cascading menu below the top-level Hyperbola menu, as shown in Figure 6-4:

org.eclipsercp.hyperbola/ApplicationActionBarAdvisor
protected void fillMenuBar(IMenuManager menuBar) {
  MenuManager hyperbolaMenu = new MenuManager(
      "&Hyperbola", "hyperbola");
  hyperbolaMenu.add(exitAction);
  MenuManager helpMenu = new MenuManager("&Help", "help");
  helpMenu.add(aboutAction);
  menuBar.add(hyperbolaMenu);
  hyperbolaMenu.add(helpMenu);
}

Figure 6-4. Cascading menu example


6.1.3. The Add Contact Action

The Exit and About actions are generic, so we used predefined actions. Of course, Hyperbola also needs some actions specific to instant messaging. Here we talk about how to:

  • Implement an action that adds a contact to the Contacts list.

  • Ensure the action is only enabled if a contact group is selected in the Contacts view.

  • Add the action into the top-level Hyperbola toolbar.

Some actions should be available at all times and are independent of the current state of the application. Exit is a good example of this. Other actions should only be available when Hyperbola is in a certain state. For example, the Add Contact action you are about to add requires that a contact group be selected. Actions that do not make sense in the current state of Hyperbola should be disabled to indicate that they are not applicable.

First, create the Add Contact action as shown below. Don't worry about the compile errors; the missing run() and selectionChanged() methods are about to be added.

org.eclipsercp.hyperbola/AddContactAction
public class AddContactAction extends Action implements
    ISelectionListener, ActionFactory.IWorkbenchAction {
  private final IWorkbenchWindow window;
  public final static String ID = "org.eclipsercp.hyperbola.addContact";
  private IStructuredSelection selection;

  public AddContactAction(IWorkbenchWindow window) {
    this.window = window;
    setId(ID);
    setText("&Add Contact...");
    setToolTipText("Add a contact to your contacts list.");
    setImageDescriptor(
        AbstractUIPlugin.imageDescriptorFromPlugin(
        "org.eclipsercp.hyperbola", IImageKeys.ADD_CONTACT));
    window.getSelectionService().addSelectionListener(this);
  }
  public void dispose() {
    window.getSelectionService().removeSelectionListener(this);
  }
  // Additional run() and selectionChanged() methods to be added
  // here.
  ...
}

The constructor for this action is pretty standard; the action is given a name, an icon, a tool tip, and an id. The id is used to uniquely identify the action and is used by ActionBarAdvisor.register(IAction) to manage the action. The IImageKeys.ADD_CONTACT is a new image key that you must define. Alternatively, you can use an existing key just for now or import icons from the final code sample.

Tip

To make the examples clearer, we do not worry about translating the action labels. However, the Eclipse Java IDE comes with a handy wizard that helps internationalize, or externalize, the strings in your Java code. The tool can be run from Source > Find Strings to Externalize...


The interesting part of the action is around the selection listening. In the constructor, the action is registered as a selection listener. Notice that the action implements ISelectionListener. This combination means that when the selection changes in the window, the action is notified via its selectionChanged(IWorkbenchPart, ISelection) method. An implementation of selectionChanged() is shown in the following snippet:

org.eclipsercp.hyperbola/AddContactAction
public void selectionChanged(IWorkbenchPart part, ISelection incoming) {
  // Selection containing elements
  if (incoming instanceof IStructuredSelection) {
    selection = (IStructuredSelection) incoming;
    setEnabled(selection.size() == 1 &&
        selection.getFirstElement() instanceof ContactsGroup);
  } else {
    // Other selections, for example containing text or of other kinds.
    setEnabled(false);
  }
}

The code first checks that the incoming selection is structured. If it is notfor example, if it's a text selection from an editor or something elsethe selection cannot affect the action's enablement state. If more than one item is selected, it does not make sense for the user to add a contact so the action is disabled. If the incoming selection is structured and contains only one element and that element is a ContactsGroup, then the Add Contact action is enabled.

Notice that the new selection is remembered by the action so that if it is run, it knows the group in which to add the new contact. Notice also that it only makes sense to hang onto the selection if it caused the action to be enabled. All other cases should make the selection field null.

We have not said where these selection events come from. You could bind the listener to just the Contacts view, but what if there is another view or window that shows contact groups? By adding its listener to the window's selection service, the action hears about all selection changes made in that window. You can access a window's selection service by calling IWorkbenchWindow.getSelectionService().

The selection service doesn't generate selections on its own. For that purpose, views and editors can register as selection providers and essentially publish their selections to selection listeners. In the current Hyperbola, we expect most contact group selection events to come from the Contacts view so the Contacts view needs to publish its selection events to the window! This needs to be done when the contents of the Contacts view is created. Take a look at ContactsView.createContents(Composite) and look for the line:

getSite().setSelectionProvider(treeViewer);

This registers the treeViewer (i.e., the contents of the Contacts view) as a selection provider.

The summary is that listening to the window instead of directly to a particular event source (e.g., a view) decouples actions from views and allows them to be used in other scenarios.

Tip

Notice that the action also implements ActionFactory.IWorkbenchAction and thus the dispose() method. When the action is disposed, it is essential that its selection listener be removed from the selection service. Failure to do this is a common cause of memory leaksdouble-check to ensure that you always remove any listeners you register.


The action is configured and structured, so now add the run() method that does the real work. The code for this is shown below. Note that you can get the code for the dialog by using the Samples Manager tool as described in Section 3.6, "Sample Code," or you can use a simple InputDialog to prompt for the contact information.

org.eclipsercp.hyperbola/AddContactAction
public void run() {
  AddContactDialog d = new AddContactDialog(window.getShell());
  int code = d.open();
  if (code == Window.OK) {
    Object item = selection.getFirstElement();
    ContactsGroup group = (ContactsGroup) item;
    ContactsEntry entry =
        new ContactsEntry(group, d.getNameText(), d.getNickname(),
            d.getServerText());
    group.addEntry(entry);
  }
}

The action needs the contact's name, nickname, and host server so it opens a dialog with three entry fields. The entered information is then used to create a ContactsEntry and add it to the previously selected group.

6.1.4. Adding the "Add Contact" Action

Now that you have created the Add Contact action, you need to add it to the top-level menu and toolbar. This part is easy since you did the same with the Exit and About actions. First, ensure that the action is created and registered in ApplicationActionBarAdvisor.makeActions(IWorkbenchWindow). Remember to register the action to ensure that it is deleted when the window is closed. Minimizing a window does not close it; instead, a window is closed when its Shell.close() method is called.

org.eclipsercp.hyperbola/ApplicationActionBarAdvisor
protected void makeActions(IWorkbenchWindow window) {
  this.window = window;
  exitAction = ActionFactory.QUIT.create(window);
  register(exitAction);
  aboutAction = ActionFactory.ABOUT.create(window);
  register(aboutAction);
  addContactAction = new AddContactAction(window);
  register(addContactAction);
}

Update fillMenuBar(IMenuManager) and fillCoolBar(ICoolBarManager) to add the action to both the menu and toolbar as shown in the ApplicationActionBarAdvisor snippet below. Note that managing toolbars and menus is very similar.

Note

It's common jargon to refer to the top-level toolbar simply as the toolbar. But the methods in the IWorkbenchWindowConfigurer refer to it as the coolbar. This is an implementation detail that has leaked into the APIs. The toolbar is implemented using an SWT CoolBar to support dynamic positioning of its controls. In this chapter, we refer to this area as the top-level toolbar.


org.eclipsercp.hyperbola/ApplicationActionBarAdvisor
protected void fillMenuBar(IMenuManager menuBar) {
  MenuManager hyperbolaMenu =
      new MenuManager("&Hyperbola", "hyperbola");
  hyperbolaMenu.add(addContactAction);
  hyperbolaMenu.add(new Separator());
  hyperbolaMenu.add(exitAction);
  MenuManager helpMenu = new MenuManager("&Help", "help");
  helpMenu.add(aboutAction);
  menuBar.add(hyperbolaMenu);
  menuBar.add(helpMenu);
}

protected void fillCoolBar(ICoolBarManager coolBar) {
  IToolBarManager toolbar = new ToolBarManager(coolBar.getStyle());
  coolBar.add(toolbar);
  toolbar.add(addContactAction);
}

Run the application and you should see an icon in the toolbar and an entry for Add Contact in the Hyperbola top-level menu. If you click the icon, you are prompted for the contact information, and after clicking OK, the contact is created and appears in the Contacts list. Notice that as you click around in the Contacts view, the action changes from enabled to disabled depending on the selection.

6.1.5. Customizable Toolbars

It is quite common for applications to allow customization of the toolbar. The Hyperbola toolbar is implemented in terms of the SWT CoolBar. Each ToolBarManager that is added to the ICoolBarManager is shown in a separate CoolItem group. As such, it can be separately positioned by the user. For example, toolbar managers can be moved onto separate rows or reordered within Hyperbola's toolbar. The Hyperbola in Figure 6-5 has two toolbar managers instead of one. Both managers have the Add Contact action. Since they are in separate managers, the actions can be moved independently within the toolbar. Experiment with this by adding the Add Contact action to multiple ToolBarManagers.

Figure 6-5. Top-level toolbar showing two cool items with the move handle


If you want your actions in the same toolbar group but separated, you can use JFace Separator instances to divide the actions into groups. An update that uses the Separator is shown below:

org.eclipsercp.hyperbola/ApplicationActionBarAdvisor
public void populateCoolBar(IActionBarConfigurer configurer) {
  ICoolBarManager mgr = configurer.getCoolBarManager();
  IToolBarManager toolbar = new ToolBarManager(mgr.getStyle());
  mgr.add(toolbar);
  toolbar.add(addContactAction);
  toolbar.add(new Separator());
  toolbar.add(addContactAction);
}


Previous Page
Next Page