segunda-feira, 29 de dezembro de 2008

Making Progress with Swing's Progress Monitoring API


Enviado para você por Penha através do Google Reader:


via Core Java Technologies Tech Tips de dananourie em 15/12/08

by Jennie Hall

In this tip, you'll learn how to use Swing's progress indicator support to monitor and report on the progress of long-running operations. It is a good practice to keep users informed as they interact with an application; one way to do this is with a progress bar. A progress bar is an animated image that indicates the degree of completion of a given task. The animation typically looks like a rectangular bar that fills in as the task becomes more complete.

Swing's Progress Monitoring API consists of three classes that enable the use of progress bars. JProgressBar subclasses JComponent and is a graphical component that illustrates the progress of an operation. It can be embedded within other graphical components. ProgressMonitor subclasses Object and is not itself a graphical component. It monitors a task and pops a dialog box with a progress bar in it. ProgressMonitorInputStream is a stream filter with an associated progress monitor. As the stream is read, the progress monitor automatically receives updates on the number of bytes read and displays the percentage of work completed in its dialog box.

The Java Tutorial provides some good rules of thumb that help to determine the appropriate class to use in a given situation. For example, use JProgressBar when you need more than one progress bar or you would like more control over the configuration of the progress bar. If you need a convenient way to cancel the monitored task or to allow the user to dismiss the dialog box while continuing to run the task in the background, ProgressMonitor provides for this. ProgressMonitor also features a modifiable status note in its dialog box that can be updated periodically by your application. The sample application for this tip uses ProgressMonitor.

The Sample Application

The sample application copies files located in a source directory (in) to a destination directory (out). It has a Swing GUI that allows the user to launch the copy operation by clicking the Copy Files button as shown in Figure 1.

Figure 1: Sample Application

Upon the launch of the copy operation, the application creates a progress monitor that keeps track of the amount of work completed and displays this information in a dialog containing a progress bar. The application also writes output regarding the progress of the operation to the console as shown in Figure 2.

Figure 2: Dialog containing progress bar

As shown above, the GUI displays the number of kilobytes copied, percentage of job completion, and file name of the file currently being copied. The user may cancel the operation at any time by clicking the Cancel button. After the copy operation completes, the GUI appears as shown in Figure 3:

Stepping Through the Sample Application

