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.
Step 1 - Run the Application
Why not?!
java -cp Scaffold.jar \ com.meldcraft.application.AppManager \ com.meldcraft.imageviewer.ImageViewer
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
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
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
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.