GUI事件处理和绘图代码在一个被称为事件分发线程的特殊线程中执行。如果一个事件需要很长的时间处理,线程就不能顾及到队列中的其他任务。为了解决这个问题,可以运行费时的任务来处理单独线程中的事件。SwingWorker是一个实现Runnable的抽象类,可以定义一个任务来扩展SwingWorker,使用任务产生的结果来运行费时的任务并更新GUI。
1 #doInBackground():T //执行任务并返回T类型的结果 2 #done():void //结束doInBackground之后执行事件分发线程 3 4 +execute():void //安排这个SwingWorker来执行工作线程 5 +get():T //如果有必要则等待计算完成,然后获取它的结果(即doInBackground返回的结果) 6 7 +isDone():boolean //如果任务完成则返回true 8 +cancle():beelean //取消任务 9 #publish(data v...):void //发送process方法要处理的数据。这个方法用于从doInBackground中传送之间结果, 10 //以处理process方法中的事件分发线程。注意,v...表示变种参数 11 12 #process(data:java.util.List<V>):void //异步地接受事件分发线程上来自publish方法的数据 13 14 #setProgress(progress:int):void //设置进展约束属性。这个值应该从0到100 15 #getProgress():void //返回进展约束属性
SwingWorker有两个参数即SwingWorker< T,V>,T是doInBackground和get方法的返回类型;V是publish和process方法要处理的数据类型
完成后台线程的计算可能需要很长的时间,最好能通知用户计算的进度,可以使用JProgressBar来显示进度。考虑例子:让用户指定素数的个数n,并显示从2开始的前n个素数。代码如下:
1 import java.awt.BorderLayout; 2 import java.awt.event.ActionEvent; 3 import java.awt.event.ActionListener; 4 import java.beans.PropertyChangeEvent; 5 import java.beans.PropertyChangeListener; 6 import java.util.List; 7 8 import javax.swing.JApplet; 9 import javax.swing.JButton; 10 import javax.swing.JLabel; 11 import javax.swing.JPanel; 12 import javax.swing.JProgressBar; 13 import javax.swing.JScrollPane; 14 import javax.swing.JTextArea; 15 import javax.swing.JTextField; 16 import javax.swing.SwingWorker; 17 18 public class ProgressBarDemo extends JApplet { 19 20 /** 21 * 22 */ 23 private static final long serialVersionUID = 1L; 24 25 private JProgressBar jpb = new JProgressBar(); //定义一个进度条显示进度 26 private JTextArea jtaResult = new JTextArea(); //定义一个文本域显示素数 27 private JTextField jtfPrimeCount = new JTextField(8); //定义一个文本框用于用户填写素数个数 28 private JButton jbtnDisplayPrime = new JButton("Display Prime"); //定义一个按钮执行任务 29 30 public ProgressBarDemo() { 31 jpb.setStringPainted(true); //设置显示进度的百分比 32 jpb.setValue(0); 33 jpb.setMaximum(100); 34 35 //设置文本域自动换行,并且断行不断字 36 jtaResult.setWrapStyleWord(true); 37 jtaResult.setLineWrap(true); 38 39 JPanel panel = new JPanel(); 40 panel.add(new JLabel("Enter the prime number count")); 41 panel.add(jtfPrimeCount); 42 panel.add(jbtnDisplayPrime); 43 44 add(jpb, BorderLayout.NORTH); 45 add(new JScrollPane(jtaResult), BorderLayout.CENTER); 46 add(panel, BorderLayout.SOUTH); 47 48 jbtnDisplayPrime.addActionListener(new ActionListener() { 49 50 @Override 51 public void actionPerformed(ActionEvent e) { 52 ComputePrime task = 53 new ComputePrime(Integer.parseInt(jtfPrimeCount.getText()), jtaResult); 54 55 task.addPropertyChangeListener(new PropertyChangeListener() { 56 57 @Override 58 public void propertyChange(PropertyChangeEvent evt) { 59 //判断改变的属性是否是进度,如果是则获取进度的值并显示在进度条上 60 if("progress".equals(evt.getPropertyName())) { 61 jpb.setValue((Integer)evt.getNewValue()); 62 } 63 } 64 }); 65 66 task.execute(); //执行 67 } 68 }); 69 } 70 71 static class ComputePrime extends SwingWorker<Integer, Integer> { 72 73 private int count; 74 private JTextArea result; 75 76 public ComputePrime(int count, JTextArea result) { 77 this.count = count; 78 this.result = result; 79 } 80 81 @Override 82 protected Integer doInBackground() throws Exception { 83 publishPrimeNumbers(count); 84 return 0; 85 } 86 87 //把找到的素数全部显示出来 88 @Override 89 protected void process(List<Integer> list) { 90 for(int i=0; i<list.size(); i++) { 91 result.append(list.get(i) + " "); 92 } 93 super.process(list); 94 } 95 96 private void publishPrimeNumbers(int n) { 97 int count = 0; 98 int number = 2; 99 100 while(count <= n) { 101 if(isPrime(number)) { 102 count ++; 103 setProgress(100 * count / n); //设置进度 104 publish(number); //通过publish方法将找到的素数number发送给process方法 105 } 106 107 number ++; 108 } 109 } 110 111 public static boolean isPrime(int number) { 112 for(int divisor = 2; divisor <= number / 2; divisor++) { 113 if(number % divisor == 0) { 114 return false; 115 } 116 } 117 118 return true; 119 } 120 121 } 122 123 }
注:doInBackground方法作为任务线程的一部分执行,它负责完成线程的基本任务,并以返回值来作为线程的执行结果。继承类须覆盖该方法并确保包含或代理任务线程的基本任务。不要直接调用该方法,应使用任务对象的execute方法来调度执行。
当从任务线程调用publish方法时,SwingWorker类调度process方法,如果SwingWorker通过publish发布了一些数据,那么也应该实现process方法来处理这些中间结果,任务对象的父类会在事件分发线程上激活process方法,因此在此方法中程序可以安全的更新UI组件。
无论何时调用setProgress方法,SwingWorker类都会产生一个propertyChangeEvent。setProgress方法设置一个0到100之间的新的进度值,这个值包装在PropertyChangeEvent中,这个事件的监听器使用getNewValue()方法获取进度值。
程序执行结果如下: