Eclipse SWT/JFace核心应用——SWT多线程程序设计

Eclipse SWT/JFace核心应用——SWT多线程程序设计
2008-08-08 9:55
转自:http://book.csdn.net/bookfiles/304/10030412715.shtml
参考:(1)http://www.ibm.com/developerworks/cn/opensource/os-cn-eclipse-multithrd/index.html
(2)http://liusu.blog.ccidnet.com/blog-htm-do-showone-uid-2571-itemid-104334-type-blog.html

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方法来设置和访问类的属性,又将常用的操作封装为方法,这才是涉及结构合理的类。

小结:
对SWT程序中特有的线程——UI线程由Display对象负责管理,是SWT程序运行的核心。最值得注意的是,如果在 非UI线程中调用界面上的控件对象是会在运行期间报错,所以Display对象使用asyncExec(Runnable runnable)和syncExec(Runnable runnable)方法更新界面的控件对象。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值