在Windows操作系统中,最常用的进度条对话框就是文件复制时的弹出框,如果想让用户愉快的使用你开发
的软件,那么在执行某个较长时间的操作时候,就应该弹出一个进度条提示框,告诉用户程序正在做什么.
做到什么地步了.进度条提示框可以让用户更有安全感也可以提高用户的耐心.
前面用到的SWT组件的进度条ProgressBar类太底层,还需要处理诸如线程分配的问题.现在
JFace提供了更容易使用的ProgressMonitorDialog类来创建进度条对话框.
标准的进度条对话框:
ProgressMonitorDialog类还算简单,只要将执行代码用IRunnableWithProgress类包装一下即可,本实例中在窗口中建立 一个GO按钮,当单击该按钮时执行一个需时较长的操作(以每次循环停顿一秒来模拟),并弹出一个进度条对话框.程序在未加进度条时的代码如下:
ProgressMonitorDialog.java
1 import org.eclipse.core.runtime.IProgressMonitor; 2 import org.eclipse.jface.dialogs.ProgressMonitorDialog; 3 import org.eclipse.jface.operation.IRunnableWithProgress; 4 import org.eclipse.swt.SWT; 5 import org.eclipse.swt.events.SelectionAdapter; 6 import org.eclipse.swt.events.SelectionEvent; 7 import org.eclipse.swt.layout.RowLayout; 8 import org.eclipse.swt.widgets.Button; 9 import org.eclipse.swt.widgets.Display; 10 import org.eclipse.swt.widgets.Shell; 11 12 public class ProgressMonitorDialog1 { 13 14 public static void main(String[] args) { 15 new ProgressMonitorDialog1().open(); 16 } 17 18 public void open() { 19 final Display display = Display.getDefault(); 20 final Shell shell = new Shell(); 21 shell.setSize(500, 375); 22 23 shell.setLayout(new RowLayout()); 24 Button button = new Button(shell, SWT.NONE); 25 button.setText(" GO "); 26 button.addSelectionListener(new SelectionAdapter() { 27 public void widgetSelected(SelectionEvent e) { 28 // 创建进度条对话框的处理过程对象 29 IRunnableWithProgress runnable = new IRunnableWithProgress() { 30 public void run(IProgressMonitor monitor) { 31 monitor.beginTask("开始执行......", 10); 32 for (int i = 0; i < 10; i++) {// 循环10次,每次间隔一秒 33 if (monitor.isCanceled()) // 随时监控是否选择了对话框的“取消”按钮 34 return;// 中断处理 35 try { 36 Thread.sleep(1000); 37 } catch (Throwable t) {} 38 monitor.setTaskName("第" + (i + 1) + "次循环");// 提示信息 39 monitor.worked(1);// 进度条前进一步 40 } 41 monitor.done();// 进度条前进到完成 42 } 43 }; 44 try { 45 // 创建一个进度条对话框,并将runnable传入 46 // 第一个参数推荐设为true,如果设为false则处理程序会运行在UI线程里,界面将有一点停滞感。 47 // 第二个参数:true=对话框的“取消”按钮有效 48 new ProgressMonitorDialog(shell).run(true, true, runnable); 49 } catch (Exception e2) { 50 e2.printStackTrace(); 51 } 52 } 53 }); 54 55 shell.layout(); 56 shell.open(); 57 while (!shell.isDisposed()) { 58 if (!display.readAndDispatch()) 59 display.sleep(); 60 } 61 } 62 63 }
运行结果:
要在执行程序时候弹出一个进度条,则就要对GO按钮的widgetSelected方法做修改(上面代码已经修改完毕):
· 编写一个处理过程接口IRunnableWithProgress的匿名实现类,并将耗时代码嵌入其run方法中,可以用run方法的参数monitor操作进度条向前移动.当然这个类写成命名内部类或者外部类也是可以的.
·创建一个ProgressMonitorDialog对话框对象,并用它的run方法弹出进度条对话框.接收IRunnableWithProgress对象做参数.run方法有受控异常需要处理,需要用try/catch括起来.
程序说明:如果无法确定进度条需要多少格长度,可以将值设置的大一些,即使真实循环数没有达到或者超过了也不会引起程序出错.
进度条对话框的实现代码看是复杂,其实简化而无变化,代码框架都一个样,关键是把IRunnableWithProgress.run方法中的处理程序写好.
反复显示的进度条对话框.
有些后台任务,可能无法将它分解成多少部,它就是一个单一的长时间的处理过程.这时可以弹出一个对话框,进度条不断的重复移动,直到后台任务结束,或被中止.
实际上这种方案后台处理程序和前台弹出进度框被分为了两个独立的部分,但它们之间仍然需要一个信使来传递信息.比如,后台任务完成了,那么就应该通知前台进度框关闭:单击了前台进度框的"取消"按钮,也需要通知后台任务中断处理.这个信使可以用一个boolean变量标志位来充当,不必去通知前台后台,自己监控该变量的变化即可.具体的实体如下代码所示:
ProgressMonitorDialog2.java
1 import org.eclipse.core.runtime.IProgressMonitor; 2 import org.eclipse.jface.dialogs.ProgressMonitorDialog; 3 import org.eclipse.jface.operation.IRunnableWithProgress; 4 import org.eclipse.swt.SWT; 5 import org.eclipse.swt.events.SelectionAdapter; 6 import org.eclipse.swt.events.SelectionEvent; 7 import org.eclipse.swt.layout.RowLayout; 8 import org.eclipse.swt.widgets.Button; 9 import org.eclipse.swt.widgets.Display; 10 import org.eclipse.swt.widgets.Shell; 11 12 public class ProgressMonitorDialog2 { 13 14 public static void main(String[] args) { 15 new ProgressMonitorDialog2().open(); 16 } 17 18 public void open() { 19 final Display display = Display.getDefault(); 20 final Shell shell = new Shell(); 21 shell.setSize(500, 375); 22 23 shell.setLayout(new RowLayout()); 24 Button button = new Button(shell, SWT.NONE); 25 button.setText(" GO "); 26 button.addSelectionListener(new SelectionAdapter() { 27 private boolean stopFlag;// 停止标志 28 29 private void go() { 30 for (int i = 0; i < 10; i++) {// 循环10次,每次间隔一秒 31 System.out.println("第" + (i + 1) + "个任务执行完毕"); 32 if (stopFlag) {// 监控是否要让停止后台任务 33 System.out.println("被中断了"); 34 return; 35 } 36 try { 37 Thread.sleep(1000); 38 } catch (Throwable t) {} 39 } 40 stopFlag = true;// 执行完毕后把标志位设为停止,好通知给进度框 41 System.out.println("全部任务执行完毕"); 42 } 43 44 public void widgetSelected(SelectionEvent e) { 45 stopFlag = false;// 每次都设初值为false 46 new Thread() {// 把后台任务放到一个新开线程里执行 47 public void run() { 48 go(); 49 } 50 }.start(); 51 showProgressDialog();// 打开一个进度框 52 } 53 54 private void showProgressDialog() { 55 IRunnableWithProgress runnable = new IRunnableWithProgress() { 56 public void run(IProgressMonitor monitor) { 57 monitor.beginTask("正在执行中......", 30); 58 int i = 0; 59 while (true) { 60 // 监听是否单击了进度框的“取消”按钮,stopFlag则是监听后台任务是否停止 61 if (monitor.isCanceled() || stopFlag) { 62 stopFlag = true; // 单击了“取消”按钮要设标志位为停止,好通知后台任务中断执行 63 break;// 中断处理 64 } 65 // i到30后清零。并将进度条重新来过 66 if ((++i) == 30) { 67 i = 0; 68 monitor.beginTask("正在执行中......", 30); 69 } 70 // 进度条每前进一步体息一会,不用太长或太短,时间可任意设。 71 try { 72 Thread.sleep(99); 73 } catch (Throwable t) {} 74 monitor.worked(1);// 进度条前进一步 75 } 76 monitor.done();// 进度条前进到完成 77 } 78 }; 79 try { 80 new ProgressMonitorDialog(shell).run(true, true, runnable); 81 } catch (Exception e) { 82 e.printStackTrace(); 83 } 84 } 85 }); 86 87 shell.layout(); 88 shell.open(); 89 while (!shell.isDisposed()) { 90 if (!display.readAndDispatch()) 91 display.sleep(); 92 } 93 } 94 95 }
运行结果如下:
点击cancle按钮,对应的后面没有执行的任务取消.
封装反复显示的进度条对话框
虽然实例2实现了一个不断反复显示的进度框,但它的代码如此凌乱,而且许多代码复生冗长,所以应该把这种不断反复的进度框封装成一个组件,让其使用起来更方便.封装成组件会遇到两个问题:
(1)如何封装?第一个想到的是集成ProgressMonitorDialog来实现新组件.这种方法未尝不可,但这样新组件就和ProgressMonitorDialog类耦合在了一起,如果以后不想用进度条而想改用一个动画,就不容易了.所以还是用组合方式的扩展比较好.实现起来也简单.
(2)封装后分成了多个类.由于boolean是简单类型是值传递.它已经不适合作为进度框和后台任务之间的信使.而Boolean类虽然是引用传递,但它是不可变化类.要改变它要重新创建一个对象.所以他也不能用.因此方案只有两个,用boolean数组,或者创建一个专门的标识类做信使.这里选择第二个方案.
打方案定了,接着开始做具体的实现,首先是这个新组件应该提供什么方法.可以从使用的角度来考虑,经过考虑之后使用方式可以参照ProgressMonitorDialog.具体代码如下所示:
ProgressMonitorDialog3.java
1 import org.eclipse.swt.SWT; 2 import org.eclipse.swt.events.SelectionAdapter; 3 import org.eclipse.swt.events.SelectionEvent; 4 import org.eclipse.swt.layout.RowLayout; 5 import org.eclipse.swt.widgets.Button; 6 import org.eclipse.swt.widgets.Display; 7 import org.eclipse.swt.widgets.Shell; 8 9 10 public class ProgressMonitorDialog3 { 11 12 public static void main(String[] args) { 13 new ProgressMonitorDialog3().open(); 14 } 15 16 public void open() { 17 final Display display = Display.getDefault(); 18 final Shell shell = new Shell(); 19 shell.setSize(500, 375); 20 21 shell.setLayout(new RowLayout()); 22 Button button = new Button(shell, SWT.NONE); 23 button.setText(" GO "); 24 button.addSelectionListener(new SelectionAdapter() { 25 public void widgetSelected(SelectionEvent e) { 26 new LoopProgressDialog(shell).run(true, new IProgressDialogRunnable() { 27 public void run(BooleanFlag stopFlag) { 28 for (int i = 0; i < 10; i++) { 29 System.out.println("第" + (i + 1) + "个任务执行完毕"); 30 if (stopFlag.getFlag()) { 31 System.out.println("被中断了"); 32 return; 33 } 34 try { 35 Thread.sleep(1000); 36 } catch (Throwable t) {} 37 } 38 stopFlag.setFlag(true); 39 System.out.println("全部任务执行完毕"); 40 } 41 }); 42 } 43 }); 44 45 shell.layout(); 46 shell.open(); 47 while (!shell.isDisposed()) { 48 if (!display.readAndDispatch()) 49 display.sleep(); 50 } 51 } 52 53 }
程序说明:LoopProgressDialog是这里的新组件.它的使用看起来非常像ProgressMonitorDialog,区别是:IProgressDialogRunnable是一个我们自己定义的接口,它提供一个run方法.方法的参数是一个用作信使的标志类BooleanFlag.BooleanFlag实际是对boolean的一个封装,和Boolean封装类不同的是它提供了setFlag修改方法.
IProgressDialogRunnable和BooleanFlag的代码比较简单.如下所示:
IProgressDialogRunnable.java
public interface IProgressDialogRunnable { void run(BooleanFlag stopFlag); }
BooleanFlag.java
1 public final class BooleanFlag { 2 private boolean flag = false; 3 4 public BooleanFlag() {} 5 6 public BooleanFlag(boolean flag) { 7 this.flag = flag; 8 } 9 10 public synchronized boolean getFlag() { 11 return flag; 12 } 13 14 public synchronized void setFlag(boolean flag) { 15 this.flag = flag; 16 } 17 }
接下来就是新组件LoopProgressDialog的实现了.
LoopProgressDialog.java
1 import org.eclipse.core.runtime.IProgressMonitor; 2 import org.eclipse.jface.dialogs.ProgressMonitorDialog; 3 import org.eclipse.jface.operation.IRunnableWithProgress; 4 import org.eclipse.swt.widgets.Shell; 5 6 7 public class LoopProgressDialog { 8 private ProgressMonitorDialog dialog; 9 public BooleanFlag stopFlag = new BooleanFlag(); 10 11 public LoopProgressDialog(Shell shell) { 12 dialog = new ProgressMonitorDialog(shell); 13 } 14 15 public void run(boolean cancelable, final IProgressDialogRunnable runnable) { 16 new Thread() { 17 public void run() { 18 runnable.run(stopFlag); 19 } 20 }.start(); 21 22 try { 23 dialog.run(true, cancelable, new LoopRunnable()); 24 } catch (Exception e) { 25 e.printStackTrace(); 26 } 27 } 28 29 private class LoopRunnable implements IRunnableWithProgress { 30 public void run(IProgressMonitor monitor) { 31 monitor.beginTask("正在执行中......", 30); 32 int i = 0; 33 while (true) { 34 // 监听是否单击了进度框的“取消”按钮,stopFlag则是监听后台任务是否停止 35 if (monitor.isCanceled() || stopFlag.getFlag()) { 36 stopFlag.setFlag(true); 37 break;// 中断处理 38 } 39 // i到30后清零。并将进度条重新来过 40 if ((++i) == 30) { 41 i = 0; 42 monitor.beginTask("正在执行中......", 30); 43 } 44 // 进度条每前进一步体息一会,不用太长或太短,时间可任意设。 45 try { 46 Thread.sleep(99); 47 } catch (Throwable t) {} 48 monitor.worked(1);// 进度条前进一步 49 } 50 monitor.done();// 进度条前进到完成 51 } 52 } 53 54 }
用动画GIF来表示进度
ProgressMonitorDialog4.java和ProgressMonitorDialog3.java唯一不同之处就是把LoopProgressDialog改成了动画GIF界面类LoopImageDialog.
LoopImageDialog的内部实现没有采用Dialog或者其子类,而是用Shell来弹出界面的.所以严格来说它不能算是一个对话框.
ProgressMonitorDialog4.java
1 import org.eclipse.swt.SWT; 2 import org.eclipse.swt.events.SelectionAdapter; 3 import org.eclipse.swt.events.SelectionEvent; 4 import org.eclipse.swt.layout.RowLayout; 5 import org.eclipse.swt.widgets.Button; 6 import org.eclipse.swt.widgets.Display; 7 import org.eclipse.swt.widgets.Shell; 8 9 10 public class ProgressMonitorDialog4 { 11 12 public static void main(String[] args) { 13 new ProgressMonitorDialog4().open(); 14 } 15 16 public void open() { 17 final Display display = Display.getDefault(); 18 final Shell shell = new Shell(); 19 shell.setSize(500, 375); 20 21 shell.setLayout(new RowLayout()); 22 Button button = new Button(shell, SWT.NONE); 23 button.setText(" GO "); 24 button.addSelectionListener(new SelectionAdapter() { 25 public void widgetSelected(SelectionEvent e) { 26 new LoopImageDialog(shell).run(true, new IProgressDialogRunnable() { 27 public void run(BooleanFlag stopFlag) { 28 for (int i = 0; i < 10; i++) { 29 System.out.println("第" + (i + 1) + "个任务执行完毕"); 30 if (stopFlag.getFlag()) { 31 System.out.println("被中断了"); 32 return; 33 } 34 try { 35 Thread.sleep(1000); 36 } catch (Throwable t) {} 37 } 38 stopFlag.setFlag(true); 39 System.out.println("全部任务执行完毕"); 40 } 41 }); 42 } 43 }); 44 45 shell.layout(); 46 shell.open(); 47 while (!shell.isDisposed()) { 48 if (!display.readAndDispatch()) 49 display.sleep(); 50 } 51 } 52 53 }
LoopImageDialog.java
1 import org.eclipse.swt.SWT; 2 import org.eclipse.swt.graphics.ImageLoader; 3 import org.eclipse.swt.layout.FillLayout; 4 import org.eclipse.swt.widgets.Display; 5 import org.eclipse.swt.widgets.Shell; 6 7 import cn.com.chengang.swt.SWTUtils; 8 9 public class LoopImageDialog { 10 private Shell parentShell; 11 public BooleanFlag stopFlag = new BooleanFlag(); 12 13 protected LoopImageDialog(Shell parentShell) { 14 this.parentShell = parentShell; 15 } 16 17 public void run(boolean cancelable, final IProgressDialogRunnable runnable) { 18 new Thread() { 19 public void run() { 20 runnable.run(stopFlag); 21 } 22 }.start(); 23 showDialog(); 24 } 25 26 private void showDialog() { 27 final Display display = Display.getDefault(); 28 Shell shell = new Shell(parentShell); 29 shell.setSize(250, 180); 30 SWTUtils.setCenter(shell); // 使用shell居中 31 shell.setLayout(new FillLayout()); 32 // 在Shell窗口显示一个动画GIF 33 final ImageLoader imageLoader = new ImageLoader(); 34 imageLoader.load("icons/animation2.gif"); 35 new AnimationGIF(shell, SWT.BORDER, imageLoader); 36 // 弹出窗口。Shell和Dialog不同,它在open弹出界面之后会继承执行下面的语句 37 shell.open(); 38 while (!shell.isDisposed()) { 39 if (stopFlag.getFlag()) 40 shell.dispose(); 41 if (!display.readAndDispatch()) 42 display.sleep(); 43 } 44 stopFlag.setFlag(true); 45 } 46 }