|
In the following sections, we discuss three classes for indicating the progress of a slow activity. A JProgressBar is a Swing component that indicates progress. A ProgressMonitor is a dialog box that contains a progress bar. A ProgressMonitorInputStream displays a progress monitor dialog box while the stream is read. Progress BarsA progress bar is a simple componentjust a rectangle that is partially filled with color to indicate the progress of an operation. By default, progress is indicated by a string "n%". You can see a progress bar in the bottom right of Figure 6-41. Figure 6-41. A progress barYou construct a progress bar much as you construct a slider, by supplying the minimum and maximum value and an optional orientation: progressBar = new JProgressBar(0, 1000); progressBar = new JProgressBar(SwingConstants.VERTICAL, 0, 1000); You can also set the minimum and maximum with the setMinimum and setMaximum methods. Unlike a slider, the progress bar cannot be adjusted by the user. Your program needs to call setValue to update it. If you call progressBar.setStringPainted(true); the progress bar computes the completion percentage and displays a string "n%". If you want to show a different string, you can supply it with the setString method: if (progressBar.getValue() > 900) progressBar.setString("Almost Done"); The program in Example 6-16 shows a progress bar that monitors a simulated time-consuming activity. The SimulatedActivity class implements a thread that increments a value current 10 times per second. When it reaches a target value, the thread finishes. To terminate the thread before it has reached its target, you interrupt it. class SimulatedActivity implements Runnable { . . . public void run() { try { while (current < target && !interrupted()) { Thread.sleep(100); current++; } } catch (InterruptedException e) { } } int current; int target; } When you click the Start button, a new SimulatedActivity tHRead is started. Updating the progress bar would appear to be an easy matterthe simulated activity thread calls the setValue method. But that is not thread safe: Recall that you should call Swing methods only from the event dispatch thread. In practice, the apparent approach is also unrealistic: In general, a worker thread is not aware of the existence of the progress bar. Instead, the example program shows how to launch a timer that periodically polls the thread for a progress status and updates the progress bar. CAUTION
Recall that a Swing timer calls the actionPerformed method of its listeners and that these calls occur in the event dispatch thread. That means it is safe to update Swing components in the timer callback. Here is the timer callback from the example program. The current value of the simulated activity is displayed both in the text area and the progress bar. If the end of the simulation has been reached, the timer is stopped and the Start button is reenabled. public void actionPerformed(ActionEvent event) { int current = activity.getCurrent(); // show progress textArea.append(current + "\n"); progressBar.setValue(current); // check if task is completed if (current == activity.getTarget()) { activityMonitor.stop(); startButton.setEnabled(true); } } JDK 1.4 adds support for an indeterminate progress bar that shows an animation indicating some kind of progress, without giving an indication of the percentage of completion. That is the kind of progress bar that you see in your browserit indicates that the browser is waiting for the server and has no idea how long the wait may be. To display the "indeterminate wait" animation, call the setIndeterminate method. Example 6-16 shows the full program code. Example 6-16. ProgressBarTest.java1. import java.awt.*; 2. import java.awt.event.*; 3. import java.util.*; 4. import javax.swing.*; 5. import javax.swing.event.*; 6. import javax.swing.Timer; 7. 8. /** 9. This program demonstrates the use of a progress bar 10. to monitor the progress of a thread. 11. */ 12. public class ProgressBarTest 13. { 14. public static void main(String[] args) 15. { 16. JFrame frame = new ProgressBarFrame(); 17. frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 18. frame.setVisible(true); 19. } 20. } 21. 22. /** 23. A frame that contains a button to launch a simulated activity, 24. a progress bar, and a text area for the activity output. 25. */ 26. class ProgressBarFrame extends JFrame 27. { 28. public ProgressBarFrame() 29. { 30. setTitle("ProgressBarTest"); 31. setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); 32. 33. // this text area holds the activity output 34. textArea = new JTextArea(); 35. 36. // set up panel with button and progress bar 37. 38. JPanel panel = new JPanel(); 39. startButton = new JButton("Start"); 40. progressBar = new JProgressBar(); 41. progressBar.setStringPainted(true); 42. panel.add(startButton); 43. panel.add(progressBar); 44. 45. checkBox = new JCheckBox("indeterminate"); 46. checkBox.addActionListener(new 47. ActionListener() 48. { 49. public void actionPerformed(ActionEvent event) 50. { 51. progressBar.setIndeterminate(checkBox.isSelected()); 52. } 53. }); 54. panel.add(checkBox); 55. add(new JScrollPane(textArea), BorderLayout.CENTER); 56. add(panel, BorderLayout.SOUTH); 57. 58. // set up the button action 59. 60. startButton.addActionListener(new 61. ActionListener() 62. { 63. public void actionPerformed(ActionEvent event) 64. { 65. progressBar.setMaximum(1000); 66. activity = new SimulatedActivity(1000); 67. new Thread(activity).start(); 68. activityMonitor.start(); 69. startButton.setEnabled(false); 70. } 71. }); 72. 73. 74. // set up the timer action 75. 76. activityMonitor = new Timer(500, new 77. ActionListener() 78. { 79. public void actionPerformed(ActionEvent event) 80. { 81. int current = activity.getCurrent(); 82. 83. // show progress 84. textArea.append(current + "\n"); 85. progressBar.setStringPainted(!progressBar.isIndeterminate()); 86. progressBar.setValue(current); 87. 88. // check if task is completed 89. if (current == activity.getTarget()) 90. { 91. activityMonitor.stop(); 92. startButton.setEnabled(true); 93. } 94. } 95. }); 96. } 97. 98. private Timer activityMonitor; 99. private JButton startButton; 100. private JProgressBar progressBar; 101. private JCheckBox checkBox; 102. private JTextArea textArea; 103. private SimulatedActivity activity; 104. 105. public static final int DEFAULT_WIDTH = 400; 106. public static final int DEFAULT_HEIGHT = 200; 107. } 108. 109. /** 110. A simulated activity runnable. 111. */ 112. class SimulatedActivity implements Runnable 113. { 114. /** 115. Constructs the simulated activity thread object. The 116. thread increments a counter from 0 to a given target. 117. @param t the target value of the counter. 118. */ 119. public SimulatedActivity(int t) 120. { 121. current = 0; 122. target = t; 123. } 124. 125. public int getTarget() 126. { 127. return target; 128. } 129. 130. public int getCurrent() 131. { 132. return current; 133. } 134. 135. public void run() 136. { 137. try 138. { 139. while (current < target) 140. { 141. Thread.sleep(100); 142. current++; 143. } 144. } 145. catch(InterruptedException e) 146. { 147. } 148. } 149. 150. private volatile int current; 151. private int target; 152. } Progress MonitorsA progress bar is a simple component that can be placed inside a window. In contrast, a ProgressMonitor is a complete dialog box that contains a progress bar (see Figure 6-42). The dialog box contains a Cancel button. If you click it, the monitor dialog box is closed. In addition, your program can query whether the user has canceled the dialog box and terminate the monitored action. (Note that the class name does not start with a "J".) Figure 6-42. A progress monitor dialog boxYou construct a progress monitor by supplying the following:
However, the progress monitor cannot measure progress or cancel an activity by itself. You still need to periodically set the progress value by calling the setProgress method. (This is the equivalent of the setValue method of the JProgressBar class.) As you update the progress value, you should also call the isCanceled method to see if the program user has clicked the Cancel button. When the monitored activity has concluded, call the close method to dismiss the dialog box. You can reuse the same dialog box by calling start again. The example program looks very similar to that of the preceding section. We still need to launch a timer to watch over the progress of the simulated activity and update the progress monitor. Here is the timer callback. public void actionPerformed(ActionEvent event) { int current = activity.getCurrent(); // show progress textArea.append(current + "\n"); progressDialog.setProgress(current); // check if task is completed or canceled if (current == activity.getTarget() || progressDialog.isCanceled()) { activityMonitor.stop(); progressDialog.close(); activity.interrupt(); startButton.setEnabled(true); } } Note that there are two conditions for termination. The activity might have completed, or the user might have canceled it. In each of these cases, we close down
If you run the program in Example 6-17, you can observe an interesting feature of the progress monitor dialog box. The dialog box doesn't come up immediately. Instead, it waits for a short interval to see if the activity has already been completed or is likely to complete in less time than it would take for the dialog box to appear. You control the timing as follows. Use the setMillisToDecideToPopup method to set the number of milliseconds to wait between the construction of the dialog object and the decision whether to show the pop-up at all. The default value is 500 milliseconds. The setMillisToPopup is your estimation of the time the dialog box needs to pop up. The Swing designers set this value to a default of 2 seconds. Clearly they were mindful of the fact that Swing dialogs don't always come up as snappily as we all would like. You should probably not touch this value. Example 6-17 shows the progress monitor in action, again measuring the progress of a simulated activity. As you can see, the progress monitor is convenient to use and only requires that you periodically query the thread that you want to monitor. Example 6-17. ProgressMonitorTest.java1. import java.awt.*; 2. import java.awt.event.*; 3. import java.util.*; 4. import javax.swing.*; 5. import javax.swing.event.*; 6. import javax.swing.Timer; 7. 8. /** 9. A program to test a progress monitor dialog. 10. */ 11. public class ProgressMonitorTest 12. { 13. public static void main(String[] args) 14. { 15. JFrame frame = new ProgressMonitorFrame(); 16. frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 17. frame.setVisible(true); 18. } 19. } 20. 21. /** 22. A frame that contains a button to launch a simulated activity 23. and a text area for the activity output. 24. */ 25. class ProgressMonitorFrame extends JFrame 26. { 27. public ProgressMonitorFrame() 28. { 29. setTitle("ProgressMonitorTest"); 30. setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); 31. 32. // this text area holds the activity output 33. textArea = new JTextArea(); 34. 35. // set up a button panel 36. JPanel panel = new JPanel(); 37. startButton = new JButton("Start"); 38. panel.add(startButton); 39. 40. add(new JScrollPane(textArea), BorderLayout.CENTER); 41. add(panel, BorderLayout.SOUTH); 42. 43. // set up the button action 44. 45. startButton.addActionListener(new 46. ActionListener() 47. { 48. public void actionPerformed(ActionEvent event) 49. { 50. // start activity 51. activity = new SimulatedActivity(1000); 52. activityThread = new Thread(activity); 53. activityThread.start(); 54. 55. // launch progress dialog 56. progressDialog = new ProgressMonitor(ProgressMonitorFrame.this, 57. "Waiting for Simulated Activity", null, 0, activity.getTarget()); 58. 59. // start timer 60. activityMonitor.start(); 61. 62. startButton.setEnabled(false); 63. } 64. }); 65. 66. // set up the timer action 67. 68. activityMonitor = new Timer(500, new 69. ActionListener() 70. { 71. public void actionPerformed(ActionEvent event) 72. { 73. int current = activity.getCurrent(); 74. 75. // show progress 76. textArea.append(current + "\n"); 77. progressDialog.setProgress(current); 78. 79. // check if task is completed or canceled 80. if (current == activity.getTarget() || progressDialog.isCanceled()) 81. { 82. activityMonitor.stop(); 83. progressDialog.close(); 84. activityThread.interrupt(); 85. startButton.setEnabled(true); 86. } 87. } 88. }); 89. } 90. 91. private Timer activityMonitor; 92. private JButton startButton; 93. private ProgressMonitor progressDialog; 94. private JTextArea textArea; 95. private Thread activityThread; 96. private SimulatedActivity activity; 97. 98. public static final int DEFAULT_WIDTH = 300; 99. public static final int DEFAULT_HEIGHT = 200; 100. } 101. 102. /** 103. A simulated activity runnable. 104. */ 105. class SimulatedActivity implements Runnable 106. { 107. /** 108. Constructs the simulated activity thread object. The 109. thread increments a counter from 0 to a given target. 110. @param t the target value of the counter. 111. */ 112. public SimulatedActivity(int t) 113. { 114. current = 0; 115. target = t; 116. } 117. 118. public int getTarget() 119. { 120. return target; 121. } 122. 123. public int getCurrent() 124. { 125. return current; 126. } 127. 128. public void run() 129. { 130. try 131. { 132. while (current < target) 133. { 134. Thread.sleep(100); 135. current++; 136. } 137. } 138. catch(InterruptedException e) 139. { 140. } 141. } 142. 143. private volatile int current; 144. private int target; 145. } Monitoring the Progress of Input StreamsThe Swing package contains a useful stream filter, ProgressMonitorInputStream, that automatically pops up a dialog box that monitors how much of the stream has been read. This filter is extremely easy to use. You sandwich in a ProgressMonitorInputStream between your usual sequence of filtered streams. (See Volume 1, Chapter 12 for more information on streams.) For example, suppose you read text from a file. You start out with a FileInputStream: FileInputStream in = new FileInputStream(f); Normally, you would convert in to an InputStreamReader: InputStreamReader reader = new InputStreamReader(in); However, to monitor the stream, first turn the file input stream into a stream with a progress monitor: ProgressMonitorInputStream progressIn = new ProgressMonitorInputStream(parent, caption, in); You supply the parent component, a caption, and, of course, the stream to monitor. The read method of the progress monitor stream simply passes along the bytes and updates the progress dialog box. You now go on building your filter sequence: InputStreamReader reader = new InputStreamReader(progressIn); That's all there is to it. When the file is read, the progress monitor automatically pops up (see Figure 6-43). This is a very nice application of stream filtering. Figure 6-43. A progress monitor for an input streamCAUTION
The program in Example 6-18 counts the lines in a file. If you read in a large file (such as "The Count of Monte Cristo" on the CD), then the progress dialog box pops up. Note that the program doesn't use a very efficient way of filling up the text area. It would be faster to first read the file into a StringBuffer and then set the text of the text area to the string buffer contents. However, in this example program, we actually like this slow approachit gives you more time to admire the progress dialog box. To avoid flicker, we do not display the text area while it is filling up. Example 6-18. ProgressMonitorInputStreamTest.java1. import java.awt.*; 2. import java.awt.event.*; 3. import java.io.*; 4. import java.util.*; 5. import javax.swing.*; 6. import javax.swing.event.*; 7. 8. /** 9. A program to test a progress monitor input stream. 10. */ 11. public class ProgressMonitorInputStreamTest 12. { 13. public static void main(String[] args) 14. { 15. JFrame frame = new TextFrame(); 16. frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 17. frame.setVisible(true); 18. } 19. } 20. 21. /** 22. A frame with a menu to load a text file and a text area 23. to display its contents. The text area is constructed 24. when the file is loaded and set as the content pane of 25. the frame when the loading is complete. That avoids flicker 26. during loading. 27. */ 28. class TextFrame extends JFrame 29. { 30. public TextFrame() 31. { 32. setTitle("ProgressMonitorInputStreamTest"); 33. setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); 34. 35. // set up menu 36. 37. JMenuBar menuBar = new JMenuBar(); 38. setJMenuBar(menuBar); 39. JMenu fileMenu = new JMenu("File"); 40. menuBar.add(fileMenu); 41. openItem = new JMenuItem("Open"); 42. openItem.addActionListener(new 43. ActionListener() 44. { 45. public void actionPerformed(ActionEvent event) 46. { 47. try 48. { 49. openFile(); 50. } 51. catch(IOException exception) 52. { 53. exception.printStackTrace(); 54. } 55. } 56. }); 57. 58. fileMenu.add(openItem); 59. exitItem = new JMenuItem("Exit"); 60. exitItem.addActionListener(new 61. ActionListener() 62. { 63. public void actionPerformed(ActionEvent event) 64. { 65. System.exit(0); 66. } 67. }); 68. fileMenu.add(exitItem); 69. } 70. 71. /** 72. Prompts the user to select a file, loads the file into 73. a text area, and sets it as the content pane of the frame. 74. */ 75. public void openFile() throws IOException 76. { 77. JFileChooser chooser = new JFileChooser(); 78. chooser.setCurrentDirectory(new File(".")); 79. chooser.setFileFilter( 80. new javax.swing.filechooser.FileFilter() 81. { 82. public boolean accept(File f) 83. { 84. String fname = f.getName().toLowerCase(); 85. return fname.endsWith(".txt") || f.isDirectory(); 86. } 87. public String getDescription() 88. { 89. return "Text Files"; 90. } 91. }); 92. 93. int r = chooser.showOpenDialog(this); 94. if (r != JFileChooser.APPROVE_OPTION) return; 95. final File f = chooser.getSelectedFile(); 96. 97. // set up stream and reader filter sequence 98. 99. FileInputStream fileIn = new FileInputStream(f); 100. ProgressMonitorInputStream progressIn 101. = new ProgressMonitorInputStream(this, "Reading " + f.getName(), fileIn); 102. final Scanner in = new Scanner(progressIn); 103. 104. // the monitored activity must be in a new thread. 105. 106. Runnable readRunnable = new 107. Runnable() 108. { 109. public void run() 110. { 111. final JTextArea textArea = new JTextArea(); 112. 113. while (in.hasNextLine()) 114. { 115. String line = in.nextLine(); 116. textArea.append(line); 117. textArea.append("\n"); 118. } 119. in.close(); 120. 121. // set content pane in the event dispatch thread 122. EventQueue.invokeLater(new 123. Runnable() 124. { 125. public void run() 126. { 127. setContentPane(new JScrollPane(textArea)); 128. validate(); 129. } 130. }); 131. 132. } 133. }; 134. 135. Thread readThread = new Thread(readRunnable); 136. readThread.start(); 137. } 138. 139. private JMenuItem openItem; 140. private JMenuItem exitItem; 141. 142. public static final int DEFAULT_WIDTH = 300; 143. public static final int DEFAULT_HEIGHT = 200; 144. } javax.swing.JProgressBar 1.2
javax.swing.ProgressMonitor 1.2
javax.swing.ProgressMonitorInputStream 1.2
|
|