Indeterminate Progress Bars

This document describes the changes we made to implement 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 JProgressBar constructors) is determinate. You may 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. You can check whether the progress bar is indeterminate using the isIndeterminate method.

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 might use the model.

New UI Defaults

To give developers control over the progress bar's speed of animation, we've added two new UI defaults:

The "ProgressBar.cycleTime" UI default lets look and feel implementers (as well as other developers)specify how many milliseconds each animation cycle takes. For example, a cycle time of 500 means that the indeterminate-mode progress bar animation repeats twice per second. The default drawing code uses this value --along with the repaint interval, box length, and component inner area --to determine how far to move the bouncing box each time it's drawn. This value must be an even multiple of the repaint interval. For example, if the repaint interval is 100, then valid cycle times are 200, 1000, and 1200, but not 100 or 500. If the user specifies an invalid cycle time,the indeterminate progress bar code automatically increases the cycle time to a suitable value.

To set the repaint interval and cycle time defaults:

    UIManager.put("ProgressBar.repaintInterval", new Integer(250));
    UIManager.put("ProgressBar.cycleTime", new Integer(6000));

To get the repaint interval and cycle time:

    int interval = UIManager.getInt("ProgressBar.repaintInterval");
    int totalTime = UIManager.getInt("ProgressBar.cycleTime");

In the basic, JLF, Motif, and Windows implementations provided by Sun, these defaults are checked when (and only when) the progress bar is switching into indeterminate mode.

New BasicProgressBarUI API

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

Methods related to painting:

Methods for setting and getting the index of the current frame of animation:

Methods for starting and stopping custom animation threads:

The paint method that previously performed all painting for the progress bar now delegates 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.The paintIndeterminate method should paint the progress bar to match the animation state, which is specified by the getAnimationIndex method.

The getBox method allows implementations to customize bouncing box painting. For example, MetalProgressBarUI invokes getBox in its paintIndeterminate method to get the box drawn by the superclass (BasicProgressBarUI) and then to add an outline to the box. By overriding the getBox method, an implementer gets full control over the bouncing box's size and position without needing to reimplement paintIndeterminate.

If you override paintIndeterminate or getBox, you might 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 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.

As a side effect of the progress bar work, we've added a new method to SwingUtilities:

The calculateInnerArea method is useful to all classes that perform drawing. It returns the area in which the component can draw -- that is, the rectangle (in the component's coordinate system) that includes all of the component except its border area (the component's insets).

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 still works, but indeterminate mode looks 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.

Implementation Details

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, andPropertyChangeHandler, 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 specified by the ProgressBar.repaintInterval UI default, which is 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)

    public boolean isIndeterminate()


In javax.swing.plaf.basic.BasicProgressBarUI:

    protected void paintIndeterminate(Graphics g, JComponent c)

    protected void paintDeterminate(Graphics g, JComponent c)

    protected int getAnimationIndex()

    protected void setAnimationIndex(int newValue)

    protected void incrementAnimationIndex()

    protected void startAnimationTimer()

    protected void stopAnimationTimer()

    protected Rectangle getBox(Rectangle r)


In javax.swing.SwingUtilities:

    public static Rectangle calculateInnerArea(JComponent c, Rectangle r)