Indeterminate Progress Bars

This document outlines a proposal for indeterminate progress bars -- GUI components that look similar to normal progress bars and, like normal progress bars, use animation to show that a time-consuming operation is occurring. Unlike normal progress bars, indeterminate progress bars do not show the degree of completeness of the operation. This document has the following sections:

New JProgressBar API

By default, every progress bar (created using one of several existing JProgressBar constructors) will be determinate. You will be able to make any JProgressBar indeterminate using the setIndeterminate method:
pb.setIndeterminate(true);

An indeterminate progress bar animates constantly. You can stop the animation and clear the progress bar by making the progress bar determinate and setting the current value to the minimum. For example:

pb.setValue(pb.getMinimum());
pb.setIndeterminate(false);

You can switch from determinate to indeterminate mode, and vice versa, at any time.

When a progress bar is indeterminate, it ignores its model (a BoundedRangeModel). However, the model should exist and contain legal data, since L&Fs that haven't been updated for indeterminate progress bars are likely to use the model.

New BasicProgressBarUI API

To help programmers implement look-and-feel code for progress bars, we plan to add the following API to the BasicProgressBarUI class.

Methods for painting:

Methods for setting and getting the index of the current frame of animation, and for getting the time interval between frames:

Methods for starting and stopping custom animation threads:

The paint method that currently performs all painting for the progress bar will delegate all its drawing to either paintDeterminate or paintIndeterminate, depending on the value of the progress bar's indeterminate property. When the progress bar is in indeterminate mode, the paint method (and thus the paintIndeterminate method) executes every repaint interval milliseconds. (You can get the repaint interval by invoking getRepaintInterval.) The paintIndeterminate method should paint the progress bar to match the animation state, which is specified by the getAnimationIndex method.

If you override paintIndeterminate, you'll probably also have to override incrementAnimationIndex so that it cycles correctly through the valid values. The first value (representing the first drawing) is 0. By convention, the second is 1, the third is 2, and so on. The last legal value is, by convention, the total number of drawings in the animation cycle, minus one. To determine the total number of drawings, you may need to take into account the repaint interval (returned by getRepaintInterval) and perhaps the component size. As the "by convention" implies, you can implement the animation index to have any meaning and values you wish, as long as zero indicates the beginning of the animation cycle.

If you don't want to use the animation thread we provide, you must override the two xxxAnimationTimer methods. You can then provide your own implementation that periodically increments the animation index and invokes repaint on the progress bar.

Converting Existing Progress Bar Implementations

Converting an existing L&F to indeterminate progress bars is relatively straightforward. If the L&F's progress bar UI class doesn't override paint (or does but also invokes super.paint), then support for indeterminate progress bars is automatic. WindowsProgressBarUI, MotifProgressBarUI, and MetalProgressBarUI are in this lucky camp.

If the L&F's progress bar UI class is a subclass of BasicProgressBarUI and overrides paint without invoking the superclass version, then determinate mode will still work, but indeterminate mode will look the same as determinate mode.

Existing drawing code should be moved out of the paint method and into the new paintDeterminate method. Code for indeterminate painting should go in the new paintIndeterminate method. If at all possible, the paint method should not be overridden unless it invokes super.paint. The reason: the BasicProgressBarUI implementation of the paint method may work with the default animation thread to enhance performance and behavior.

The Mac look and feel (both the no-longer-maintained Sun version and the Apple version) is an example of a look and feel that overrides paint without invoking the superclass version.

If you already have a thread scheme for indeterminate painting, you can continue to use that scheme by overriding startAnimationTimer and stopAnimationTimer. Or you can just delete your thread code and use our scheme.

Implementing Indeterminate Progress Bars

The BasicProgressBarUI class contains most of our implementation of indeterminate progress bars. Aside from the drawing code, most of the code is in two private inner classes: Animator, which implements the animation thread, and PropertyChangeHandler, which listens for changes to and from indeterminate mode.

