Scaffold small client application frameworkwww.meldcraft.com
Slacker Guide to Java Swing Application Development
Using the Scaffold small client application framework

ImageViewer Application

A convenience feature of the Scaffold framework is context sensitive proxy object creation (as in java.lang.reflect.Proxy). This example demonstrates use of a psuedo-anonymous proxy Application Element to monitor image loading using the javax.imagio API, and display the progress efficiently in a JProgressBar.

ImageViewer4 Screen Shot

Step 1 - Run the Application

Why not?!

java -cp Scaffold.jar \
com.meldcraft.application.AppManager \
com.meldcraft.imageviewer.ImageViewer

ImageViewer1 Screen Shot

Closing the frame (e.g. by selecting the window manager close icon) exits the VM - a feature of the Scaffold framework.

Step 2 - Basic GUI

Lets start by building the UI. All we need is a single properties file.

Application Files
com/meldcraft/imageviewer/ImageViewer.properties
com/meldcraft/imageviewer/ImageViewer.properties
###################################################################################
# Image Viewer application
Application.name = Image Viewer
size = 500,500

###################################################################################
# Actions & Menus
Application.mainmenu = File Help

File.name = _File
File.items = Open | Exit

Open.name = _Open

Exit.name = E_xit
Exit.method = applicationClose

Help.name = _Help
Help.items = About

About.name = _About
About.actionClassName = com.meldcraft.application.swing.action.AboutAction

###################################################################################
# GUI components
Application.rootContent = ImagePane

ImagePane.className = javax.swing.JScrollPane
ImagePane.elementProperties = viewport.view=<element:ImageLabel>

ImageLabel.className = javax.swing.JLabel
ImageLabel.elementProperties = horizontalAlignment=CENTER

Run it:

java -cp Scaffold.jar;ImageViewer/classes \
com.meldcraft.application.AppManager \
com.meldcraft.imageviewer.ImageViewer

ImageViewer2 Screen Shot

Step 3 - Open a file

Next, lets add functionality to open a file. Add a few lines to the properties file to hook things up, and a new file for Java code to open an image.

Application Files
com/meldcraft/imageviewer/ImageLoader.java
com/meldcraft/imageviewer/ImageViewer.properties
com/meldcraft/imageviewer/ImageLoader.java
package com.meldcraft.imageviewer;

import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Iterator;

import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;

public class ImageLoader
{
    /** Load an image from File or URL using the javax.imageio API. */
    public Image loadImage(Object arg) throws IOException
    {
        Object obj = arg;

        if (obj instanceof String)
        {
            try
            {
                obj = new URL((String)obj);
            }
            catch (MalformedURLException e)
            {
                obj = new File((String)obj);
            }
        }

        if (obj instanceof File)
        {
            File file = (File)obj;
            try
            {
                obj = file.toURI().toURL();
            }
            catch (MalformedURLException e)
            {
            }
        }

        Image image = null;
        if (obj instanceof URL)
        {
            URL url = (URL)obj;
            image = readImage(url);
        }

        return image;
    }
    
    protected Image readImage(URL url) throws IOException
    {
        ImageReader reader = findImageReader(url);
        if (reader == null)
        {
            throw new IOException("Unknown image type.");
        }
        return loadImage(reader);
    }

    protected ImageReader findImageReader(URL url) throws IOException
    {
        ImageInputStream input = null;
        input = ImageIO.createImageInputStream(url.openStream());
        ImageReader reader = null;
        if (input != null)
        {
            Iterator readers = ImageIO.getImageReaders(input);
            while ((reader == null) && (readers != null) && readers.hasNext())
            {
                reader = (ImageReader) readers.next();
            }
            if (reader != null)
            {
                reader.setInput(input);
            }
        }
        return reader;
    }

