Newsletter sign-up
View all newsletters

Enterprise Java Newsletter
Stay up to date on the latest tutorials and Java community news posted on JavaWorld

Sponsored Links

Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs

Creating download progress bars for applets

Here's a way to keep users happy while they wait for your applet to download

  • Print
  • Feedback
Here's the problem: You have written and posted to the Web a non-trivial applet, but the applet downloads slowly. Users don't know how long the applet will take to download, and they are getting bored waiting for it. You can try to post the expected download time on the Web page containing the applet, but these will vary wildly depending on the user's connection speed, on local Internet congestion, and on whether parts of the applet are cached (locally or via a proxy server). With estimated download times varying by as much as a factor of ten, providing an accurate estimated download time is difficult.

The Web browser is no help either in providing an estimate of the length of download time required. Browsers know how much progress has been made on a particular file but not how many files need to be downloaded before your applet can run.

A solution is to give the users of your applet a progress indicator that shows how much of the applet has been loaded and how much is left to go. This article shows how to implement and use such an indicator.

Design goals

There are three design goals for the download progress indicator:
  • Minimize additional launch overhead
    Adding a progress bar increases the overall launch time for the applet because two additional classes must be downloaded. While users benefit by being able to see the applet's download progress, the additional overhead should be kept to a minimum.

  • Minimize latency
    The progress bar must appear as quickly as possible. A progress bar appearing one full minute after the applet begins downloading defeats the entire purpose of having a progress bar. The applet must be designed to minimize the latency between the time the applet launches and the time the progress bar appears.

  • Reasonable granularity
    The progress bar should show progress in chunks as small as is reasonable. In practice this means the progress bar should be updated after each class is downloaded.



Implementing the download progress bar

Two classes implement the download progress bar: ProgressBar and ClassPreLoader. In addition, the class that extends Applet must be redesigned to avoid latency problems.

Class ProgressBar
The ProgressBar class implements a functionally minimal progress indicator. It is created with a title and a count of total items. It then updates itself each time its updateProgress method is called. The progress bar disposes of itself when updateProgress has been called once for each item.

This class could be made to have much more functionality. However, doing so would increase the launch time for the applet (as the class would be larger) and would also increase the latency. The code for class ProgressBar looks like this:

    import java.awt.*;

public class ProgressBar extends Frame { private int Count; private int Max; private static final int FrameBottom = 24;
public ProgressBar (String Title, int TotalItems) { super(Title);
Count = 0; Max = TotalItems;
// Allowing this to be resized causes more trouble than it is worth // and the goal is for this to load and launch quickly! setResizable(false);
setLayout(null); addNotify(); resize (insets().left + insets().right + 379, insets().top + insets().bottom + FrameBottom); }
public synchronized void show() { move(50, 50); super.show(); }
// Update the count and then update the progress indicator. If we have // updated the progress indicator once for each item, dispose of the // progress indicator. public void updateProgress () { ++Count;
if (Count == Max) dispose(); else repaint(); }
// Paint the progress indicator. public void paint (Graphics g) { Dimension FrameDimension = size(); double PercentComplete = (double)Count * 100.0 /(double)Max; int BarPixelWidth = (FrameDimension.width * Count)/ Max;
// Fill the bar the appropriate percent full. g.setColor (Color.red); g.fillRect (0, 0, BarPixelWidth, FrameDimension.height);
// Build a string showing the % completed as a numeric value. String s = String.valueOf((int)PercentComplete) + " %";
// Set the color of the text. If we don't, it appears in the same color // as the rectangle making the text effectively invisible. g.setColor (Color.black);
// Calculate the width of the string in pixels. We use this to center // the string in the progress bar window. FontMetrics fm = g.getFontMetrics(g.getFont()); int StringPixelWidth = fm.stringWidth(s);
g.drawString(s, (FrameDimension.width - StringPixelWidth)/2, FrameBottom); }
public boolean handleEvent(Event event) { if (event.id == Event.WINDOW_DESTROY) { dispose(); return true; }
return super.handleEvent(event); } }


Class ClassPreLoader
The ClassPreLoader class loads Java classes while updating a status bar. ClassPreLoader has no methods other than the constructor. There is only one constructor, which takes two arguments -- a ProgressBar to update and an array of strings. Each string in the array is the name of a class that needs to be loaded before the applet can run. The code for class ClassPreLoader looks like this:

    class ClassPreLoader
    {
        public ClassPreLoader(ProgressBar theProgressBar, String classArray[])
        {
            int i;
            for (i = 0; i < classArray.length; ++ i)
            {
                try
                {
                    Class c = Class.forName(classArray[i]);

theProgressBar.updateProgress(); } catch (Exception e) { // Do nothing. } } } }


Class MyApplet
The class that extends Applet needs to be redesigned slightly. Netscape Navigator 3.0 "helpfully" preloads class files it thinks it will need. Preloading these classes takes precedence over running code that has already been downloaded. If the applet class needs other classes, those classes are downloaded before the applet starts to run. This can add significantly to the time it takes for the progress bar to display once an applet has started downloading.

To get around this time problem, the class that extends Applet must reference a minimum number of non-built-in Java classes. The example class below, MyApplet, runs as follows:

  1. The MyApplet class downloads itself.
  2. Download, but not run, the ProgressBar class.
  3. Download, but not run, the ClassPreLoader class.
  4. Download, but not run, the MyAppletRunner class.
  5. Begin running the init method in class MyApplet that creates a progress bar, displays it, and then creates a new object of type ClassPreLoader which then loads the specified classes. Finally, the class MyAppletRunner gets created and actually runs the applet.
Notice that four classes are downloaded before anything happens. If the body of MyAppletRunner is in line with the init method of MyApplet, then any classes needed by MyAppletRunner will also be loaded before the progress bar is displayed. The trick here is to minimize the number of classes the browser loads before displaying the progress bar. The code for class MyApplet looks something like this:

    import java.awt.*;
    import java.applet.*;

public class MyApplet extends Applet { public void init() { // The classes needed by your applet. They have the same // name as the *.class files (but without the .class). // Don't bother including ClassPreLoader or ProgressBar. String classArray[] { "FirstClassName", "SecondClassName", "ThirdClassName", // Possible lots more class names in here. "LastClassName" };
super.init(); setLayout(null); resize(6,6);
// Make and display the progress bar. The '+2' // is because there are two time consuming steps // in the constructor of MyAppletRunner, and we // update the progress bar inside there, too. ProgressBar theProgressBar = new ProgressBar("My nifty applet downloading...", classArray.length + 2); theProgressBar.show();
// Preload all the necessary classes, updating the progress bar as // they are loaded. ClassPreLoader myPreLoader = new ClassPreLoader(theProgressBar, classArray);
// The constructor for class MyAppletRunner implements the code that // is traditionally in the init() method. MyAppletRunner myApplet = new MyAppletRunner(theProgressBar); }
public boolean handleEvent(Event event) { return super.handleEvent(event); } }


Some comments on the implementation

You can further reduce download time by moving the functionality of the ClassPreLoader into a method of the MyApplet class. This eliminates one class download with all the associated connection overhead.

  • Print
  • Feedback

Resources
  • The progress bar works on all the Java-enabled browsers on which the author has tested it. The example applet for which it is showing download progress only works properly on Netscape Navigator 3.0 at this time. The following Web site provides information on the experience the author had with other browsershttp://www.mistybeach.com/Forth/BrowserExperience.html