Chapter 8. JFace

JFace is a Java application framework based on SWT. The goal of JFace is to provide a set of reusable components that make it easier to write a Java-based GUI application. Among the components JFace provides are such familiar GUI concepts as wizards, preference pages, actions, and dialogs. These components tend to be the bits and pieces that are integral to the basic widget set but are common enough that there is significant benefit to drawing them together into a reusable framework. Although its heritage is based on a long line of frameworks for writing IDEs, most of JFace is generally useful in a broad range of graphical desktop applications. JFace has a few connections to classes in the Eclipse runtime kernel, but it is fairly straightforward to extract JFace and SWT for use in stand-alone Java applications that are not based on the Eclipse runtime. JFace does not make use of such Eclipse-specific concepts as extensions and extension points.

FAQ 146: What is a viewer?

The purpose of a viewer is to simplify the interaction between an underlying model and the widgets used to present elements of that model. A viewer is not used as a high-level replacement for an SWT widget but as an adapter that sits beside an SWT widget and automates some of the more mundane widget-manipulation tasks, such as adding and removing items, sorting, filtering, and refreshing.

A viewer is created by first creating an SWT widget, constructing the viewer on that widget, and then setting its content provider, label provider, and input. This snippet is from the BooksView class in the FAQ Examples plug-in, which creates a table viewer to display a library of books:

   int style = SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL;
   Table table = new Table(parent, style);
   TableViewer viewer = new TableViewer(table);
   viewer.setContentProvider(new BookshelfContentProvider());
   viewer.setLabelProvider(new BookshelfLabelProvider());
   viewer.setInput(createBookshelf());

In general, JFace viewers allow you to create a model-view-controller (MVC) architecture. The view is the underlying SWT widget, the model is specified by the framework user, and the JFace viewer and its associated components form the controller. The viewer input is a model element that seeds the population of the viewer.

The JDT Package Explorer uses a TreeViewer to display the contents of a workspace, represented as a JavaModel. The UI manifests itself as shown in Figure 8.1.

Package Explorer showing the workspace contents

Figure 8.1. Package Explorer showing the workspace contents

The corresponding Spider diagram (Figure 8.2) shows the relationships among the Package Explorer, its viewer, its model, the underlying widget, and the label and content providers for the JDT Package Explorer.

Spider diagram of Package Explorer components

Figure 8.2. Spider diagram of Package Explorer components

Note

Spider diagram of Package Explorer components

FAQ 147 What are content and label providers?

FAQ 150 How do I sort the contents of a viewer?

FAQ 151 How do I filter the contents of a viewer?

FAQ 195 What is a view?

FAQ 196 What is the difference between a view and a viewer?

FAQ 147: What are content and label providers?

Minimally, all content viewers require you to supply a content provider and the label provider to interact with your model. The content provider is the viewer’s gateway to the structure of the model elements that will be displayed in the viewer. When an input is provided to a viewer, the viewer asks the content provider what elements to show for that input. For more complex structures, such as trees, the viewer asks the content provider when it needs to know the children or parent of a given model element.

In addition to answering these questions from the viewer, the content provider must also notify the viewer whenever the model has been changed. The content provider does this by calling the various incremental update methods on the viewer subclasses. Although these methods look slightly different, depending on the kind of viewer, they generally follow a naming convention:

  • add, for adding a single new element or batches of new elements.

  • remove, for removing single elements or batches of elements.

  • update, for updating the appearance of a single item, such as the label or icon. Optionally, this method takes a list of properties that are passed to the label provider, filters, and sorters to determine whether they are affected by the change.

  • refresh for naive updating of an element and its children.

Although refresh is the easiest of these methods to use, it is also generally the least efficient. Because it is provided with no context information to figure out exactly what has changed, it simply rebuilds the view from scratch, starting with the provided element. This can in turn trigger costly sorting and filtering of the elements.

The label provider deals only with presentation of individual model elements. The viewer asks the label provider only what text and what icon, if any, are associated with each model element? The label provider is also responsible for disposing of any images it creates when the viewer is disposed of.

Note

FAQ 147: What are content and label providers?

FAQ 146 What is a viewer?

FAQ 149 Why should I use a viewer?

FAQ 152 How do I use properties to optimize a viewer?

FAQ 148: What kinds of viewers does JFace provide?