    protected BufferedImage loadImage(ImageReader reader) throws IOException
    {
        BufferedImage image = null;
        try
        {
            int index = reader.getMinIndex();
            image = reader.read(index);
        }
        finally
        {
            if (reader != null)
            {
                try
                {
                    reader.dispose();
                }
                finally
                {
                    ImageInputStream input = (ImageInputStream)reader.getInput();
                    if (input != null)
                    {
                        try
                        {
                            input.close();
                        }
                        catch (IOException e)
                        {
                            // ignored
                        }
                    }
                }
            }
        }
        return image;
    }
}
com/meldcraft/imageviewer/ImageViewer.properties
###################################################################################
# Image Viewer application
Application.name = Image Viewer
size = 500,500

###################################################################################
# Actions & Menus
Application.mainmenu = File Help

File.name = _File
File.items = Open | Exit

Open.name = _Open
Open.target = ImageChooser
Open.method = showOpenDialog

Exit.name = E_xit
Exit.method = applicationClose

Help.name = _Help
Help.items = About

About.name = _About
About.actionClassName = com.meldcraft.application.swing.action.AboutAction

###################################################################################
# GUI components
Application.rootContent = ImagePane

ImagePane.className = javax.swing.JScrollPane
ImagePane.elementProperties = viewport.view=<element:ImageLabel>

ImageLabel.className = javax.swing.JLabel
ImageLabel.elementProperties = horizontalAlignment=CENTER

ImageChooser.baseResource = com.meldcraft.application.guis.SSFileChooser
ImageChooser.fileFilters = ImageChooser.AllFileFilter, ImageFilter

ImageFilter.className = com.meldcraft.application.guis.filechooser.BasicFileFilter
ImageFilter.extensions = .jpg,.jpeg,.JPG,.JPEG,.gif,.GIF,.png,.PNG
ImageFilter.description = ImageFiles (*.jpg, *.gif, *.png)

###################################################################################
# Business objects
ImageLoader.className = com.meldcraft.imageviewer.ImageLoader

###################################################################################
# Context listeners
Application.contextListeners = Open.invokeReturn=FileOpenInvoker,\
                   FileOpenInvoker.invokeStart=OpeningInvoker,ClearIconInvoker,\
                   FileOpenInvoker.invokeReturn=OpenIconInvoker,ClearErrorInvoker,\
                   FileOpenInvoker.invokeError=OpenErrorInvoker,ClearIconInvoker,\
                   FileOpenInvoker.invokeEnd=TitleInvoker\
							   
FileOpenInvoker.target = ImageLoader
FileOpenInvoker.method = loadImage
FileOpenInvoker.method.arg = <target>
FileOpenInvoker.invokeTargetThread = newThread
FileOpenInvoker.busyCursor = true
FileOpenInvoker.invokeCondition = <target>

OpenIconInvoker.target = ImageLabel
OpenIconInvoker.method = setIcon
OpenIconInvoker.method.arg = <target>

ClearIconInvoker.target = ImageLabel
ClearIconInvoker.method = setIcon
ClearIconInvoker.method.arg = <null>

OpenErrorInvoker.target = ImageLabel
OpenErrorInvoker.method = setText
OpenErrorInvoker.method.arg = Error loading image.

ClearErrorInvoker.target = ImageLabel
ClearErrorInvoker.method = setText
ClearErrorInvoker.method.arg = <null>

TitleInvoker.target = UIRoot
TitleInvoker.method = setTitle
TitleInvoker.method.arg = <propertyRef:Application.name> - <target>.context

OpeningInvoker.target = ImageLabel
OpeningInvoker.method = setText
OpeningInvoker.method.arg = Opening <target>.context ...

Compile the application and run it:

java -cp Scaffold.jar;ScaffoldGUIS.jar;ImageViewer/classes \
com.meldcraft.application.AppManager \
com.meldcraft.imageviewer.ImageViewer

ImageViewer3 File Chooser Screen Shot

ImageViewer3 Screen Shot

Selecting File | Open displays a file chooser, and the selected image is opened asynchronously with a busy cursor over the ImageViewer.

How does this work? Watching the ApplicationContext messages in the console log gives some good hints:

2007/08/28 22:11:18 PDT INFO: SwingApplicationContext [Image Viewer] contextChanged[AWT-EventQueue-0]: key=Open.action value=null
2007/08/28 22:11:18 PDT INFO: SwingApplicationContext [Image Viewer] contextChanged[AWT-EventQueue-0]: key=Open.invokeStart value=6,false,null

