uDig

«  Plugin Internationalization with ResourceBundles   ::   Contents   ::   JFace Actions  »

ImageRegistry and Images

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....

JFace and 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.

The Party Line

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.

AbstractUIPlugin and 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"; //$NON-NLS-1$
       public final static String ICONS_PATH = "icons/";//$NON-NLS-1$
       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/";//$NON-NLS-1$
    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, created on PlugIn startup takes over the management of common 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) {
            //obtain the cached image corresponding to the descriptor
            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() );
        ....
}

links

«  Plugin Internationalization with ResourceBundles   ::   Contents   ::   JFace Actions  »