JFace includes the following basic types of viewers:

  • TreeViewer connects to an SWT Tree widget.

  • TableViewer connects to an SWT Table widget.

  • ListViewer connects to an SWT List widget. Note that because the native List widget cannot display icons, most applications use a TableViewer with a single column instead.

  • TableTreeViewer connects to an SWT TableTree widget. A TableTree is a table whose first column can contain a tree, and each row of the table corresponds to a single item in the tree. The Properties view by default uses a TableTree.

  • TextViewer connects to an SWT StyledText widget.

  • CheckboxTreeViewer and CheckboxTableViewer are just like their namesakes, but have a check box next to each tree and table entry.

In addition to these basic viewer types, you are free to implement your own or to subclass an existing viewer to obtain specialized functionality. Viewers, however, are designed to be extremely customizable without the need to create a subclass.

As a historical footnote, an earlier version of JFace used viewer subclassing as the mechanism for customization. This led to a profusion of “monolithic” subclasses that were specialized for a particular purpose. The problem with subclassing is that it provides only one dimension of reuse. If you wanted to create a new viewer that filtered its contents like viewer X but displayed its contents like viewer Y, you were stuck with subclassing the viewer with the most contents in common and using copy and paste or other ad-hoc forms of reuse to get what you needed. With the current JFace viewer architecture, each pluggable component provides a new dimension of reuse. You can still subclass a viewer for very specialized situations, but you can generally get away with instantiating one of the standard viewers and installing the necessary pieces to get the behavior you want.

Note

FAQ 148: What kinds of viewers does JFace provide?

FAQ 146 What is a viewer?

FAQ 149 Why should I use a viewer?

FAQ 149: Why should I use a viewer?

The main benefit of JFace viewers is that they allow you to work directly with your model elements instead of with low-level widgets. Instead of creating, disposing of, and modifying TreeItem instances in a Tree widget, you use the TreeViewer convenience methods, which deal directly with objects from your domain model. This makes your code cleaner and simpler, off-loading the complex work of finding and updating the widgets corresponding to your model elements when they change. These viewer methods have also been heavily tested and optimized over the years, which means that you will probably get a much more efficient, bug-free implementation than you would if you had written it yourself.

Another benefit of the viewer framework is that it is designed to encourage reuse. Viewers are customized by plugging in modular pieces rather than by subclassing. Each of these modular pieces addresses a specific concern, such as structure, appearance, and sorting. These pieces act as the unit of reuse: Any given piece can be reused in multiple viewers independently of the others. Thus, the piece that describes the appearance of your model elements—the label provider—can be reused in several viewers, independently of the piece that describes the relationship between elements—the content provider.

Aside from these main benefits, JFace viewers provide numerous other features that we don’t have room to describe in detail.

Here is a quick overview of some of the main benefits of JFace viewers.

  • TreeViewer populates lazily, avoiding the work of creating and updating items that are not visible.

  • They support simple, model-based drag-and-drop.

  • They have methods for querying or changing the selection and for adding selection listeners.

  • They support pluggable sorters and content filters.

  • Cell editors allow the user to modify table values in place.

  • Decorators are available for adding annotations to the text and icons of viewer items.

In an earlier version of JFace, predating open source, viewers largely encapsulated the underlying widget toolkit, allowing you to create an application with few direct dependencies on the underlying widgets. Porting the application to another toolkit would thus mainly consist of porting JFace. This saved a great deal of effort when early prototypes built on JFace switched from a Swing-based implementation to an SWT-based implementation. But it was decided that this encapsulation required far too much duplication of SWT-level functionality and didn’t always allow the native look and feel to shine through. Viewers were then simplified to their current form as adapters on the side of an SWT widget, enhancing but not hiding its function.

Note

FAQ 149: Why should I use a viewer?

FAQ 146 What is a viewer?

FAQ 148 What kinds of viewers does JFace provide?

FAQ 150: How do I sort the contents of a viewer?

Structured viewers are sorted by plugging in an instance of ViewerSorter, using the StructuredViewer.setSorter method. For simple sorting based on label text, use the generic ViewerSorter class itself, optionally supplying a java.text.Collator instance to define how strings are compared. For more complex comparisons, you’ll need to subclass ViewerSorter. You can override the category method to divide the elements up into an ordered set of categories, leaving the text collator to sort the elements within each category. For complete customization of sorting, override the compare method, which acts much like the standard java.util.Comparator interface.

Note

FAQ 150: How do I sort the contents of a viewer?