The Open Application Element actually defines two objects - an Action and an Invoker. Pressing the Open menu button (or pressing the O accelerator) activates the Action (via the actionPerformed() method), which sends an Open.action message on the ApplicationContext. The Open Invoker listens for that message, and in turn invokes the showOpenDialog() method on the ImageChooser Application Element, which causes a file chooser dialog to display.

2007/08/28 22:11:22 PDT INFO: SwingApplicationContext [Image Viewer] contextChanged[AWT-EventQueue-0]: key=Open.invokeReturn value=C:\Documents and Settings\rob\My Documents\TestFile.jpg
...
2007/08/28 22:11:22 PDT INFO: SwingApplicationContext [Image Viewer] contextChanged[ReflectInvoker-FileOpenInvoker-Thread]: key=FileOpenInvoker.invokeStart value=7,false,C:\Documents and Settings\rob\My Documents\TestFile.jpg
2007/08/28 22:11:22 PDT INFO: SwingApplicationContextChangedInvoker [Image Viewer] contextChanged[ReflectInvoker-FileOpenInvoker-Thread]: OpeningInvoker using EDT thread override for javax.swing.JLabel[,0,0,2048x1536,alignmentX=0.0,alignmentY=0.0,border=,flags=8388608,maximumSize=,minimumSize=,preferredSize=,defaultIcon=javax.swing.ImageIcon@1980630,disabledIcon=,horizontalAlignment=CENTER,horizontalTextPosition=TRAILING,iconTextGap=4,labelFor=,text=,verticalAlignment=CENTER,verticalTextPosition=CENTER]
2007/08/28 22:11:22 PDT INFO: SwingApplicationContext [Image Viewer] contextChanged[AWT-EventQueue-0]: key=OpeningInvoker.invokeStart value=8,false,7,false,C:\Documents and Settings\rob\My Documents\TestFile.jpg
...
2007/08/28 22:11:22 PDT INFO: SwingApplicationContext [Image Viewer] contextChanged[ReflectInvoker-FileOpenInvoker-Thread]: key=FileOpenInvoker.invokeReturn value=BufferedImage@15d616e: type = 5 ColorModel: #pixelBits = 24 numComponents = 3 color space = java.awt.color.ICC_ColorSpace@1fa39bb transparency = 1 has alpha = false isAlphaPre = false ByteInterleavedRaster: width = 2048 height = 1536 #numDataElements 3 dataOff[0] = 2
2007/08/28 22:11:22 PDT INFO: SwingApplicationContextChangedInvoker [Image Viewer] contextChanged[ReflectInvoker-FileOpenInvoker-Thread]: OpenIconInvoker using EDT thread override for javax.swing.JLabel[,0,0,2442x1536,alignmentX=0.0,alignmentY=0.0,border=,flags=8388608,maximumSize=,minimumSize=,preferredSize=,defaultIcon=javax.swing.ImageIcon@1980630,disabledIcon=,horizontalAlignment=CENTER,horizontalTextPosition=TRAILING,iconTextGap=4,labelFor=,text=Opening C:\Documents and Settings\rob\My Documents\TestFile.jpg,verticalAlignment=CENTER,verticalTextPosition=CENTER]
2007/08/28 22:11:22 PDT INFO: SwingApplicationContext [Image Viewer] contextChanged[AWT-EventQueue-0]: key=OpenIconInvoker.invokeStart value=9,false,BufferedImage@15d616e: type = 5 ColorModel: #pixelBits = 24 numComponents = 3 color space = java.awt.color.ICC_ColorSpace@1fa39bb transparency = 1 has alpha = false isAlphaPre = false ByteInterleavedRaster: width = 2048 height = 1536 #numDataElements 3 dataOff[0] = 2
...
2007/08/28 22:11:22 PDT INFO: SwingApplicationContextChangedInvoker [Image Viewer] contextChanged[ReflectInvoker-FileOpenInvoker-Thread]: ClearErrorInvoker using EDT thread override for javax.swing.JLabel[,0,0,2442x1536,alignmentX=0.0,alignmentY=0.0,border=,flags=8388608,maximumSize=,minimumSize=,preferredSize=,defaultIcon=javax.swing.ImageIcon@b23d12,disabledIcon=,horizontalAlignment=CENTER,horizontalTextPosition=TRAILING,iconTextGap=4,labelFor=,text=Opening C:\Documents and Settings\rob\My Documents\TestFile.jpg,verticalAlignment=CENTER,verticalTextPosition=CENTER]
2007/08/28 22:11:22 PDT INFO: SwingApplicationContext [Image Viewer] contextChanged[AWT-EventQueue-0]: key=ClearErrorInvoker.invokeStart value=10,false,BufferedImage@15d616e: type = 5 ColorModel: #pixelBits = 24 numComponents = 3 color space = java.awt.color.ICC_ColorSpace@1fa39bb transparency = 1 has alpha = false isAlphaPre = false ByteInterleavedRaster: width = 2048 height = 1536 #numDataElements 3 dataOff[0] = 2
...
2007/08/28 22:11:22 PDT INFO: SwingApplicationContext [Image Viewer] contextChanged[ReflectInvoker-FileOpenInvoker-Thread]: key=FileOpenInvoker.invokeEnd value=7,true,C:\Documents and Settings\rob\My Documents\TestFile.jpg
2007/08/28 22:11:22 PDT INFO: SwingApplicationContextChangedInvoker [Image Viewer] contextChanged[ReflectInvoker-FileOpenInvoker-Thread]: TitleInvoker using EDT thread override for javax.swing.JFrame[frame0,550,350,500x500,invalid,layout=java.awt.BorderLayout,title=Image Viewer - C:\Documents and Settings\rob\My Documents\TestFile.jpg,resizable,normal,defaultCloseOperation=DO_NOTHING_ON_CLOSE,rootPane=javax.swing.JRootPane[,4,23,492x473,invalid,layout=javax.swing.JRootPane$RootLayout,alignmentX=0.0,alignmentY=0.0,border=,flags=16785867,maximumSize=,minimumSize=,preferredSize=],rootPaneCheckingEnabled=true]
2007/08/28 22:11:22 PDT INFO: SwingApplicationContext [Image Viewer] contextChanged[AWT-EventQueue-0]: key=TitleInvoker.invokeStart value=11,false,7,true,C:\Documents and Settings\rob\My Documents\TestFile.jpg
...

