Creating download progress bars for applets

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

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.

The ClassPreLoader constructor cannot be replaced by a bunch of "new Classxxxx;" calls because the browser will preload all the classes before executing the code. The result is a huge delay while all of the classes are downloaded by the browser. The progress bar doesn't update until after these classes download, at which time it gets updated very quickly. This defeats the purpose of using a progress bar in the first place.

The progress bar object is passed to MyAppletRunner because there are some delays in that class before the applet is finally ready to go. If this parameter is eliminated (and the total item count to the progress bar constructor adjusted), the progress bar disappears and there is a delay before the applet runs. This can be disorienting for the user.

The class list in MyApplet should not include every class in your applet. Some classes won't be needed in order for the applet to start; these should be loaded later (for example, in a word-processing applet, the spell check code shouldn't be loaded initially; it should be loaded later).

The error handling policy in class ProgressBar is to ignore any errors. This prevents typos in the class name array from halting the applet. One of the most common errors will be misspelling a class name in the classArray list. The Java compiler can't check this for us and we don't want this to cause the applet to fail, so we ignore any errors encountered while running the progress indicator.

An example

An example of the progress bar for a large applet (approximately 70 kilobytes of class files) is the Forth applet

here

.

The progress bar works on all the Java-enabled browsers with which I have tested it. Unfortunately, the Forth applet for which the progress bar is showing progress only works properly with Netscape Navigator 3.0. I have provided a Web page (see Resources), where I discuss the experiences I have had with various Java-enabled browsers.

Sample code

Here is sample code for a

very

simple applet. It is presented two different ways:

The two are identical except for the progress bar.

Conclusion

Using a progress indicator won't decrease the download time for an applet. In fact, it increases the download time slightly. However, it does make the download time more palatable for the users of the applet by letting them know how the download is progressing.

Mark Roulo has been programming using C and C++ for the past seven years. He has been using Java for the past year and is currently working on implementing an ANS-compliant Forth interpreter in Java. His main programming interests are portable, distributed, concurrent software as well as software engineering.

Learn more about this topic

Recommended
Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more