The sample application consists of a class, ProgressMonitorExample, that extends javax.swing.JPanel and implements java.awt.event.ActionListener and java.beans.PropertyChangeListener. ProgressMonitorExample's main() method tells the event dispatch thread to schedule the execution of a Runnable that creates the application GUI:

	public static void main(String[] args) { 		// tell the event dispatch thread to schedule the execution 		// of this Runnable (which will create the example app GUI)                  // for a later time 		SwingUtilities.invokeLater(new Runnable() { 			public void run() { 				// create example app window 				JFrame frame = new JFrame("Progress Monitor Example"); 				// application will exit on close 				frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 				frame.setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); 				 				// create example app content pane 				// ProgressMonitorExample constructor does additional GUI setup 				JComponent contentPane = new ProgressMonitorExample(); 				contentPane.setOpaque(true); 				frame.setContentPane(contentPane);					 				... 

ProgressMonitorExample contains an inner class, CopyFiles, that extends javax.swing.SwingWorker. When the user clicks the Copy Files button, ProgressMonitorExample's actionPerformed() method receives the event, creates a new ProgressMonitor, and starts the file-copying operation on a background thread. Here's the code that creates the ProgressMonitor:

    public void actionPerformed(ActionEvent event) {         // make sure there are files to copy         srcDir = new File("in");         if (srcDir.exists() && (srcDir.listFiles() != null && srcDir.listFiles().length > 0)) {             // create the progress monitor             progressMonitor = new ProgressMonitor(ProgressMonitorExample.this,                                                   "Operation in progress...",                                                   "", 0, 100);             progressMonitor.setProgress(0); 			... 

ProgressMonitor has a single constructor. The first argument is the parent component to the progress monitor's dialog box. The second argument, of type Object, is displayed on the dialog box. It should be a string, icon, or component. This example supplies the constructor with a string that lets the user know that the requested operation is underway. The third argument is an optional status note that also appears on the dialog box. This status note can be updated periodically as the monitored task runs. Set this value to null if no status note is necessary. The fourth and fifth arguments are the minimum and maximum values for the progress bar in the progress monitor dialog box.

The code below, also excerpted from actionPerformed(), creates a new instance of CopyFiles, adds ProgressMonitorExample as a property change listener on the instance, and executes the instance:

	// schedule the copy files operation for execution on a background thread  	operation = new CopyFiles(); 	// add ProgressMonitorExample as a listener on CopyFiles; 	// we're specifically interested in the progress property 	operation.addPropertyChangeListener(this); 	operation.execute(); 	// we're running our operation; disable copy button 	copyButton.setEnabled(false);

CopyFiles subclasses SwingWorker, so the call to inherited method execute() schedules CopyFiles for execution on a background thread and returns immediately. Time-consuming activities should always run on a background thread rather than the event dispatch thread. This way, the GUI remains responsive.

Although the file-copying operation has begun, the progress monitor dialog box doesn't pop up right away. By default, ProgressMonitor waits for 500 ms before making a decision on whether or not to show the dialog box at all. After this time period has elapsed, if ProgressMonitor determines that the monitored operation has already completed or is likely to complete before the dialog box can be displayed, ProgressMonitor does not pop the dialog box. ProgressMonitor's method setMillisToDecideToPopup() controls this setting. setMillisToPopup() sets the estimated amount of time it will take the dialog box to appear; the default value for this property is 2 seconds.

The real work of copying the files occurs in doInBackground(), an abstract method on SwingWorker that CopyFiles overrides. Here's a partial listing:

	@Override 	public Void doInBackground() { 		int progress = 0; 		// initialize bound property progress (inherited from SwingWorker) 		setProgress(0); 		// get the files to be copied from the source directory 		File[] files = srcDir.listFiles(); 		// determine the scope of the task 		totalBytes = calcTotalBytes(files); 		 		while (progress < 100 && !isCancelled()) {                  			// copy the files to the destination directory 			for (File f : files) { 				File destFile = new File(destDir, f.getName()); 				long previousLen = 0; 				 				try { 					InputStream in = new FileInputStream(f); 					OutputStream out = new FileOutputStream(destFile);                     					byte[] buf = new byte[1024]; 					int counter = 0; 					int len; 					 					while ((len = > 0) { 						out.write(buf, 0, len); 						counter += len; 						bytesCopied += (destFile.length() - previousLen); 						previousLen = destFile.length(); 						if (counter > PROGRESS_CHECKPOINT || bytesCopied == totalBytes) { 							// get % complete for the task 							progress = (int)((100 * bytesCopied) / totalBytes); 							// capture the filename of the file currently being copied 							fileName = f.getName(); 							counter = 0; 							// set new progress value on bound property 							// and fire property change event 							setProgress(progress); 						} 					} 					in.close(); 					out.close(); 				} catch (IOException e) { 					// ignore 				} 				... 

doInBackground() gets any files located in the in directory and copies them one by one to the out directory. Each time a specified number of bytes have been copied, the application calculates what percentage of the total number of bytes has been copied so far and captures the filename of the file currently being copied. The application then updates the bound property progress with the calculated percentage, firing a property change event in the process.

ProgressMonitorExample's propertyChange() method extracts the progress value from the property change event. It then updates the progress monitor by calling its setProgress() and passing the progress value. propertyChange() also updates the progress monitor's status note with the number of kilobytes copied so far, the percentage of work completed, and the filename of the file currently being copied. Here's the code:

    public void propertyChange(PropertyChangeEvent event) {         // get the progress information from the event         // and set it on the progress monitor         if (event.getPropertyName().equals("progress")) {             int progress = ((Integer)event.getNewValue()).intValue();             progressMonitor.setProgress(progress);             String progressNote = operation.getKiloBytesCopied()                           + " of " + operation.getTotalKiloBytes()                            + " kb copied; job " + progress + "% complete.";             String fileNameNote = "Now copying " + operation.getFileName();                          // update the progress monitor's status note and              // additionally append the note to the console             if (progress < 100) {                 progressMonitor.setNote(progressNote + " " + fileNameNote);                 console.append(progressNote + "\n" + fileNameNote + "\n");             } else {                 progressMonitor.setNote(progressNote);                 console.append(progressNote + "\n");             }                          // if the operation is finished or has been canceled by             // the user, take appropriate action             if (progressMonitor.isCanceled() || operation.isDone()) {                 if (progressMonitor.isCanceled()) {                     operation.cancel(true);                     console.append("Copy operation canceled.\n");                 } else {                     console.append("Copy operation completed.\n");                 }                  copyButton.setEnabled(true);             }         }     }  

Notice that ProgressMonitor provides a convenient way to determine if the dialog has been canceled by the user. The sample application responds to a user cancellation by terminating the monitored activity, but in other situations it might be appropriate to allow the user to dismiss the dialog box while the activity continues to run in the background. When its background operation is finished, CopyFiles sets its own state to done and invokes the done() method in the event dispatch thread. The sample application's done() method simply resets the application.

Running the Sample Application

To run the sample application, download the sample code and unzip it. The sample application assumes that there are files to copy in the in directory located under the project root, so add some (preferably large) files of your choice to this directory. Launch NetBeans and select File -> Open Project. In the Open Project dialog box, navigate to the directory where you unzipped the sample code and select the folder progressMonitorExample. Select the Open as Main Project check box. Click Open Project Folder. Right-click the progressMonitorExample project and select Build, then right-click the project again and select Run.

References and Resources

Sample code for this tip
The Java Tutorial

About the Author

Jennie Hall is a lead developer working in the financial sector.


Coisas que você pode fazer a partir daqui:

Share |

0 comentários:


Pesquisa na WEB

assine o feed

siga no Twitter




comente também