October 1, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

The (J)Face of Eclipse

  • November 17, 2005
  • By Peter Nehrer
  • Send Email »
  • More Articles »

Adding a Custom Dialog

As you already know, one of the uses of a dialog box is to present information to the user at a point in time. Your custom dialog displays additional properties of the view that is currently selected in the Superview. You could use other means to accomplish the same, but this is a reasonable example for demonstrating the creation of custom dialog boxes.

You start by creating a new class named ViewPropertiesDialog as a subclass of org.eclipse.jface.dialogs.Dialog. This is the base class of JFace's Dialog hierarchy and by default displays an empty dialog box with OK and Cancel buttons. You implement a constructor that takes the required parent Shell (in which to open the dialog window) and the view descriptor (IViewDescriptor) whose properties to display—you keep it for populating the widgets with property values.

You customize the dialog by extending method createDialogArea(Composite). Much like the view's createPartControl method, this method gives you a Composite, which you can use as the parent of each widget you create. It differs in that you are required to return the top-level composite you create, so that the dialog can use it for its own bookkeeping purposes. Here, you actually extend the method, rather than completely overriding it—you call its superclass implementation before doing anything else. The reason is that the superclass creates for you a container Composite, configured with the standard dialog layout and margins. This is the composite you actually use as the parent of your widgets and return as the result of this method.

Before you start creating any widgets, you alter the container composite's layout by changing its number of columns to two, to get a column of property labels, followed by a column of property values. You then continue to create a Label and a read-only Text for each property of interest. For values, you use a Text control, rather than another Label, because even though it is read-only, the user can still select the displayed values and copy them to the Clipboard (something they cannot do with Labels). Note that each created widget gets a GridData to control its layout within the container composite—labels are left-aligned horizontally, center-aligned vertically, and don't "grab" the excess cell space, whereas the text controls are told to fill their cells horizontally and grab any excess cell space. This causes the text controls to receive as much space as needed to display their content. For better clarity, creation of labels and text controls has each been moved into a reusable method.

A few properties do not follow this simple pattern—the icon property should display the view's icon, the description property should show its content using multiple rows, and the "allows multiple instances" property should be a read-only checkbox (just to demonstrate a different control type). To display the icon, you use the Label control instead of Text, because Labels are capable of displaying images, too. You obtain the icon's image much like you do in Superview's label provider (and must remember to dispose it accordingly).