When a file is opened via the file chooser, the message Open.invokeReturn is sent on the ApplicationContext with a value returned by the ImageChooser showOpenDialog() method (i.e. the selected File). This triggers the FileOpenInvoker, which spawns a new thread, and invokes the method loadImage() on the ImageLoader Application Element. The FileOpenInvoker also places a busy cursor over the application frame and prevents mouse and keyboard interaction until the invocation completes.

The FileOpenInvoker.invokeStart message on the ApplicationContext causes the OpeningInvoker to invoke the setText() method on the ImageLabel. Similarly, the ClearIconInvoker invokes the setIcon() method on the ImageLabel. Note that, as indicated in the console log, the Scaffold Invoker detects that the invocation target (i.e. the ImageLabel) is a Swing component, and automatically makes the invocation in the AWT Event Dispatch thread, even though the ApplicationContext message occurred on the ReflectInvoker-FileOpenInvoker-Thread spawned by the FileOpenInvoker! This is the default behavior for Invokers, and can be overriden using the invokeTargetThread property.

When the loadImage() method completes, the FileOpenInvoker.invokeReturn ApplicationContext message causes the OpenIconInvoker and ClearErrorInvoker to set the icon and clear the text on the ImageLabel. Again, these methods are invoked automatically in the AWT Event Dispatch thread.

Note that the ImageLoader loadImage() method returns an Image, whereas the ImageLabel setIcon() method takes an Icon. Another part of Scaffold magic is implicit type conversion. The Scaffold ReflectUtil class has a configurable set of type converters; many common such conversions are available out of the box. In this case, a javax.swing.ImageIcon is wrapped around the image by a stock converter.

