ProgressMonitorInputStream
In Java I/O, I wrote a Swing based GUI program that could display any file in a variety of encodings such as hex dump or ASCII. A screen shot is shown below. You can find the actual program source at Cafe au Lait or in Chapter 13 of Java I/O.
The biggest problem with this program is that after clicking the “View File” button, the user may have to wait for several seconds to several minutes before the file is displayed. Reading in a multi-megabyte file and converting it to a hex string is slow, even on a relatively fast machine with a fast hard drive. While the file is being read from the disk, the program is effectively frozen from the user’s perspective. This is not a good thing. Although there are undoubtedly still some optimizations that could be performed on the code to make it run faster, no matter how much it was optimized you could always throw a larger file at it that would make it seem frozen once again. Consequently I want to focus here not on optimizing the code, but on changing the user interface to make the program at least appear more responsive.
One general principle of user centered program design is to always give the user feedback, even if the feedback is no more significant than “Yes, I’m still running; No I haven’t frozen.” For simple operations an animated cursor like the Macintosh’s spinning beach ball may be sufficient . For longer operations, you should display a progress bar that indicates how much of the operation has been accomplished and how much remains to be done. Swing 1.1.1 lets you easily display progress bars in JOptionPane
s like the one shown below:
Java offers several ways to show a progress bar like this one. The progress bar itself is an instance of javax.swing.ProgressBar
. This is a simple component that can be added to your own containers along with buttons, labels, icons, and the like. In this figure, the progress bar is embedded in a JOptionPane
created by a javax.swing.ProgressMonitor
. The ProgressMonitor
class is useful when you want to manually set the update intervals and amounts. However, if what you’re monitoring is simply the amount of data read from an InputStream
, the javax.swing.ProgressMonitorInputStream
class is even easier to use.
ProgressMonitorInputStream
is a FilterInputStream
that you can chain to any other input stream. If the data is read very quickly in less time than the user would notice (as for a small file read from a disk) then no dialog is shown. However, if the input begins to take a noticeable amount of time (approximately half a second), then Java pops up a progress monitor. This monitor includes a cancel button. If the user presses that button, then an InterruptedIOException
is thrown. Otherwise, input continues and the progress bar is updated until the end of the stream is reached.
The primary method you need to be aware of in ProgressMonitorInputStream
is the constructor:
public ProgressMonitorInputStream(Component parent, Object message, InputStream in)
The parent
argument gives the parent component of the progress monitor, though it may be null if no such component is available. The message
argument is normally a String
specifying the message to be include in the dialog. If some other type is used, its toString()
method is invoked to convert the object to a String
. Finally, in
is the underlying InputStream
this stream is chained to. For example, this code fragment will use a progress monitor to keep the user updated about how far along it is while it’s reading the file lotsofdata.txt:
File f = new File("lotsofdata.txt");
InputStream in = new FileInputStream(fin);
ProgressMonitorInputStream pin
= new ProgressMonitorInputStream(null, f.getName(), in);
The only other new method you need to know about in this class is getProgressMonitor():
public ProgressMonitor getProgressMonitor()
This returns a reference to the actual progress monitor so that you can adjust its behavior with the methods of the ProgressMonitor
class. For instance, you can change the default time before the progress monitor is shown or the maximum and minimum values used for the monitor. You use this for the final step in setting up the progress monitor. You have to tell the ProgressMonitor
how much data is expected through the setMaximum()
methods. For instance, when reading a file you use the length()
method in the File
class to determine how many bytes you expect to be read. That would be the maximum for the progress bar.
ProgressMonitor pm = pin.getProgressMonitor();
pm.setMaximum(f.length());
Aside from getProgressMonitor()
, ProgressMonitorInputStream
just has the usual methods of any InputStream
class like read()
. You use these methods to read from the stream just as you’d use them to read from any other stream. If the process takes more than about half a second, and it looks like it will take more than two seconds, Java will pop up a ProgressMonitor
showing the user just how much is done and how much remains to be done. You can adjust these times using the methods of the ProgressMonitor
class, but the defaults for everything except the maximum value are generally reasonable.
Programs that read data from the network take even longer than programs that read from files. Here’s a complete example program that reads data from a URL given on the command line and copies it to System.out
. It uses a ProgressMonitor
to keep the user alerted as to its progress.
import java.net.*;
import java.io.*;
import javax.swing.*;
public class SourceViewer {
public static void main (String[] args) {
if (args.length > 0) {
try {
// Open the URLConnection for reading
URL u = new URL(args[0]);
URLConnection uc = u.openConnection();
InputStream in = uc.getInputStream();
// Chain a ProgressMonitorInputStream to the
// URLConnection's InputStream
ProgressMonitorInputStream pin
= new ProgressMonitorInputStream(null, u.toString(), in);
// Set the maximum value of the ProgressMonitor
ProgressMonitor pm = pin.getProgressMonitor();
pm.setMaximum(uc.getContentLength());
// Read the data
int c;
while ((c = pin.read()) != -1) {
System.out.print((char) c);
}
pin.close();
}
catch (MalformedURLException e) {
System.err.println(args[0] + " is not a parseable URL");
}
catch (InterruptedIOException e) {
// User cancelled. Do nothing.
}
catch (IOException e) {
System.err.println(e);
}
} // end if
// Since we brought up a GUI, we have to explicitly exit here
// rather than simply returning from the main() method.
System.exit(0);
} // end main
} // end SourceViewer
The screenshot of a progress monitor shown above was taken from this program. Overall, ProgressMonitorInputStream
is a simple class that’s very easy to program with and that will make your users’ experiences much more pleasant.
Further reading:
The URL
and URLConnection
classes used in the sample program are discussed in my own Java Network Programming. The original file-based program that alerted me to the need for some sort of progress bar is developed starting in Chapter 4 of Java I/O and continued throughout the book. Java I/O also discusses the general behavior of input streams and filter input streams like ProgressMonitorInputStream
in great detail.
The importance of providing users with feedback is elucidated in any good text on user interface design. However, one of the most readable is Bruce Tognazzini’s Tog on Interface.
The ProgressMonitor
and ProgressMonitorInputStream
classes themselves are discussed in Chapter 9 of John Zukowski’s Definitive Guide to Swing.
December 3rd, 2004 at 7:44 am
Object-orientation and progress bars
Giving the user feedback is certainly important, particularly for lengthy operations like file or network IO as you mention. This is also a case where good UI design conflicts with good object-oriented design. Central to good UI design is transparency: the user should have access to the state of the application, if it matters to her. The state of a lengthy operation like file or network IO matters, since you must be certain nothing is wrong (in addition it’s often nice to be able to cancel the operation). Object-oriented design, on the other hand, values abstraction and black-box thinking: don’t reveal too much about the inner workings of an object, just define the necessary interface. The conflict arises when such a black box does things the user needs/wants to know, like how much of the job is done. I’ve many times had to redesign a class to make monitoring possible. E.g. I have some code that traverses a large XOM tree, that may take several seconds. To support monitoring the progress I first had to count the number of nodes and then allow passing in a progress bar (similar to the visitor pattern) to the traversal method. In many cases, libraries cannot be modified to support this. Consider if XOM only could build from a URL, not an InputStream. It would then be impossible to support the kind of ProgressBar discussed above. Compare it with JDBC, which cannot be made to provide feedback about the progress of a database query, since the whole query processing is abstracted away into the database engine (I guess the SQL engine connection protocol is to blame). The black-box design goes against the transparent design values of GUI design.
December 4th, 2006 at 8:31 am
sir,
can i get the source code for embedding progress bar in the JScrollPane