SWT中的UI线程
SWT作为一种桌面程序,比普通的Java程序要多一个UI线程,UI线程负责不断地画出显示的UI控件,当 然这个UI线程还要负责事件的处理。什么是事件呢?例如单击按钮或是按下键盘,系统都会生成一个事件放在事件队列中,即接下来UI线程按顺序处理队列中的 事件。SWT中Display对象就是一个UI线程,并且负责管理队列中的事件。
以下代码,读者并不陌生,在之前使用的SWT程序中都用到过,下面来仔细分析一下它的详细情况。
//当窗口未释放时
while (!shell.isDisposed()) {
//如果display对象事件队列中没有了等待的事件,就让该线程进入等待状态
if (!display.readAndDispatch())
display.sleep();
}
可以这样理解UI线程:当程序启动后,如果用户不进行任何操作,该UI线程就进入了等待状态。一旦触发了某个 事件,比如说单击了某个按钮或是按下了键盘上的按键,这时在事件队列中就等待了一个事件,此时UI线程就处理队列中的事件,直至队列中的事件全部处理完 毕,又恢复了睡眠状态。处理事件的过程也就是响应用户操作的过程。
这就产生了一个问题,当某一个队列中的事件是一个非常耗时的事件时,比如说是检索文件或者是查询大量数据的数 据库时,这时,用户就需要长时间的等待。所以在这种情况下,就需要为长时间处理的程序单独开辟出一个线程来执行,不影响UI线程继续处理其他事件了,这样 给用户的感觉就是,虽然后台运行着程序,但也不会影响界面上的操作。
11.3 其他线程访问UI线程
理解了线程的基本知识,再重新看一下9.7节中进度条的示例程序。这个程序是在开始运行窗口时就在后台启动了一个线程,这个后台线程每隔0.1秒更新运行一次设置滚动条的值。可以看到在run()方法体中有一段代码读者可能有疑问,重新加注释后的代码如下:
//创建一个线程,该线程每0.1秒更新一次滚动条的值
Runnable runnable = new Runnable() {
public void run() {
//线程运行的主体
for (int i = minimus; i < maximum; i++) {
try {
//让线程睡眠0.1秒
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//如果使用以下一行代码更新滚动条的值,运行时会出现Invalid thread access运行错误
//progressBar.setSelection(progressBar.getSelection() + 1);
//让UI线程更新滚动条的值
display.asyncExec(new Runnable() {
//这也是一个线程,该线程的功能是更新滚动条的值,一瞬间就结束了
//并且这个线程是被UI线程调用的
public void run() {
if (progressBar.isDisposed())
return;
progressBar.setSelection(progressBar.getSelection() + 1);
}
});
}
}
};
//启动这个线程
new Thread(runnable).start();
在设置滚动条的progressBar的值时,为什么不能直接使用 progressBar.setSelection (progressBar.getSelection() + 1)代码来直接设置滚动条的值呢?这是因为滚动条对象是UI界面上的控件,它是由UI线程创建的。若要访问UI界面上的对象必须通过UI线程来访问,就是 说在非UI线程中调用UI对象是不允许的,这是出于线程安全的考虑。正因为如此,只能通过另一种方式来更新进度条的值,解决方案就是需要再开辟一个线程, 专门更新滚动条的值,这个线程交给UI线程来调用。
Display对象中负责调用其他线程的方法有以下3种:
● asyncExec(Runnable runnable):异步启动新的线程。所谓异步就是,UI线程不会等待runnable对象执行结束后再继续进行,就是说UI线程可以和runnable对象所在的线程同时运行。
● syncExec(Runnable runnable):同步启动新的线程。所谓同步就是,UI线程会等待runnable对象执行结束后才会继续进行,当runnable对象是耗时大的线 程时,尽量不要采用此种方式。另外,对于该种方式创建的线程可通过getSyncThread()方法获得线程对象。
● timerExec(int milliseconds,Runnable runnable):指定一段时间再启动新的线程。用此方法创建的线程,将会在指定的时间后再启动线程。当然用此方法创建的线程启动后,与UI线程是异步 的。如果指定的时间为负数,将不会按时启动线程。
另外Display对象中,与UI线程相关的方法如下所示:
● 获得当前的UI线程对象的方法:getThread(),返回Thread对象。
● 使UI线程处于休眠状态:sleep()。
● 唤醒UI线程:wake()。
(1)编写一个MutiTaskTestDrive类,作为该系统的入口。该类比较简单,代码如下:
MutiTaskTestDrive.java
package com.fengmanfei.ch11;
import org.eclipse.swt.widgets.Display;
import com.fengmanfei.util.ImageFactory;
public class MutiTaskTestDrive {
public static void main(String[] args) {
Display display = Display.getDefault();
MutiTaskGUI mutiTask= new MutiTaskGUI();
mutiTask.getShell().open();
while (!mutiTask.getShell().isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
ImageFactory.dispose();
display.dispose();
}
}
(2)MutiTaskGUI类为主窗口类,也就是放置表格的窗口,该类具体的代码如下:
MutiTaskGUI.java
package com.fengmanfei.ch11;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
public class MutiTaskGUI {
private Shell shell = null;
private Table table = null;
public MutiTaskGUI( ){
//构造方法中调用初始化窗口的方法
init();
}
//初始化窗口方法
public void init() {
shell = new Shell();
shell.setLayout(new GridLayout());
shell.setText("多线程");
Button bt = new Button ( shell , SWT.NONE);
bt.setText("开始一个任务");
// 创建表格对象
table = new Table(shell, SWT.BORDER);
table.setLayoutData( new GridData(SWT.FILL,SWT.FILL,true,true));
table.setHeaderVisible(true);
table.setLinesVisible(true);
String[] header = new String[]{"任务","进度","操作"};
// 创建表头
for (int i = 0; i < 3; i++) {
TableColumn col = new TableColumn(table, SWT.NONE);
col.setText( header[i] );
}
//设置表头宽度
table.getColumn(0).setWidth(80);
table.getColumn(1).setWidth(150);
table.getColumn(2).setWidth(80);
shell.pack();
//注册创建任务按钮事件
bt.addSelectionListener( new SelectionAdapter(){
//当单击创建一个任务按钮时
public void widgetSelected(SelectionEvent e) {
//首先创建一个Task对象
Task task = new Task ( table );
//然后在表格中添加一行
task.createTableItem();
//最后启动该任务,该任务为一个线程
task.start();
}
});
}
//获得和设置属性的getter和setter方法
public Shell getShell() {
return shell;
}
public void setShell(Shell shell) {
this.shell = shell;
}
public Table getTable() {
return table;
}
public void setTable(Table table) {
this.table = table;
}
}
该类需要注意的地方是,“开始一个任务”按钮事件的处理。当单击该按钮时,就创建一表格中的一行,并且启动一个线程。添加表格中的一行和启动线程是使用Task对象来完成的。
(3)Task类继承自Thread,并覆盖了run()方法,具有线程的特性。Task类具体实现的代码如下:
Task.java
package com.fengmanfei.ch11;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.TableEditor;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.ProgressBar;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableItem;
import com.fengmanfei.util.ImageFactory;
//该Task类继承自Thread,并且覆盖了run()方法
public class Task extends Thread {
//该类的一些属性
private Table table = null;
//是否停止的标志
private boolean done = false;
//声明进度条对象
private ProgressBar bar = null;
private int min = 0;
private int max = 100;
public Task(Table table) {
this.table = table;
}
//创建表格中的一行
public void createTableItem() {
TableItem item = new TableItem(table, SWT.NONE);
item.setText(this.getName());
item.setImage(ImageFactory.loadImage(table.getDisplay(), ImageFactory.PROGRESS_TASK));
// 创建一个进度条
bar = new ProgressBar(table, SWT.NONE);
bar.setMinimum(min);
bar.setMaximum(max);
// 创建一个可编辑的表格对象
TableEditor editor = new TableEditor(table);
editor.grabHorizontal = true;
editor.grabVertical = true;
// 将进度条绑定到第二列中
editor.setEditor(bar, item, 1);
//重新创建一个可编辑的表格对象
editor = new TableEditor(table);
editor.grabHorizontal = true;
editor.grabVertical = true;
//创建一个按钮
Button stop = new Button(table, SWT.NONE);
stop.setText("Stop");
editor.setEditor(stop, item, 2);
stop.addSelectionListener(new SelectionAdapter() {
//当停止按钮按下时,设置停止标记true
public void widgetSelected(SelectionEvent e) {
if (!isDone())
setDone(true);
}
});
}
//线程方法体,与前面单个的进度条的程序类似
public void run() {
for (int i = min; i < max; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
table.getDisplay().asyncExec(new Runnable() {
public void run() {
if (bar.isDisposed())
return;
bar.setSelection(bar.getSelection() + 1);
}
});
//如果停止,则结束该线程
if (isDone()) {
break;
}
}
}
//获得和设置属性的getter和setter方法
public Table getTable() {
return table;
}
public void setTable(Table table) {
this.table = table;
}
public boolean isDone() {
return done;
}
public void setDone(boolean done) {
this.done = done;
}
}
Task类中的run()方法体中的代码与之前单个进度条的处理方式类似。从以上的程序代码中可以看出,类中大量使用了bean的方式,也就是通过一些getter和setter方法来设置和访问类的属性,又将常用的操作封装为方法,这才是涉及结构合理的类。
小结: