Sometimes a task running within a program might take a while to complete. A user-friendly program provides some indication to the user about how long the task might take and how much work has already been done. Swing provides three classes to help you create GUIs that monitor and display the progress of a long-running task:
JProgressBar
ProgressMonitor
ProgressMonitorInputStream
getProgressMonitor
and configure it as described in How
to Use Progress Monitors. Here's a picture of a small demo application that uses a progress bar to measure the progress of a task that runs in its own thread:
Below is the code from
Try this:
- Compile and run the application. The main source file is
ProgressBarDemo.java
. You will also needLongTask.java
andSwingWorker.java
.
See Getting Started with Swing if you need help compiling or running this application.- Push the Start button. Watch the progress bar as the task makes progress. The task displays its output in the text area at the bottom of the window.
ProgressBarDemo.java
that creates
and sets up the progress bar:
The constructor that creates the progress bar sets the progress bar's minimum and maximum values. You can also set these values with//Where member variables are declared: JProgressBar progressBar; //...in the constructor for the demo's frame: progressBar = new JProgressBar(0, task.getLengthOfTask()); progressBar.setValue(0); progressBar.setStringPainted(true);
setMinimum
and setMaximum
. The minimum and maximum
values used in this program are 0 and the length of the task, which is typical
of many programs and tasks. However, a progress bar's minimum and maximum
values can be any value, even negative. The code snippet also sets the
progress bar's current value to 0.
The call to setStringPainted
causes the progress bar to
display, within its bounds, a textual indication of the percentage of the task
that has completed. By default, the progress bar displays the value returned
by its getPercentComplete
method formatted as a percent, such as
33%. Alternatively, you can replace the default with a
different string by calling setString
. For example,
if (/*...half way done...*/) progressBar.setString("Half way there!");
You start this example's task by clicking the Start
button. Once the task has begun, a timer (an instance of the Timer
class) fires an action event every second. Here's the
ActionPerformed
method of the timer's action listener:
The bold line of code gets the amount of work completed by the task and updates the progress bar with that value. So this example's progress bar measures the progress made by the task each second, not the elapsed time. The rest of the code appends a message to the output log (a text area namedpublic void actionPerformed(ActionEvent evt) { progressBar.setValue(task.getCurrent()); taskOutput.append(task.getMessage() + newline); taskOutput.setCaretPosition(taskOutput.getDocument().getLength()); if (task.done()) { Toolkit.getDefaultToolkit().beep(); timer.stop(); startButton.setEnabled(true); progressBar.setValue(progressBar.getMinimum()); } }
taskOutput
) and, if the task is done, turns the
timer off and resets the other controls.
As mentioned, the long-running task in this program runs in a separate
thread. Generally, it's a good idea to isolate a potentially long-running task
in its own thread so that the task doesn't block the rest of the program. The
long-running task is implemented by LongTask.java
, which uses a
SwingWorker
to ensure that the thread runs safely. See Using
the SwingWorker Class in Threads
and Swing for information about the SwingWorker
class.
Now let's rewrite the previous example to use a progress monitor
instead of a progress bar. Here's a picture of the new demo program, ProgressMonitorDemo.java
:
A progress monitor cannot be used again, so a new one must be created each time a new task is started. This program creates a progress monitor each time the user starts a new task with the Start button.
Try this:
- Compile and run the application. The main source file is
ProgressMonitorDemo.java
. You will also needLongTask.java
andSwingWorker.java
.
See Getting Started with Swing if you need help compiling or running this application.- Push the Start button. After a certain amount of time, the program displays a progress dialog.
- Click the OK button. Note that the task continues even though the dialog is gone.
- Start another task. After the dialog pops up, click the Cancel button. The dialog goes away and the task stops.
Here's the statement that creates the progress monitor:
This code usesprogressMonitor = new ProgressMonitor(ProgressMonitorDemo.this, "Running a Long Task", "", 0, task.getLengthOfTask());
ProgressMonitor
's only
constructor to create the monitor and initialize several arguments:
null
for this argument, the note is
omitted from the dialog. The example updates the note each time the timer
fires an action event. It updates the monitor's current value at the same
time:
progressMonitor.setNote(task.getMessage()); progressMonitor.setProgress(task.getCurrent());
The first line sets the current position of the progress bar on the dialog. The second tells the progress monitor to wait two seconds before deciding whether to bring up a dialog. If, after two seconds, the progress monitor's progress is less than its maximum, the monitor will bring up the dialog.progressMonitor.setProgress(0); progressMonitor.setMillisToDecideToPopup(2 * ONE_SECOND);
By the simple fact that this example uses a progress monitor, it adds a feature that wasn't present in the version of the program that uses a progress bar. The user can cancel the task by clicking the Cancel button on the dialog. Here's the code in the example that checks to see if the user canceled the task or if the task exited normally:
Note that the progress monitor doesn't itself cancel the task. It provides the GUI and API to allow the program to do so easily.if (progressMonitor.isCanceled() || task.done()) { progressMonitor.close(); task.stop(); Toolkit.getDefaultToolkit().beep(); timer.stop(); startButton.setEnabled(true); }
Use a progress bar if:
Use a progress monitor if:
isCanceled
method to find out if the user pressed the
Cancel button.
setNote
method so that the
task can provide further information about what it's doing. For example, an
installation task might report the name of each file as it's installed.
ProgressMonitorInputStream
class.
The following tables list the commonly used API for using progress
bars and progress monitors. Because JProgressBar
is a subclass of
JComponent
, other methods you are likely to call on a
JProgressBar
are listed in The
JComponent Class. Note that ProgressMonitor
is a subclass of
Object
and is not a visual component.
The API for monitoring progress falls into these categories:
Constructor | Purpose |
---|---|
JProgressBar() |
Create a horizontal progress bar. The default constructor
initializes the progress bar with a minimum and initial value of 0 and a
maximum of 100. Use the min and
max arguments to specify other values. |
JProgressBar(int orientation)
|
Create a progress bar with the specified orientation, which can be
either JProgressBar.HORIZONTAL or
JProgressBar.VERTICAL . Use the min
and max arguments to specify minimum and maximum
values. |
JProgressBar(BoundedRangeModel) |
Create a horizontal progress bar with the specified range model. |
Method or Constructor | Purpose |
---|---|
void setValue(int) |
Set or get the current value of the progress bar. The value is constrained by the minimum and maximum values. |
double getPercentComplete() |
Get the percent complete for the progress bar. |
void setMinimum(int) |
Set or get the minimum value of the progress bar. |
void setMaximum(int) |
Set or get the maximum value of the progress bar. |
void setModel(BoundedRangeModel)
|
Set or get the model used by the progress bar. The model establishes the progress bar's constraints and values. So you can use this method as an alternative to using the individual set/get methods listed above. |
Method | Purpose |
---|---|
void setOrientation(int) |
Set or get whether the progress bar is vertical or horizontal.
Acceptable values are JProgressBar.VERTICAL or
JProgressBar.HORIZONTAL . |
void setBorderPainted(boolean) |
Set or get whether the progress bar has a border. |
void setStringPainted(boolean) |
Set or get whether the progress bar displays a percent string. By
default, the value of the percent string is the value returned by
getPercentComplete formatted as a percent. You can set the
string to be displayed with setString . |
void setString(String) |
Set or get the percent string. |
Method or Constructor | Purpose |
---|---|
ProgressMonitor(Component, Object, String, int,
int) |
Create a progress monitor. The Component argument is
the parent for the monitor's dialog. The Object argument is
a message to put on the option
pane within the dialog. The value of this object is typically a
String . The String argument is a changeable
status note. The final two int arguments set the minimum
and maximum values, respectively, for the progress bar used in the
dialog. |
ProgressMonitor
getProgressMonitor() (in ProgressMonitorInputStream ) |
Gets a progress monitor that monitors reading from an input stream. |
Method | Purpose |
---|---|
void setMinimum(int) |
Set or get the minimum value of the progress monitor. This value is used by the monitor to set up the progress bar in the dialog. |
void setMaximum(int) |
Set or get the maximum value of the progress monitor. This value is used by the monitor to set up the progress bar in the dialog. |
void setProgress(int) |
Update the monitor's progress. |
void setNote(String) |
Set or get the status note. This note is displayed on the dialog. To
omit the status note from the dialog, provide null as the
third argument to the monitor's constructor. |
void setMillisToDecideToPopup(int) |
Set or get the time after which the monitor should decide whether to popup a dialog. |
Method | Purpose |
---|---|
close() |
Close the progress monitor. This disposes of the dialog. |
boolean isCanceled() |
Determine whether the user pressed the Cancel button. |
import java.awt.*; import java.awt.event.*; import javax.swing.*; public class ProgressBarDemo extends JFrame { public final static int ONE_SECOND = 1000; private JProgressBar progressBar; private Timer timer; private JButton startButton; private LongTask task; private JTextArea taskOutput; private String newline = "\n"; public ProgressBarDemo() { super("ProgressBarDemo"); task = new LongTask(); //Create the demo's UI. startButton = new JButton("Start"); startButton.setActionCommand("start"); startButton.addActionListener(new ButtonListener()); progressBar = new JProgressBar(0, task.getLengthOfTask()); progressBar.setValue(0); progressBar.setStringPainted(true); taskOutput = new JTextArea(5, 20); taskOutput.setMargin(new Insets(5,5,5,5)); taskOutput.setEditable(false); JPanel panel = new JPanel(); panel.add(startButton); panel.add(progressBar); JPanel contentPane = new JPanel(); contentPane.setLayout(new BorderLayout()); contentPane.add(panel, BorderLayout.NORTH); contentPane.add(new JScrollPane(taskOutput), BorderLayout.CENTER); contentPane.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20)); setContentPane(contentPane); //Create a timer. timer = new Timer(ONE_SECOND, new ActionListener() { public void actionPerformed(ActionEvent evt) { progressBar.setValue(task.getCurrent()); taskOutput.append(task.getMessage() + newline); taskOutput.setCaretPosition( taskOutput.getDocument().getLength()); if (task.done()) { Toolkit.getDefaultToolkit().beep(); timer.stop(); startButton.setEnabled(true); progressBar.setValue(progressBar.getMinimum()); } } }); } /** * The actionPerformed method in this class * is called when the user presses the start button. */ class ButtonListener implements ActionListener { public void actionPerformed(ActionEvent evt) { startButton.setEnabled(false); task.go(); timer.start(); } } public static void main(String[] args) { JFrame frame = new ProgressBarDemo(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.pack(); frame.setVisible(true); } }
import java.awt.*; import java.awt.event.*; import javax.swing.*; public class ProgressMonitorDemo extends JFrame { public final static int ONE_SECOND = 1000; private ProgressMonitor progressMonitor; private Timer timer; private JButton startButton; private LongTask task; private JTextArea taskOutput; private String newline = "\n"; public ProgressMonitorDemo() { super("ProgressMonitorDemo"); task = new LongTask(); //Create the demo's UI. startButton = new JButton("Start"); startButton.setActionCommand("start"); startButton.addActionListener(new ButtonListener()); taskOutput = new JTextArea(5, 20); taskOutput.setMargin(new Insets(5,5,5,5)); taskOutput.setEditable(false); JPanel contentPane = new JPanel(); contentPane.setLayout(new BorderLayout()); contentPane.add(startButton, BorderLayout.NORTH); contentPane.add(new JScrollPane(taskOutput), BorderLayout.CENTER); contentPane.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20)); setContentPane(contentPane); //Create a timer. timer = new Timer(ONE_SECOND, new TimerListener()); } /** * The actionPerformed method in this class * is called each time the Timer "goes off". */ class TimerListener implements ActionListener { public void actionPerformed(ActionEvent evt) { if (progressMonitor.isCanceled() || task.done()) { progressMonitor.close(); task.stop(); Toolkit.getDefaultToolkit().beep(); timer.stop(); if (task.done()) { taskOutput.append("Task completed." + newline); } startButton.setEnabled(true); } else { progressMonitor.setNote(task.getMessage()); progressMonitor.setProgress(task.getCurrent()); taskOutput.append(task.getMessage() + newline); taskOutput.setCaretPosition( taskOutput.getDocument().getLength()); } } } /** * The actionPerformed method in this class * is called when the user presses the start button. */ class ButtonListener implements ActionListener { public void actionPerformed(ActionEvent evt) { progressMonitor = new ProgressMonitor(ProgressMonitorDemo.this, "Running a Long Task", "", 0, task.getLengthOfTask()); progressMonitor.setProgress(0); progressMonitor.setMillisToDecideToPopup(2 * ONE_SECOND); startButton.setEnabled(false); task.go(); timer.start(); } } public static void main(String[] args) { JFrame frame = new ProgressMonitorDemo(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.pack(); frame.setVisible(true); } }
/** Uses a SwingWorker to perform a time-consuming (and utterly fake) task. */ public class LongTask { private int lengthOfTask; private int current = 0; private String statMessage; LongTask() { //Compute length of task... //In a real program, this would figure out //the number of bytes to read or whatever. lengthOfTask = 1000; } /** * Called from ProgressBarDemo to start the task. */ void go() { current = 0; final SwingWorker worker = new SwingWorker() { public Object construct() { return new ActualTask(); } }; worker.start(); } /** * Called from ProgressBarDemo to find out how much work needs * to be done. */ int getLengthOfTask() { return lengthOfTask; } /** * Called from ProgressBarDemo to find out how much has been done. */ int getCurrent() { return current; } void stop() { current = lengthOfTask; } /** * Called from ProgressBarDemo to find out if the task has completed. */ boolean done() { if (current >= lengthOfTask) return true; else return false; } String getMessage() { return statMessage; } /** * The actual long running task. This runs in a SwingWorker thread. */ class ActualTask { ActualTask () { //Fake a long task, //making a random amount of progress every second. while (current < lengthOfTask) { try { Thread.sleep(1000); //sleep for a second current += Math.random() * 100; //make some progress if (current > lengthOfTask) { current = lengthOfTask; } statMessage = "Completed " + current + " out of " + lengthOfTask + "."; } catch (InterruptedException e) {} } } } }
import javax.swing.SwingUtilities; /** * This is the 3rd version of SwingWorker (also known as * SwingWorker 3), an abstract class that you subclass to * perform GUI-related work in a dedicated thread. For * instructions on using this class, see: * * http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html * * Note that the API changed slightly in the 3rd version: * You must now invoke start() on the SwingWorker after * creating it. */ public abstract class SwingWorker { private Object value; // see getValue(), setValue() private Thread thread; /** * Class to maintain reference to current worker thread * under separate synchronization control. */ private static class ThreadVar { private Thread thread; ThreadVar(Thread t) { thread = t; } synchronized Thread get() { return thread; } synchronized void clear() { thread = null; } } private ThreadVar threadVar; /** * Get the value produced by the worker thread, or null if it * hasn't been constructed yet. */ protected synchronized Object getValue() { return value; } /** * Set the value produced by worker thread */ private synchronized void setValue(Object x) { value = x; } /** * Compute the value to be returned by theget
method. */ public abstract Object construct(); /** * Called on the event dispatching thread (not on the worker thread) * after theconstruct
method has returned. */ public void finished() { } /** * A new method that interrupts the worker thread. Call this method * to force the worker to stop what it's doing. */ public void interrupt() { Thread t = threadVar.get(); if (t != null) { t.interrupt(); } threadVar.clear(); } /** * Return the value created by theconstruct
method. * Returns null if either the constructing thread or the current * thread was interrupted before a value was produced. * * @return the value created by theconstruct
method */ public Object get() { while (true) { Thread t = threadVar.get(); if (t == null) { return getValue(); } try { t.join(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); // propagate return null; } } } /** * Start a thread that will call theconstruct
method * and then exit. */ public SwingWorker() { final Runnable doFinished = new Runnable() { public void run() { finished(); } }; Runnable doConstruct = new Runnable() { public void run() { try { setValue(construct()); } finally { threadVar.clear(); } SwingUtilities.invokeLater(doFinished); } }; Thread t = new Thread(doConstruct); threadVar = new ThreadVar(t); } /** * Start the worker thread. */ public void start() { Thread t = threadVar.get(); if (t != null) { t.start(); } } }
Maintained by John Loomis, last updated 15 July 2000