A Plugin is responsible for any resource used, including management of Icons. SWT wants us to manually manage our images, this includes creation and disposal.
| Image |
a real image from the operating system, i.e. a limited resource |
| ImageDescriptor |
a placeholder that allows the image to be used as needed |
Icons fall under the category of Images that are "used very frequently or must be shared among several widgets for unknown lengths of time". This makes them a pain to manage on a per use basis. The solution is to create a class to manage these resources and clean up after them.
This means we are allowed to keep an ImageRegistry of icons....
Background:JFace & ImageRegistry
JFace provides an ImageRegistry yourself (it works like a Map that will dispose of the Images when an associated Display is cleaned up).
static final String ICON = "obj16/icon.gif"
ImageRegistry registry = new ImageRegistry();
ImageDescriptor desc = ImageDescriptor.createFromURL( );
registry.put( ICON, desc);
JFace also offers a global JFaceResources class that provides a shared ImageRegistry.
There is a bit of a difference between the formal party line provided by the EclipseFAQ, and what everyone is really doing.
Lets work through what is supposed to happen.
Use of AbstractUIPlugin & ImageRegistry
Chances are your plugin extends AbstractUIPlugIn - this means there is a ImageRegistry already to go.
We simply need to supply the icons using the provided callback:
public class MyPlugin extends AbstractUIPlugin {
public static final String ID = "example.package.my"; public final static String ICONS_PATH = "icons/"; public final static String PATH_ELOCALTOOL = ICONS_PATH+"elcl16/";
public final static String ADD_CO = PATH_ELOCALTOOL + "add_co.gif";
protected void initializeImageRegistry(
ImageRegistry registry) {
Bundle bundle = Platform.getBundle(ID);
IPath path = new Path( ImageConstants.ADD_CO );
URL url = Platform.find(bundle, path);
ImageDescriptor desc = ImageDescriptor.createFromURL(url);
registry.put( ImageConstants.ADD_CO, desc);
}
initializeImageRegistry is called during Plugin startup. An interesting thing to note is the use of Platform.getBundle( ID ) to locate the correct url to start searching from.
Images are then available via:
MyPlugIn.getDefault().getImageRegistry().getDescriptor( ImageConstants.ADD_CO )
ISharedImages
If your icons need to be accessed from outside of your Plug-In a convention exists - provide the the constants in a ISharedImages file.
The ISharedImage file need to be in a public directory:
public interface ISharedImages {
public final static String ICONS_PATH = "icons/"; public final static String PATH_ELOCALTOOL = ICONS_PATH+"elcl16/";
public static final String IMG_ADD_COMMAND = PATH_ELOCALTOOL + "add_co.gif";
}
External code can now make use of your Images:
ImageRegistry images = MyPlugIn.getDefault().getImageRegistry();
ImageDescriptor image = images.getDescriptor( ISharedImages.IMG_DATASTORE_OBJ );
Pragmatism
The above is a lot of work, forces a lot of complexity on your PlugIn class. And is not actually what several popular implementations are doing.
Here are some of the forces that are effecting real world implementations:
- For images in very common use an shared Image, rather than a ImageDescriptor is made available via ISharedImage. This improves performance
- The size of resource keys has started to be a problem in the broader Eclipse project. Allowing the use of shorter strings apparently is worthwhile
- The need to publish Images for reuse with in a plug-in
- Separation of concerns, taking image resource code out of the plug-in class
ImageConstants
To ease access to your icons create a ImageConstants based on the path to the resource in your icons directory.
This class should be located in an internal package.
public interface ImageConstants {
public final static String PATH_ELOCALTOOL = "elcl16/";
public final static String ADD_CO = PATH_ELOCALTOOL + "add_co.gif";
}
We have removed the leading ICONS_PATH in order to shorten key names.
ISharedImage
ISharedImage works just like before - only this time we can refer to some well know keys from ImageConstants.
public interface ISharedImages {
public static final String IMG_ADD_COMMAND = ImageConstants.ADD_CO;
}
Two out of three ISharedImage have defined methods:
- getImageDescriptor( String key ) - allows access to a shared ImageDescriptor (any created Image must be disposed)
- getImage( String key ) - allows access to a real shared Image that must not be disposed
We need something to implement ISharedImage, the PlugIn is responsible for keeping providing a class implementing this interface.
Images (a utility class )
A utility class, created on PlugIn startup takes over the management of common images.
Using Images
Usually when it comes time to actually use an Image you are working in a View.
Keeping track of your Images (in a cache) is a easy way to go:
private Map imageCache = new HashMap();
private Image getIcon(String icon) {
Image image = (Image) imageCache.get(icon);
if (image == null) {
ImageDescriptor id = Images.image( icon );
image = id.createImage();
imageCache.put(icon, image);
}
return image;
}
The only trick being that you need to clean up the cache in your dispose method:
public void dispose() {
for (Iterator i = imageCache.values().iterator(); i.hasNext(); ) {
((Image) i.next()).dispose();
}
imageCache.clear();
}
An alternative is using ImageRegistry instead of HashMap:
private ImageRegistry imageCache;
public void createPartControl(Composite parent) {
imageCache = new ImageRegistry( parent.getDisplay() );
....
}