FAQ 146 What is a viewer?

FAQ 152 How do I use properties to optimize a viewer?

FAQ 151: How do I filter the contents of a viewer?

You do this with a ViewerFilter. ViewerFilters are added to a structured viewer by using the addFilter method. When a filter is added, it defines a subset of the original elements to be shown. Writing your own filter is trivial: Subclass ViewerFilter and override the method select. This method returns true if the element should be shown and false if it should be hidden. If more than one filter is added, the viewer will show only their intersection; that is, all filters must return true in their select method for the element to be shown.

One final tidbit of useful information: StructuredViewer has a convenience method, resetFilters, for removing all of a viewer’s filters. This is more efficient than removing filters one at a time as each time a filter is added or removed, the entire viewer needs to be refreshed.

Note

FAQ 151: How do I filter the contents of a viewer?

FAQ 146 What is a viewer?

FAQ 150 How do I sort the contents of a viewer?

FAQ 152 How do I use properties to optimize a viewer?

FAQ 152: How do I use properties to optimize a viewer?

The word property is one of those unfortunate programming terms that is overused because it can mean just about anything. The downside of using such a semantically weak term is that in different contexts it can mean very different things. The Eclipse Project certainly does not escape this foible: At the time of this writing, 154 classes and 1,105 methods contain the word property in the Eclipse SDK code base.

In the context of JFace viewers, property means any aspect that has relevance to how the domain objects being displayed are presented. Concretely, this means that whenever a property changes, the visual presentation of the viewer has to be updated in some way.

The interface IBasicPropertyConstants defines some simple properties—parent, children, text, and image—but the set of possible properties is open ended. Let’s say, for example, that you have a tree viewer that is showing a person’s ancestral tree. The label of each tree item may contain different information, such as the person’s name and year of birth. The viewer may have several filters for showing subsets of the family tree, such as gender or hair color. You could define a set of domain-specific properties for that viewer to enumerate all these values:

    public interface IAncestralConstants {
       public static final String BIRTH_YEAR = "birthyear";
       public static final String NAME = "name";
       public static final String GENDER = "gender";
       public static final String HAIR_COLOR= "hair-color";
    }

Note that a property doesn’t necessarily represent something that is directly visible but rather something that affects the presentation in some way.

The label provider, filters, and sorters associated with the viewer define which properties affect them. In this case, the label provider cares only about the name and birth year, so it would override the isLabelProperty method on IBaseLabelProvider as follows:

    public boolean isLabelProperty(Object el, String prop) {
       return IAncestralConstants.NAME.equals(prop) ||
          IAncestralConstants.BIRTH_YEAR.equals(prop);
    }

Similarly, a sorter that sorts entries by name would override the method ViewerSorter.isSorterProperty to return true only for the NAME property. A filter that defines a subset based on hair color would override ViewerFilter.isFilterProperty to return true only for the HAIR_COLOR property.

Still not seeing the point of all this? Well, here’s the interesting bit. When changes are made to the domain objects, the viewer’s update method, defined on StructuredViewer, can be passed a set of properties to help it optimize the update:

    viewer.update(person, new String[] {NAME});
    viewer.update(person, new String[] {GENDER, HAIR_COLOR});

The viewer update algorithm will ask the label provider and any installed sorter and filters whether they are affected by the property being changed. This information can allow the update method to make some very significant optimizations. If the sorter and filters are not affected by the change, the update is very fast. If any sorter or filter is affected by the change, significant work has to be done to update the view.

You can ignore this whole mechanism by passing null as the properties to the update method, but it means that a complete refresh will happen on every update. Resist the temptation to do this, especially if your viewer will ever have sorters or filters attached to it. For a viewer that contains a large number of values, the speedup from using properties can be significant.

Note

FAQ 152: How do I use properties to optimize a viewer?

FAQ 146 What is a viewer?

FAQ 153: What is a label decorator?

A DecoratingLabelProvider can be used when an element’s text and image need to be annotated to show different states or properties. This is accomplished by bringing together a standard label provider with an optional decorator. The standard label provider is first asked to generate the text and image for the input element, and then the decorator, if installed, is allowed to augment or replace the original text and image with new values. JFace provides a helper class, CompositeImageDescriptor, to help you combine an image with one or more overlays. Because multiple overlays in a particular position will obscure one another, you must be careful to avoid overlapping decorators when creating a decorating label provider.