To display description, you use both columns to get the maximum space. You accomplish this by instructing the layout to make each widget span two columns using its layout data. To make it easier to read multi-row view descriptions, you configure the text widget to wrap its content and display a vertical scrollbar (you do this by specifying the appropriate style bits in the widget's constructor). Finally, in the layout data you specify that the height of the widget's cell should be four times the height of its text line (plus two, for good measure). Note that because view descriptions are actually quite rare, and the description widget takes up a lot of screen real estate, you only create it when there actually is a description.

To indicate whether the view "allows multiple instances," you create a checkbox—a Button with the checkbox style bit. You also make it span two columns, and set its selection state (checked/unchecked) to match the property's value. Finally, you disable it because you don't want the user to be able to change its value (because there's nothing in your model they can actually change).

There are a few other tasks to complete before you can call the dialog done. For one, you should set its title to something useful (by default, it is blank). You can do this in the configureShell(Shell) method, as part of configuring the dialog's shell. You also must change the set of dialog buttons, which by default contains an OK and a Cancel button. Your dialog is purely informational, and should therefore display only a Close button. You create it in the createButtonsForButtonBar() method, and react to the user clicking it in the buttonPressed(int) method by closing the dialog. Finally, you extend the close method to dispose of the icon image.

To actually open the dialog when the user selects the appropriate context menu option, you implement the corresponding action's run method—you instantiate the dialog, pass it the view's shell and the selected view descriptor, and call its open() method. That's it!

Adding a Multi-Page Wizard

From user's perspective, wizards are essentially dialog boxes with one or more pages; the user can navigate between these pages using the Previous and Next buttons, and click Finish to complete the task. From a developer's perspective, you don't actually program against the Dialog API. The wizard pattern is abstracted into class Wizard, which is composed of one or more WizardPages, and is "hosted" in the WizardDialog (or another appropriate IWizardContainer).

To demonstrate this concept, you create a custom wizard to allow the user to select an existing (or new) workbench window in which to show the selected view. While typically only one instance of a given view may exist in a workbench window, some views may be designed to allow multiple instances. Such views may be created with a different "secondary ID"—I will discuss the details of workbench views in another installment. In your wizard, you allow the user to specify this secondary ID in another wizard page for views that support it. You are right if you think that this use case could easily be handled in a single-page wizard or dialog; however, the goal here is to demonstrate the creation of a multi-page wizard.

You start by creating a subclass of org.eclipse.jface.wizard.Wizard, named ShowInWindowWizard. You give it a constructor that takes an IViewDescriptor to represent the currently selected view, and an IWorkbenchWindow to represent the window to pre-select. You use them later when creating wizard pages. To actually add your wizard pages, you need to extend the addPages() method. But first, you must implement them.

The first page in this wizard displays a list of open workbench windows for the user to select. In addition, you want to allow the user to open a new workbench window, if they choose to. To implement this page, you create a nested class called WindowPage and make it a subclass of org.eclipse.jface.wizard.WizardPage. In its constructor, you set the page title and description. The user interface of the page is constructed in its createControl(Composite) method, where again you get a Composite to use as the parent of your widgets. In this page, you use a ListViewer to show the list of open workbench windows. This viewer is very similar to TableViewer; after instantiating it (this time, you do specify a few extra style bits), you configure it with a content provider, a label provider, and a sorter. Because you're showing workbench windows in the list (more precisely, their titles), you have to provide your own content and label providers (implemented in two nested classes named WorkbenchWindowContentProvider and WorkbenchWindowLabelProvider, respectively). You set the workbench itself as the viewer's input (because that is the model your content provider understands). You also set the workbench window passed into the wizard's constructor as the default selection. Finally, you inform the page of the newly created control by calling its setControl(Control) method (so that it can perform any bookkeeping it may need) and passing in the viewer's underlying control.

Listing 4: WindowPage's createControl:

public void createControl(Composite parent) {
   listViewer = new ListViewer(parent, SWT.SINGLE | SWT.V_SCROLL |
                               SWT.H_SCROLL | SWT.BORDER);
   listViewer.setUseHashlookup(true);
   listViewer.setContentProvider(new WorkbenchWindowContentProvider());
   listViewer.setLabelProvider(new WorkbenchWindowLabelProvider());
   listViewer.setSorter(new ViewerSorter());
   listViewer.setInput(workbenchWindow.getWorkbench());
   listViewer.setSelection(new StructuredSelection(workbenchWindow),
                           true);
   setControl(listViewer.getControl());
}

By design, this wizard is to have an optional second page, depending on the type of view being shown. Views that allow multiple instances in a single workbench window should cause the second page to be available to the user. To accomplish this, you make the enablement of the Next button on the first wizard page, determined by method canFlipToNextPage(), conditional upon this property of the selected view (in other words, IViewDescriptor.getAllowMultiple() must return true).

The second wizard page, implemented in nested class ViewPage, is rather simple. It contains a text field, preceded by a descriptive label, where the user can enter an optional secondary ID to use when showing the view.

Now that you implemented both pages, you can instantiate them in the wizard's addPages() method and add each to the wizard using its addPage(IWizardPage) method. You also override its canFinish() method to always return true, because the second page is always optional and the first one will never contain invalid data (in other words, will never be incomplete).

To make the wizard perform the actual task of showing the selected view when the user clicks Finish, you implement its performFinish() method. Wizard tasks are usually long operations, and so you should give the user some progress feedback, optionally allow them to cancel it, and may even choose to run it in a background thread. You can accomplish all this by implementing your operation as an IRunnableWithProgress, which consists of a single run(IProgressMonitor) method. When instantiated, you pass this runnable into the wizard container's run(boolean, boolean, IRunnableWithProgress) method, which presents the user with a progress bar while running the operation.

Again, the details of showing the selected view in the selected (or new) workbench window are not important in this article. You may, however, take a closer look at how you use the progress monitor throughout the run method. First, you call its beginTask(String, int) method, which indicates the beginning of your job. Then, you wrap the entire body in a try/finally block, and call the monitor's done() method in the finally clause. In the body of the operation, you increment the indicated progress by calling the monitor's worked(int) method, and check for user's cancellation by calling the isCancelled() method (if the user did cancel, you must respond by throwing an OperationCancelledException).

Lastly, notice how the wizard container's run method throws two types of exceptions—an InvocationTargetException, which is thrown when anything goes wrong while executing the operation, and InterruptedException, when the user cancels the operation (or the background thread, if used, is interrupted). When the former is caught, you log it using your plug-in's logger and show a message box displaying the error to the user. When the latter is caught, you simply ignore it; however, in both cases, you return false from the performFinish method, which will cause the wizard to remain open. Only when the operation succeeds do you allow the wizard to close (by returning true).

Just as you did with your custom dialog, you must finish implementing the view's action that invokes the wizard. In it, you first create an instance of your wizard, pass it into a new instance of WizardDialog, and open the dialog.





Page 4 of 5



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Sitemap | Contact Us

Rocket Fuel