Eclipse Corner Article |
Summary
Managing images in a large graphical application can be a daunting task. Since modern operating systems such as Windows® only support a small number of images in memory at once, an application’s icons and background images must be carefully managed and sometimes shared between widgets. This article describes the image management facilities provided by the Eclipse Platform, along with some best practice guidelines to keep in mind when writing your own Eclipse UI plug-ins. We assume the reader already has a basic understanding of Eclipse, the UI extension points defined by the Eclipse Platform, and the Standard Widget Toolkit (SWT).By John Arthorne, OTI
April 20, 2001; updated September 12, 2002 for Eclipse 2.0
We begin by describing the interesting classes to know about when writing UI plug-ins that define their own images. Just about anybody writing substantial UI components for a plug-in needs to understand these basic image management objects.
org.eclipse.swt.graphics.Image. Instances of Image are ready for display on the screen. They have all their data in memory, and they maintain a handle to an underlying OS resource. These are heavy-weight objects that generally should not be held onto indefinitely. When no longer needed, the user must call dispose() on the Image to free the underlying resource. The crucial point to understand here is that these objects are very expensive to leave lying around. The operating system typically only allows a fixed number of images at once. For example, Windows 98 only allows about 1000 images in memory before it begins to fail. Whenever you create an instance of Image, think about how long it will be needed, and make sure that a mechanism is in place to dispose of it when no longer needed. You cannot rely on the Java™ garbage collector to do this for you, since Images reference memory allocated by the OS, not the Java virtual machine. An interesting characteristic of Images is that one Image instance can be placed in multiple widgets, a technique called image sharing. A major challenge of image management is to take advantage of this sharing as much as possible, but to still know when it is safe to dispose the image.
<extension point="org.eclipse.ui.import"> <wizard id="com.xyz.ImportWizard1" name="XYZ Web Scraper" class="com.xyz.imports.ImportWizard1" icon="./images/import1.gif"> </wizard> </extension>
Here, the wizard is given an image called "import1.gif", found in a subdirectory of the plug-in's base directory called "images". See the documentation for each of the above extension points for more details and examples of their use.
There are many advantages to specifying images directly in your XML file, over defining images programmatically in your UI code:
Step 1: Create an image using your favorite image editing program. For action icons, the recommended format is a 16x16 pixel image in the GIF format. For this example, we'll call the image "my_action.gif", and place it in a subdirectory of the plug-in base directory called "images".
Step 2: Define an ImageDescriptor in your MyAction class. It's a good idea to make this field static so that it can be shared across instances of the action.
public class MyAction extends Action { private static ImageDescriptor image; static { URL url = null; try { url = new URL(MyPlugin.getInstance().getDescriptor().getInstallURL(), "images/my_action.gif"); } catch (MalformedURLException e) { } image = ImageDescriptor.createFromURL(url); } ... other methods and fields defined here ... }
Because plug-ins can be stored anywhere, we use URLs to describe their location. The image location will always be the same relative to the plug-in location, so we use that as the base of the image's URL. We then use the factory method defined on the ImageDescriptor class to create our descriptor. The method ImageDescriptor#createFromURL() will handle the case where a null URL is passed to it, so we don't need to handle the possible URL exception. Images that could not be loaded are visible in the UI as small red squares. If you see a red square next to your action in the popup menu, it probably means you got the image location wrong, or the image is missing.
Step 3: Set the ImageDescriptor as the action's image in the constructor:
public MyAction() { super("My Action", image); }
That's it! Now, when you startup the Eclipse UI and find your action in a menu or toolbar, your image will appear next to it. The same approach can be used to define images within custom views and wizards. Note that since you only had to deal with ImageDescriptors in this example, no disposal was required. In this case the platform is responsible for creating, managing, and disposing any SWT Image it creates from the image descriptor you supplied.
You can think of label providers as containing a pool of images that lasts for the lifetime of a particular viewer. They allow the same SWT Images to be reused throughout the time that viewer's widget is open. Note that ILabelProviders don't provide a way to share the same Image across multiple viewers, which is fine because there are rarely more than a couple of viewers open at any given time that could share images anyway. Generally, sharing images across multiple viewers introduces a great deal of complexity for very little gain.
To give you an idea of how ILabelProviders work, it is instructive to step through the lifetime of an ILabelProvider instance.
public Image getImage(Object object);
This method is called by the viewer framework for each item in the viewer. As a silly example, let's say you have a fruit view that wants to display various fruits. Your label provider might look as follows:
public class FruitLabelProvider extends LabelProvider {
private Image appleImage = new Image(…);
private Image kiwiImage = new Image(…);
public Image getImage(Object object) {
if (object.getClass() == Apple.class)
{
return
appleImage;
}
if (object.getClass() == Kiwi.class)
{
return
kiwiImage;
}
return null;
}
public String getLabel(Object o) {
//return appropriate labels
for the various fruits
}
public void dispose() {
appleImage.dispose();
appleImage = null;
kiwiImage.dispose();
kiwiImage = null;
}
}
Some interesting things to note about this label provider:
The easiest way to use an ImageRegistry in your plug-in is to make your Plugin class a subclass of org.eclipse.ui.plugin.AbstractUIPlugin. This class creates an ImageRegistry automatically (one per plug-in instance), if requested using the getImageRegistry() method. This registry can then be populated with ImageDescriptors in your plug-in by overriding the initializeImageRegistry(ImageRegistry) method. The registry will then lazily generate and manage SWT images as they are requested. The registry knows how to dispose of itself when the UI shuts down.
It is possible to create instances of ImageRegistry manually, without using the AbstractUIPlugin's mechanism. However, this should be done with caution. An ImageRegistry, once created, cannot be closed until the whole UI shuts down. They should only be created and used if you genuinely have need for images that last for the whole lifetime of your plug-in.
The technique used for making Images available in a plug-in's API may vary between plug-ins. The Platform UI provides an interface called org.eclipse.ui.ISharedImages, accessible from the workbench. This interface supplies images and image descriptors corresponding to a set of keys defined in ISharedImages. See the JavaDoc for that class for descriptions of all the available methods. To give an example, say you want to obtain the image for a file object. You would invoke the following:
PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJ_FILE);
The workbench is also accessible from AbstractUIPlugin, which your plug-in class will typically subclass. Other plug-ins use a similar approach for sharing their images. For example, the Java Development Tooling supplies org.eclipse.jdt.ui.SharedImages in its API.
Important: Images provided by these APIs are shared, which means you must not dispose of them. Again, following the general rule, since you didn't create the image, it's not your responsibility to dispose of it.
If your plug-in defines images that may be useful to others, you may want to use a similar approach to make them available in your API. Keep in mind that the number of SWT Images shared by your plug-in should be small, since these images will have to stay around for the lifetime of your plug-in.
Java and all Java-based trademarks and logos are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States, other countries, or both.
Microsoft, Windows, Windows NT, and the Windows logo are trademarks of Microsoft Corporation in the United States, other countries, or both.