Figure 8.3 shows how the JDT’s Package Explorer uses a decorating label provider to decorate resources with a decorator to indicate either a warning or an error.

Spider diagram of the parts of a decorating label provider

Figure 8.3. Spider diagram of the parts of a decorating label provider

Lightweight label decorators were introduced in Eclipse 2.1. These decorators abstract away the details of performing the image overlay. An implementation of ILightWeightLabelDecorator is provided with an IDecoration object, which it calls in order to add the decorations. The following simple example decorates a viewer containing java.io.File objects to indicate whether they are read-only:

   ImageDescriptor readOnlyOverlay = ...;
   public void decorate(Object element, IDecoration decoration) {
      if (!(element instanceof java.io.File))
         return;
      boolean readOnly = !( (java.io.File) element).canWrite();
      if (!readOnly)
         return;
      decoration.addOverlay(readOnlyOverlay);
      decoration.addSuffix(" (read only)");
   }

A decorating label provider is installed on a viewer in the same way as any other label provider:

   ILabelProvider lp = ... the basic label provider
   ILabelDecorator decorator = ... the decoration
   viewer.setLabelProvider(
      new DecoratingLabelProvider(lp, decorator));

Note

Spider diagram of the parts of a decorating label provider

FAQ 146 What is a viewer?

FAQ 147 What are content and label providers?

FAQ 174 How do I create a label decorator declaratively?

FAQ 175 How do I add label decorations to my viewer?

FAQ 154: How do I use image and font registries?

With SWT, you are responsible for managing the lifecycle of such operating system resources as fonts, colors, and images. Most of the time, you can simply tie the lifecycle of these resources to the lifecycle of the widget using them. They can be allocated when a shell is opened, for example, and disposed of when the shell is disposed of.

Sometimes, a resource is used very frequently or must be shared among several widgets for unknown lengths of time. In these situations, the resources can be stored in a permanent registry. JFace provides three such registries: FontRegistry, ImageRegistry, and ColorRegistry. You can instantiate one of these registries yourself or use the global ones accessible from the JFaceResources class. The advantage to using a private instance is that you don’t have to worry about your keys overlapping with the keys of another component. Here is an example of creating and accessing images in a private image registry:

   static final String IMAGE_ID = "my.image";
   ...
   //declaring the image
   ImageRegistry registry = new ImageRegistry();
   ImageDescriptor desc = ImageDescriptor.createFromURL(...);
   registry.put(IMAGE_ID, desc);
   ...
   //using the image
   Button button = new Button(...);
   Image image = registry.get(IMAGE_ID);
   button.setImage(image);

These registries create resources lazily as they are accessed, thus avoiding the expense of creating resources that may never be used. The resources are disposed of automatically when the display is disposed of.

A FontRegistry can also be loaded from a Java properties file, allowing you to specify different fonts, depending on the operating system, windowing system, or locale. See the javadoc for FontRegistry for more information on the format and naming conventions for these property files. Finally, the JFace font registry allows you to replace the font for a given key. This allows you to centralize the handling of user font changes around the font registry. When the user changes the font, you put the new value into the font registry. The registry then sends out notifications to registered listeners, allowing them to update their presentation with the new font.

Keep in mind that it is not practical to store all your resources in a permanent registry. Windowing systems generally limit the number of resource handles that can be in use. A large application with many resources can cause the windowing system to run out of resource handles, causing failures in your application or even in other applications.

Note

FAQ 154: How do I use image and font registries?

FAQ 139 Why do I have to dispose of colors, fonts, and images?

FAQ 155: What is a wizard?

A wizard is a series of pages that guide a user through a complex task. Back and Next buttons allow the user to move forward and backward through the pages. Typically, each page collects a piece of information; when the user clicks the Finish button, the information is used to perform a task. At any time before clicking Finish, the user can cancel the task, which should undo any side effects of the steps completed so far. Figure 8.4 shows a simple wizard that is included in the FAQ Examples plug-in.

Sample wizard

Figure 8.4. Sample wizard

A wizard is typically presented in a dialog, but this is not required. The abstraction called IWizardContainer represents the context in which a wizard runs. A wizard container is guaranteed to have a title, a message area, and a progress monitor. A wizard must implement IWizard, and each page within the wizard must implement IWizardPage.

Note

Sample wizard

FAQ 156 How do I specify the order of pages in a wizard?

FAQ 159 How do I make my wizard appear in the UI?

FAQ 156: How do I specify the order of pages in a wizard?

In its simplest and most common form, a wizard is made up of a static, ordered list of pages. A wizard adds pages to the list by using the Wizard.addPage method, and the Next and Back buttons simply cycle through this ordered list. For a wizard with a static set of pages, you can simply override the addPages method and add all of your pages at once. This snippet is from AddingWizard in the FAQ Examples plug-in:

   public class AddingWizard extends Wizard {
      private AddingWizardPage page1, page2;
      ...
      public void addPages() {
         page1 = new AddingWizardPage("Page1",
            "Please enter the first number");
         addPage(page1);
         page2 = new AddingWizardPage("Page2",
            "Enter another number");
         addPage(page2);
      }
   }

If you want to change the behavior of the wizard, depending on the values that are entered, you can dynamically create new pages or change the page order after the wizard is opened. Pages must still be added to the wizard by using the addPage method, but the progression of pages through the wizard can change, depending on the user’s input. This ordering is changed by overriding the wizard methods getNextPage and getPreviousPage, which are called when the user clicks on Next or Back button.

Even with dynamic wizards, you must create at least one page before the wizard is opened. If only one page is added at the beginning, but more pages will be added later on, you will need to call Wizard.setForcePreviousAndNextButtons(true) before the wizard is opened. Otherwise, the Next and Back buttons will appear only if more than one page is in the page list.

Note

FAQ 156: How do I specify the order of pages in a wizard?

FAQ 155 What is a wizard?

FAQ 156 How do I specify the order of pages in a wizard?

FAQ 157: How can I reuse wizard pages in more than one wizard?

The wizard framework is designed to allow for very loose coupling between a wizard page and its containing wizard. Just as a wizard interacts with an abstraction of its container via IWizardContainer, a wizard page can interact with the wizard it is part of, using the generic IWizard interface. This allows the page to act as a reusable piece that can be incorporated into different concrete wizards.

Of course, it is up to the page implementer to ensure that tighter coupling does not creep in. Some wizard page implementations pass a reference to a concrete wizard in the page constructor, thus making it difficult or impossible to reuse the page in a different wizard. To avoid this, try to keep each wizard page as generic as possible. A page is really only a mechanism for gathering information from the user. The page should simply expose accessors for the information it collects, allowing the concrete wizard to pull together all the information and use it for that wizard’s ultimate goal.

Note

FAQ 157: How can I reuse wizard pages in more than one wizard?

FAQ 158 Can I reuse wizards from other plug-ins?

FAQ 158: Can I reuse wizards from other plug-ins?

Yes, as long as the wizards expose them as API. Most of the time, plug-ins will make their wizard pages API but will not make the wizard itself API. The reason is that most of the interesting functionality is in the pages anyway, and other plug-ins wanting to reuse a wizard will generally want to insert some of their own pages as well.

Almost all the wizard pages you see in the Eclipse SDK are available as API, so you can use them in your own wizard. The pages from the import and export wizards, along with the wizards for creating simple files, folders, and projects, are found in the org.eclipse.ui.ide plug-in in the org.eclipse.ui.dialogs package. The import and export pages are abstract, but they allow subclasses to insert the controls for the source or destination of the import, and the abstract page takes care of the rest. The org.eclipse.jdt.ui plug-in exposes wizard pages for creating Java projects, packages, and types in the org.eclipse.jdt.ui.wizards package.

Note

FAQ 158: Can I reuse wizards from other plug-ins?

FAQ 157 How can I reuse wizard pages in more than one wizard?

FAQ 159: How do I make my wizard appear in the UI?

To make a wizard appear, you need an implementation of the wizard interface called IWizardContainer. The container is responsible for all the presentation outside the pages themselves, including a title area, button bar, and progress indicator. You can implement this interface yourself if you want to embed a wizard into a custom control. JFace provides a default wizard container that is a simple modal dialog: WizardDialog. The following code snippet opens a wizard in a wizard dialog:

   Shell shell = window.getShell();
   AddingWizard wizard = new AddingWizard();
   WizardDialog dialog = new WizardDialog(shell, wizard);
   int result = dialog.open();

Note

FAQ 159: How do I make my wizard appear in the UI?

FAQ 155 What is a wizard?

FAQ 181 How do I add my wizard to the New, Import, or Export menu categories?

FAQ 160: How do I run a lengthy process in a wizard?