Step 4 - Monitor Progress

Next, lets add progress monitoring. A third file, StatusBar.properties, is added to define a progress bar with text label. In addition, the ImageLoader POJO is enhanced with IIOReadProgressListener progress notification, and the ImageViewer.properties gets a few lines to create an IIOReadProgressListener using the Scaffold <proxy> convenience (as in dynamic proxy using java.lang.reflect.Proxy), and to update the progress bar during image loading.

Application Files
com/meldcraft/imageviewer/ImageLoader.java
com/meldcraft/imageviewer/ImageViewer.properties
com/meldcraft/imageviewer/StatusBar.properties
com/meldcraft/imageviewer/ImageLoader.java
package com.meldcraft.imageviewer;

import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Iterator;

import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.event.IIOReadProgressListener;
import javax.imageio.stream.ImageInputStream;

public class ImageLoader
{

    private ArrayList   mProgressListeners = new ArrayList();

    public void addIIOReadProgressListener(IIOReadProgressListener listener)
    {
        if (!mProgressListeners.contains(listener))
        {
            mProgressListeners.add(listener);
        }
    }

    public boolean removeIIOReadProgressListener(IIOReadProgressListener listener)
    {
        return mProgressListeners.remove(listener);
    }

    /** Load an image from File or URL using the javax.imageio API. */
    public Image loadImage(Object arg) throws IOException
    {
        Object obj = arg;

        if (obj instanceof String)
        {
            try
            {
                obj = new URL((String)obj);
            }
            catch (MalformedURLException e)
            {
                obj = new File((String)obj);
            }
        }

        if (obj instanceof File)
        {
            File file = (File)obj;
            try
            {
                obj = file.toURI().toURL();
            }
            catch (MalformedURLException e)
            {
            }
        }

        Image image = null;
        if (obj instanceof URL)
        {
            URL url = (URL)obj;
            image = readImage(url);
        }

        return image;
    }
    
    protected Image readImage(URL url) throws IOException
    {
        ImageReader reader = findImageReader(url);
        if (reader == null)
        {
            throw new IOException("Unknown image type.");
        }
        Object[] listeners = mProgressListeners.toArray();
        if ((listeners != null) && (listeners.length > 0))
        {
            for (int i=0 ; i < listeners.length ; i++)
            {
                reader.addIIOReadProgressListener((IIOReadProgressListener)listeners[i]);
            }
        }
        return loadImage(reader);
    }

    protected ImageReader findImageReader(URL url) throws IOException
    {
        ImageInputStream input = null;
        input = ImageIO.createImageInputStream(url.openStream());
        ImageReader reader = null;
        if (input != null)
        {
            Iterator readers = ImageIO.getImageReaders(input);
            while ((reader == null) && (readers != null) && readers.hasNext())
            {
                reader = (ImageReader) readers.next();
            }
            if (reader != null)
            {
                reader.setInput(input);
            }
        }
        return reader;
    }

    protected BufferedImage loadImage(ImageReader reader) throws IOException
    {
        BufferedImage image = null;
        try
        {
            int index = reader.getMinIndex();
            image = reader.read(index);
        }
        finally
        {
            if (reader != null)
            {
                try
                {
                    reader.dispose();
                }
                finally
                {
                    try
                    {
                        reader.removeAllIIOReadProgressListeners();
                    }
                    finally
                    {
                        ImageInputStream input = (ImageInputStream)reader.getInput();
                        if (input != null)
                        {
                            try
                            {
                                input.close();
                            }
                            catch (IOException e)
                            {
                                // ignored
                            }
                        }
                    }
                }
            }
        }
        return image;
    }
}
com/meldcraft/imageviewer/ImageViewer.properties
###################################################################################
# Image Viewer application
Application.name = Image Viewer
size = 500,500

###################################################################################
# Actions & Menus
Application.mainmenu = File Help

File.name = _File
File.items = Open | Exit

Open.name = _Open
Open.target = ImageChooser
Open.method = showOpenDialog

Exit.name = E_xit
Exit.method = applicationClose

Help.name = _Help
Help.items = About