The Animator implements the default animation thread, using the Swing Timer class. An Animator instance is created if necessary by the BasicProgressBarUI startAnimationTimer method, which the property handler invokes when the progress bar switches to indeterminate mode. When the progress bar is indeterminate, the Animator timer fires an action event once every repaint interval milliseconds. Animator's action event handler invokes incrementAnimationIndex, followed by repaint (which causes paintIndeterminate to run). Repaint interval is a UI default that's checked by startAnimationTimer.

The PropertyChangeHandler registers itself as a property listener on the progress bar. When it detects the "indeterminate" property changing, the handler notes the change and invokes either stopAnimationTimer or startAnimationTimer.

Javadoc for New API

In javax.swing.JProgressBar:
public void setIndeterminate(boolean newValue):
/**
 * Sets the indeterminate property of the progress bar.
 * An indeterminate progress bar continuously displays animation
 * indicating that an operation of unknown length is occurring.
 * By default, this property is false.
 *
 * for examples of using indeterminate progress bars.
 *
 * @param newValue  true if the progress bar
 *                  should change to indeterminate mode;
 *                  false if it should revert to normal
 *
 * @see #isIndeterminate
 *
 * @since 1.4
 */

public boolean isIndeterminate():
/**
 * Returns the value of the indeterminate property.
 * By default, the progress bar is determinate 
 * and this method returns false.
 *
 * @return true if the progress bar is indeterminate;
 *         otherwise, false
 * @see    #setIndeterminate
 *
 * @since 1.4
 */


In javax.swing.plaf.basic.BasicProgressBarUI:

protected void paintIndeterminate(Graphics g, JComponent c):
/**
 * All-purpose paint method that should do the right thing for almost all
 * bouncing-box indeterminate progress bars. 
 * You can customize the painting by setting a few values in
 * the defaults table.
 * Naturally, override this if you are making a barber-pole,
 * circular, or semi-circular progress bar.
 *
 * @see #paintDeterminate
 *
 * @since 1.4
 */

protected void paintDeterminate(Graphics g, JComponent c)
[contains the same code and comment as in the 1.3 paint method]:
/**
 * All-purpose paint method that should do the right thing for most
 * linear, determinate progress bars. By setting a few values in
 * the defaults
 * table, things should work just fine to paint your progress bar.
 * Naturally, override this if you are making a circular or
 * semi-circular progress bar.
 *
 * @see #paintIndeterminate
 *
 * @since 1.4
 */

protected int getAnimationIndex():
/**
 * Gets the index of the current animation frame.
 *
 * @since 1.4
 */

protected void setAnimationIndex(int newValue):
/**
 * Sets the index of the current animation frame
 * to the specified value.
 *
 * @since 1.4
 */

protected void incrementAnimationIndex():
/**
 * Increments the index of the current animation frame,
 * making sure that the value is not too large.
 * This method is invoked by the default animation thread.
 * If the value would be too large,
 * this method sets the index to 0.
 * Subclasses often need to override this method
 * to ensure that the index does not go over
 * the number of frames needed for the particular
 * progress bar instance.
 *
 * @since 1.4
 */

protected int getRepaintInterval():
/**
 * Returns the number of milliseconds between
 * successive invocations of paintIndeterminate.
 * The value is meaningful
 * only if the progress bar is in indeterminate mode.
 * The default animation thread implementation
 * updates the repaint interval
 * every time the progress bar enters indeterminate mode,
 * setting it to the value specified by
 * the "ProgressBar.repaintInterval" UI default.
 *
 * @since 1.4
 */

protected void startAnimationTimer():
/**
 * Starts the animation thread, creating and initializing
 * it if necessary.  This method is invoked when
 * the progress bar changes to
 * indeterminate mode.
 * If you implement your own animation thread,
 * you must override this method.
 *
 * @since 1.4
 */

protected void stopAnimationTimer():
/**
 * Stops the animation thread.  This method is invoked when
 * the progress bar changes from
 * indeterminate to determinate mode
 * and when this UI is uninstalled.
 * If you implement your own animation thread,
 * you must override this method.
 *
 * @since 1.4
 */



Last modified: Fri June 28 2000