The IWizardContainer passed to your wizard extends IRunnableContext. This means that you can pass it an IRunnableWithProgress and that it will give progress feedback to the user while it runs. Keep in mind that as with all long-running operations, the container will generally fork a different thread to run your operation. If you want to manipulate any widgets from the operation, you’ll have to use Display.asyncExec.

The FAQ Examples plug-in includes a sample wizard, called AddingWizard, that computes the sum of two integers, using the wizard container’s progress monitor:

   getContainer().run(true, true, new IRunnableWithProgress() {
      public void run(IProgressMonitor monitor) {
         int sum = n1 + n2;
         monitor.beginTask("Computing sum: ", sum);
         for (int i = 0; i < sum; i++) {
            monitor.subTask(Integer.toString(i));
            //sleep to simulate long running operation
            Thread.sleep(100);
            monitor.worked(1);
         }
         monitor.done();
      }
   });

Your wizard can specify whether it needs a progress bar or a simple busy cursor. For operations that may take more than a second, you should use a progress bar. This is done by implementing needsProgressMonitor method on IWizard to return true.

Note

FAQ 160: How do I run a lengthy process in a wizard?

FAQ 119 How do I use progress monitors?

FAQ 140 Why do I get an invalid thread access exception?

FAQ 161: How do I launch the preference page that belongs to my plug-in?

Sometimes, you want to allow the user to edit preferences for your plug-in quickly, without manually opening the Preference dialog via Window > Preferences and locating the page for your plug-in. You can do this by launching the Preference dialog yourself with a customized PreferenceManager, a class responsible for building the tree of preference nodes available on the left-hand side of the Preference dialog.

For each preference page you want to display, create a PreferenceNode instance that contains a reference to your page and some unique page ID. Pages are then added to a preference manager. If you want to create a hierarchy of pages, use the PreferenceManager method addTo, where the path is a period-delimited series of page IDs above the page being added. Finally, create an instance of PreferenceDialog, passing in the preference manager you have created. Here is sample code for opening the Preference dialog on a single preference page called MyPreferencePage:

   IPreferencePage page = new MyPreferencePage();
   PreferenceManager mgr = new PreferenceManager();
   IPreferenceNode node = new PreferenceNode("1", page);
   mgr.addToRoot(node);
   PreferenceDialog dialog = new PreferenceDialog(shell, mgr);
   dialog.create();
   dialog.setMessage(page.getTitle());
   dialog.open();

Note

FAQ 161: How do I launch the preference page that belongs to my plug-in?

FAQ 178 How do I create my own preference page?

FAQ 162: How do I ask a simple yes or no question?

The MessageDialog class provides a couple of convenience methods for asking questions with Boolean answers. The openQuestion method will present a dialog with Yes and No buttons and will return the result. The openConfirm method is similar but uses Ok and Cancel as the button labels. If you want to use different button labels or have more buttons, such as Yes/No/Always/Never, you can construct a customized dialog yourself:

   MessageDialog dialog = new MessageDialog(
      null, "Title", null, "Question",
      MessageDialog.QUESTION,
      new String[] {"Yes", "No", "Always", "Never"},
      0);, // yes is the default
   int result = dialog.open();

The return value from the open method will be the index of the button in the label array. If the user cancels or closes the dialog, a result of one is returned by convention. This means that you should try to make your second button match the behavior that makes sense for your circumstances. If you want completely different behavior for dialog cancellation, you will need to subclass MessageDialog and override the cancelPressed method.

The MessageDialogWithToggle class—introduced in Eclipse 3.0—is an extension of MessageDialog that adds the capability of remembering the user’s selection to avoid having to prompt them again. This can be used for introductory warning messages that advanced users want to be able to turn off.

MessageDialogWithToggle has similar static convenience methods as MessageDialog for common questions, but you can use the constructor to provide a customized set of buttons if necessary.

FAQ 163: How do I inform the user of a problem?

Warnings and errors can be reported to the user by using an ErrorDialog. Here is a very simple example of an Error dialog displaying a warning message:

   IStatus warning = new Status(IStatus.WARNING,
      ExamplesPlugin.ID, 1, "You have been warned.", null);
   ErrorDialog.openError(window.getShell(),
      "This is your final warning", null, warning);