About.name = _About
About.actionClassName = com.meldcraft.application.swing.action.AboutAction

###################################################################################
# GUI components
Application.rootContent = ContentPane

ContentPane.baseResource = com.meldcraft.application.guis.SSFactory
ContentPane.type = panel
ContentPane.elements = ImagePane,,,StatusBar

StatusBar.baseResource = com.meldcraft.imageviewer.StatusBar

ImagePane.baseResource = com.meldcraft.application.guis.SSFactory
ImagePane.type = scrollPane
ImagePane.viewportView = ImageLabel

ImageLabel.baseResource = com.meldcraft.application.guis.SSFactory
ImageLabel.type = label
ImageLabel.elementProperties = horizontalAlignment=CENTER

ImageChooser.baseResource = com.meldcraft.application.guis.SSFileChooser
ImageChooser.fileFilters = ImageChooser.AllFileFilter, ImageFilter

ImageFilter.className = com.meldcraft.application.guis.filechooser.BasicFileFilter
ImageFilter.extensions = .jpg,.jpeg,.JPG,.JPEG,.gif,.GIF,.png,.PNG
ImageFilter.description = ImageFiles (*.jpg, *.gif, *.png)

###################################################################################
# Business objects
ImageLoader.className = com.meldcraft.imageviewer.ImageLoader
ImageLoader.elementProperty.addIIOReadProgressListener=<proxy>

###################################################################################
# Context listeners
Application.contextListeners = Open.invokeReturn=FileOpenInvoker,\
          FileOpenInvoker.invokeStart=OpeningInvoker,\
          FileOpenInvoker.invokeReturn=OpenIconInvoker,ClearErrorInvoker,\
          FileOpenInvoker.invokeError=OpenErrorInvoker,ClearIconInvoker,\
          FileOpenInvoker.invokeEnd=TitleInvoker,ProgressCompleteInvoker,\
          ImageLoader.addIIOReadProgressListenerProxy.imageProgress=ProgressInvoker

ProgressInvoker.target = StatusBar.ProgressBar
ProgressInvoker.method = setValue
ProgressInvoker.method.arg = <target>[1].intValue
ProgressInvoker.meterInterval = 500

ProgressCompleteInvoker.target = StatusBar.ProgressBar
ProgressCompleteInvoker.method = setValue
ProgressCompleteInvoker.method.arg = 0

FileOpenInvoker.target = ImageLoader
FileOpenInvoker.method = loadImage
FileOpenInvoker.method.arg = <target>
FileOpenInvoker.invokeTargetThread = newThread
FileOpenInvoker.busyCursor = true
FileOpenInvoker.invokeCondition = <target>

OpenIconInvoker.target = ImageLabel
OpenIconInvoker.method = setIcon
OpenIconInvoker.method.arg = <target>

ClearIconInvoker.target = ImageLabel
ClearIconInvoker.method = setIcon
ClearIconInvoker.method.arg = <null>

OpenErrorInvoker.target = StatusBar.Label
OpenErrorInvoker.method = setText
OpenErrorInvoker.method.arg = Error loading image.

ClearErrorInvoker.target = StatusBar.Label
ClearErrorInvoker.method = setText
ClearErrorInvoker.method.arg = <null>

TitleInvoker.target = UIRoot
TitleInvoker.method = setTitle
TitleInvoker.method.arg = <propertyRef:Application.name> - <target>.context

OpeningInvoker.target = StatusBar.Label
OpeningInvoker.method = setText
OpeningInvoker.method.arg = Opening <target>.context ...
com/meldcraft/imageviewer/StatusBar.properties
baseResource = com.meldcraft.application.guis.SSFactory
type = panel
elements = <elementId>.Label,,<elementId>.ProgressBar
border = 5,2,5,2

ProgressBar.className = javax.swing.JProgressBar

Label.className = javax.swing.JLabel

Again, compile and run the program:

java -cp Scaffold.jar;ScaffoldGUIS.jar;ImageViewer/classes \
com.meldcraft.application.AppManager \
com.meldcraft.imageviewer.ImageViewer

ImageViewer4 Progress Screen Shot

ImageViewer4 Screen Shot

Now, a progress bar is updated as the image loads, and status messages show up in the status bar, instead of in the image area. Below is a summary of the progress related ApplicationContext messages during the ImageLoader loadImage() call:

2007/08/28 23:07:42 PDT INFO: SwingApplicationContext [Image Viewer] contextChanged[ReflectInvoker-FileOpenInvoker-Thread]: key=ImageLoader.addIIOReadProgressListenerProxy.imageStarted value=[Ljava.lang.Object;@16ea269
2007/08/28 23:07:42 PDT INFO: SwingApplicationContext [Image Viewer] contextChanged[ReflectInvoker-FileOpenInvoker-Thread]: key=ImageLoader.addIIOReadProgressListenerProxy.imageProgress value=[Ljava.lang.Object;@68cb6b
2007/08/28 23:07:42 PDT INFO: SwingApplicationContextChangedInvoker [Image Viewer] contextChanged[ReflectInvoker-FileOpenInvoker-Thread]: ProgressInvoker using EDT thread override for javax.swing.JProgressBar[,340,5,150x16,alignmentX=0.0,alignmentY=0.0,border=javax.swing.plaf.BorderUIResource$CompoundBorderUIResource@30c221,flags=8,maximumSize=,minimumSize=,preferredSize=,orientation=HORIZONTAL,paintBorder=true,paintString=false,progressString=,indeterminateString=false]
2007/08/28 23:07:42 PDT INFO: SwingApplicationContext [Image Viewer] contextChanged[AWT-EventQueue-0]: key=ProgressInvoker.invokeStart value=3,false,[Ljava.lang.Object;@68cb6b
...
2007/08/28 23:07:43 PDT INFO: SwingApplicationContext [Image Viewer] contextChanged[ReflectInvoker-FileOpenInvoker-Thread]: key=ImageLoader.addIIOReadProgressListenerProxy.imageProgress value=[Ljava.lang.Object;@28305d
2007/08/28 23:07:43 PDT INFO: SwingApplicationContextChangedInvoker [Image Viewer] contextChanged[ReflectInvoker-FileOpenInvoker-Thread]: ProgressInvoker using EDT thread override for javax.swing.JProgressBar[,340,5,150x16,alignmentX=0.0,alignmentY=0.0,border=javax.swing.plaf.BorderUIResource$CompoundBorderUIResource@30c221,flags=8,maximumSize=,minimumSize=,preferredSize=,orientation=HORIZONTAL,paintBorder=true,paintString=false,progressString=,indeterminateString=false]
2007/08/28 23:07:43 PDT INFO: SwingApplicationContext [Image Viewer] contextChanged[AWT-EventQueue-0]: key=ProgressInvoker.invokeStart value=4,false,[Ljava.lang.Object;@28305d
...
2007/08/28 23:07:43 PDT INFO: SwingApplicationContext [Image Viewer] contextChanged[ReflectInvoker-FileOpenInvoker-Thread]: key=ImageLoader.addIIOReadProgressListenerProxy.imageComplete value=com.sun.imageio.plugins.jpeg.JPEGImageReader@e99681
...
2007/08/28 23:07:43 PDT INFO: SwingApplicationContext [Image Viewer] contextChanged[ReflectInvoker-FileOpenInvoker-Thread]: key=FileOpenInvoker.invokeReturn value=BufferedImage@1980630: type = 5 ColorModel: #pixelBits = 24 numComponents = 3 color space = java.awt.color.ICC_ColorSpace@1be4777 transparency = 1 has alpha = false isAlphaPre = false ByteInterleavedRaster: width = 2048 height = 1536 #numDataElements 3 dataOff[0] = 2

Again, the progress bar is correctly updated automatically on the AWT Event Dispatch thread.

Note that the progress bar is updated at most every 500 milliseconds, per the ProgressInvoker meterInterval parameter, thereby preventing excessive GUI updates regardless of how frequently image progress notification occurs. Metering is built in to the default Scaffold invoker class, and can thus be used for any general Invoker.

© 2007 Meldcraft Corporation