The null parameter to the status constructor is for supplying an exception object. The exception object is not used by the Error dialog, so we’ll leave it blank in this case. If you’re creating a status object for another purpose, such as logging, you’ll want to supply the exception, if there is one. The openError method has parameters for a title and a message, but both of these parameters are optional. If you don’t supply a title, the method will use the generic Problems Occurred message. If you don’t supply a message, the method will take the message from the supplied status object.

Note

FAQ 163: How do I inform the user of a problem?

FAQ 121 How do I use the platform logging facility?

FAQ 164 How do I create a dialog with a details area?

FAQ 164: How do I create a dialog with a details area?

Many dialogs in Eclipse have a Details button that shows or hides an extra area with more information. This functionality is provided by the JFace ErrorDialog. The naming of this class is a bit unfortunate as it doesn’t fully express all the things it can be used for. A better name might have been StatusDialog as it is used to display any IStatus object, which can represent information, warnings, or errors. The dialog looks at the severity of the supplied status and uses an appropriate icon: an exclamation mark for errors, a yield sign for warnings, and an i character for information.

If you want to provide more information in the details area, you need to supply a MultiStatus object. The dialog will obtain the message string from the MultiStatus parent, and one line in the details area will be for the message from each child status. The following example uses an error dialog to display some information—the date—with more details provided in the details area:

   Date date = new Date();
   SimpleDateFormat format = new SimpleDateFormat();
   String[] patterns = new String[] {
      "EEEE", "yyyy", "MMMM", "h 'o' 'clock'"};
   String[] prefixes = new String[] {
      "Today is ", "The year is ", "It is ", "It is "};
   String[] msg = new String[patterns.length];
   for (int i = 0; i < msg.length; i++) {
      format.applyPattern(patterns[i]);
      msg[i] = prefixes[i] + format.format(date);
   }
   final String PID = ExamplesPlugin.ID;
   MultiStatus info = new MultiStatus(PID, 1, msg[0], null);
   info.add(new Status(IStatus.INFO, PID, 1, msg[1], null));
   info.add(new Status(IStatus.INFO, PID, 1, msg[2], null));
   info.add(new Status(IStatus.INFO, PID, 1, msg[3], null));
   ErrorDialog.openError(window.getShell(), "Time", null, info);

Note

FAQ 164: How do I create a dialog with a details area?

FAQ 163 How do I inform the user of a problem?

FAQ 165: How do I set the title of a custom dialog?

If you have created your own subclass of the JFace Dialog class, you can set the dialog title by overriding the configureShell method:

   protected void configureShell(Shell shell) {
      super.configureShell(shell);
      shell.setText("My Dialog");
   }

The configureShell method can also be used to set the dialog menu bar image (Shell.setImage), the font (Shell.setFont), the cursor (Shell.setCursor), and other shell attributes.

FAQ 166: How do I save settings for a dialog or wizard?

The IDialogSettings mechanism can be used to store any hierarchical collection of strings and numbers. It is used in Eclipse to persist preferences and history for most views, dialogs, and wizards. For example, dialogs will often store the last few values entered in each entry field and allow the user to select from these previous values, using a combo box. Views will use dialog settings to store filtering, sorting, and layout preferences. In all these cases, the basic steps involved are the same.

First, you need to find or create an instance of IDialogSettings. If your plug-in is a subclass of AbstractUIPlugin, your plug-in already has a global setting object. Simply call the method getDialogSettings on your plug-in to obtain an instance. If you’re not using AbstractUIPlugin, you can create an instance of DialogSettings yourself.

Once you have a settings instance, you simply store the data values you want to keep on the settings object. If you’re using a global settings object, you can add subsections, using addNewSection. Typically, each dialog, wizard page, and view will have its own settings section within the global settings instance.

Finally, the settings must be saved to disk at some point. If you’re using the AbstractUIPlugin settings instance, it is saved automatically when the plug-in is shut down. If you created the settings yourself, you have to save the settings manually when you’re finished changing them by calling the save method. Note that each plug-in has a state location allotted to it, and you can find out the location by calling getStateLocation on your plug-in instance. The following example loads a settings file from disk, changes some values, and then saves it again:

   IPath path = ExamplesPlugin.getDefault().getStateLocation();
   String filename = path.append("settings.txt").toOSString();
   DialogSettings settings = new DialogSettings("Top");
   settings.load(filename);
   settings.put("Name", 'Joe");
   settings.put("Age", 35);
   settings.save(filename);

Note

FAQ 166: How do I save settings for a dialog or wizard?

FAQ 204 How does a view persist its state between sessions?

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.165.62