首页 新闻 论坛 群组 Blog 文档 下载 读书 Tag 网摘 搜索 开源 FAQ 第二书店 博文视点 程序员
频道: 研发 数据库 中间件 信息化 视频 .NET Java 游戏 移动 服务: 人才 外包 培训
    图书品种:235680
       
热门搜索: ASP.NET Ajax Spring Hibernate Java

在上一章中,介绍了一个完整的插件的开发,相信读者已经掌握了插件开发的基本知识。不过我们只是介绍了每一步怎么去做,却没有介绍为什么要这么做以及相关的知识点。本章将会把插件开发相关的常用知识学习一遍。插件开发涉及到的知识是非常多的,这些知识都可以通过阅读Eclipse的帮助文档以及阅读开源项目的代码获得,所以这里没有必要去介绍每一个细节,我们只会把知识点介绍一下,读者无须强行记忆,只要知道这一章是做什么的,并且遇到问题时能去查询相关的资料就可以了。

3.1  程序界面的基础——SWT/JFace

AWT、Swing是Java标准库中的图形化界面框架,但是由于其在性能、稳定性、美观性等方面有很多问题,导致用Java开发出来的成熟GUI项目非常少,而且即使是成熟的Java GUI系统,比如JBuilder、NetBeans等的界面也是遭人诟病的。在IBM开始开发Eclipse的时候,开发人员都强烈反对使用Swing开发Eclipse,因此IBM决定采用Smalltalk GUI的实现方式来开发一个新的Java界面框架,这个框架也就是SWT。

与AWT/Swing不同,SWT底层采用的是JNI native调用本地操作系统的API,只有本地操作系统实现不了的部件才去自己绘制,因此SWT的效率是非常高的。

经过不断成熟和发展,SWT现在已经成为与Eclipse无关的开发包了,也就是用SWT做出来的程序只要没有调用Eclipse平台的东西,那么它就可以脱离Eclipse运行。由于本章中讲到的SWT界面都是运行在Eclipse中的,因此我们不再介绍单独运行SWT程序的方法。

SWT是对操作系统GUI API的封装,因此没有做更多应用层次的封装,比如要显示一个对话框,就要自己去画【确定】、【取消】按钮,要弹出消息对话框就要自己去写数行代码。为了简化SWT的开发,IBM开发出了JFace,JFace不是与SWT格格不入的,JFace就是调用SWT实现了更多实际应用开发中要用到的公共类。SWT和JFace的关系就像Windows开发中Windows SDK和MFC的关系一样,我们在开发的时候应当尽量去使用JFace的东西,只有当JFace的东西不满足我们要求的时候才去直接求助SWT。

3.1.1  SWT的类库结构

SWT的所有类都在org.eclipse.swt包下。最重要的类就是Widget,它是所有界面对象的基类,类图如图3.1所示。

Widget的直接子类有Caret(插入光标)、Menu(菜单)、ScrollBar(滚动条)、Tray(系统托盘图标)等。Widget的子类Item下的类是一些无法独立于其他部件的部件,比如MenuItem(菜单项)、TableItem(表格项)、TrayItem(系统托盘图标项)、TreeItem(树项)等。Widget的子类Control是一个比较庞大的基类,大部分SWT部件都在此类下,其直接子类有Button(按钮)、Label(标记)、ProgressBar(进度条)等。Control的子类Scrollable是所有可以带滚动条的对象的基类,比如Text(文本框)、List(列表框)等。Scrollable的子类Composite是SWT中一个重要的类,它是所有可以容纳其他部件的类的基类,其子类有Browser(浏览器)、Combo(下拉列表框)、Group(组合框)、Table(表格)、Tree(树)等。

图3.1  SWT的类结构图

上面从类层次的角度研究了SWT的类结构,下面再来看一下SWT的包结构:

l   org.eclipse.swt下有SWT,SWTException和SWTError类。SWT中定义了SWT中的公共常量,包括部件风格、消息常量等;SWTException和SWTError则是SWT中异常的基类。

l   org.eclipse.swt.widgets包下定义了常用、核心SWT窗口小部件(widget)的公有API类定义。如Display、Shell、Button、Menu等。一般编写GUI程序用到的Widget大部分都在这个包下。

l   org.eclipse.swt.events包中提供了对SWT事件监视器(Event Listener)的支持,如Button的SelectionListener、Mouse的MouseListener、MouseMoveListener和MouseTrackListener等,还有与这些Listener对应的Adapter实现类和 Event类。

l   org.eclipse.swt.layout包中定义了SWT的布局管理器,其中有FillLayout、GridLayout和RowLayout三种。

l   org.eclipse.swt.graphics包中包含了SWT中graphic类,如Color、Font和Image等,这个包下的类的资源管理方式和其他部件略有不同,3.1.2节中将会介绍。

l   org.eclipse.swt.printer提供了对打印的支持。

l   org.eclipse.swt.custom包中包含了一些可自定义的窗口小部件,它们是学习开发自定义SWT部件的很好的例子。

l   org.eclipse.swt.dnd提供了对拖放操作的支持。

3.1.2  SWT中的资源管理

AWT/Swing的资源管理使用的是Java提供的垃圾回收机制,但是由于GUI是非常消耗资源的,要求对象不被使用的时候立即被回收,而垃圾回收机制是无时间保证的。这对系统资源的处理会是致命的,比如程序在一个循环语句中去加载数万张图片,对其进行加盖印章处理后再进行保存,常规的处理方式是每次调入一张,修改保存,然后就立即释放该图片资源,而后再循环调入下一张图片,这对操作系统而言,任何时刻程序占用的仅仅是一张图片的资源。但如果资源管理完全交给垃圾回收机制去处理,也许会是在循环语句结束后,JVM才会去释放图片资源,其结果可能是你的程序还没有运行结束,系统就已经内存溢出了。而SWT则创新性地抛弃了Java提供的垃圾回收机制,让资源由开发者进行生命周期管理,即显式地释放已经分配的任何操作系统资源(调用dispose方法释放资源)。其法则就是:①如果您创建对象,则您必须销毁它;②父部件被销毁,子部件也同时被销毁。具体实施起来有如下规则:

l   如果使用构造函数来创建图形对象或窗口小部件,使用完时必须显式地将其销毁。

l   当调用一个包含子部件的部件(即Composite的子类)的dispose方法时,将递归地调用其所有子部件的dispose方法。因此无需手动去释放这些子部件的资源。

l   如果部件不是您创建的,而是调用其他类的某个方法得到的,则不要将其除去,这是因为它不是您创建的。

很多开发人员在看到“父部件被销毁,子部件也同时被销毁”这一条的时候就认为自己又不用去管理资源的释放工作了,因为只要销毁根部件,那么所有的子部件就都会被销毁了。这是十分危险的误解,因为SWT中还有一部分资源不是继承自Widget的,也就是不可能有父部件,这样就需要程序员手动去释放资源。例如org.eclipse.swt.graphics下的类,这些类都继承了Resource类,Resource中也定义了dispose方法。这些类有Color(颜色)、Cursor(鼠标指针)、Font(字体)、GC(图形设备设置)、Image(图片)等。比如您为按钮设定了一种字体,那么必须在销毁这个按钮的时候手动去释放字体对象。

当然并不是所有的SWT对象都需要程序员去释放的,比如org.eclipse.swt.graphics包下的Point(点)、Rectangle(矩形)、RGB等类是没有dispose方法的,因此只有把它们交给JVM的垃圾回收机制去管理了。

3.1.3  在非用户线程中访问用户线程的GUI资源

在非用户线程中对用户线程的GUI资源进行访问的时候,如果不进行同步的话就会造成不可预料的问题。AWT/Swing中并没有强制在非用户线程中访问用户线程的GUI资源的时候要进行同步,而SWT则进行了同步控制,这样就可以预防这些不可预料的问题。在SWT中,通常存在一个被称为“用户线程”的唯一线程,只有在这个线程中才能调用对组件或某些图形API的访问操作。如果在非用户线程中程序直接调用这些访问操作,那么SWTExcepiton异常会被抛出。

下面看一个例子:

Runnable r = new Runnable() {

    public void run()

    {

        for (int i = 0; i < 100; i++)

        {

            try

            {

                wait(1000);

            } catch (InterruptedException e)

            {

            }

            text.setText(new Integer(i).toString());

        }

    }

};

我们启动一个线程,在这个线程中,每隔一秒为界面文本控件赋值一次,运行后就会抛出SWT异常。

解决这个问题的方法也是非常简单的,那就是通过Display类的syncExec(Runnable)和asyncExec(Runnable)这两个方法去实现:

Runnable r = new Runnable() {

    public void run()

    {

        for (int i = 0; i < 100; i++)

        {

            try

            {

                wait(1000);

           } catch (InterruptedException e) { }

            final int j = i;

            display.asyncExec(new Runnable() {

                public void run()

                {

                    text.setText(new Integer(j).toString());

                }

            });

        }

    }

};

方法syncExec()和asyncExec()的区别在于前者要在指定的线程执行结束后才返回,而后者无论指定的线程是否执行都会立即返回到当前线程。

3.1.4  访问对话框中的值

在程序中经常会弹出一些对话框,要求用户输入一些值,然后根据用户输入的值来进行后续操作。比如在程序中弹出一个对话框要求用户输入姓名和国家,然后根据输入的值显示问候语。

定义SettingDialog类:

public class SettingDialog extends Dialog

{

    private Text txtName;

    private Text txtCountry;

    ...

    public String getName()

    {

        return txtName.getText();

    }

   

    public String getCountry()

    {

        return txtCountry.getText();

    }

   

}

主界面:

SettingDialog dlg = … ;

dlg.open();

if(dlg.open()==Window.OK)

{

    MessageDialog.openInformation(shell, "", dlg.getName()+" is from "+

dlg.getCountry());

}

当运行的时候就会抛出如下异常:

org.eclipse.swt.SWTException: Widget is disposed

这是为什么呢?

让我们来看一下org.eclipse.jface.window.Window类,它是SettingDialog的间接父类,在Window类的close方法中将界面控件全部销毁掉了,当我们关闭一个界面的时候就调用了close方法,这样当窗口已经关闭的时候,我们再去调用dlg.getName()的话,getName方法就会去访问txtName控件,可是txtName已经被销毁掉了,不能被访问了,所以就抛出了Widget is disposed这个异常消息。

那么我们应该怎么修改呢?既然不能在窗口关闭以后访问界面控件对象,那么只有在关闭之前来把要访问的控件值提前保存起来了。做如下修改:

public class SettingDialog extends Dialog

{

    private Text txtName;

    private Text txtCountry;

    private String name;

    private String country;

    ...

    protected void okPressed()

    {

        name = txtName.getText();

        country = txtCountry.getText();

        super.okPressed();

    }

    public String getName()

    {

        return name;

    }

    public String getCountry()

    {

        return country;

    }

}

当单击【确定】按钮的时候,okPressed方法会被调用,我们在调用父类的okPressed之前将控件的值保存起来就可以了,并且改写了get方法,让它返回我们保存的值。

3.1.5  如何知道部件支持哪些style

SWT中的部件的构造函数都有一个int style参数,这个参数表示要创建的部件的风格。

这个参数的类型是整数而非枚举,那么如何确定它支持哪些值呢?答案就是查看JavaDoc。以Text部件为例,查看Text的源码,定位到它的构造函数处,如图3.2所示。

图3.2  构造函数的JavaDoc

JavaDoc中明确地指出支持如下的风格:

SWT.SINGLE、SWT.MULTI、SWT.READ_ONLY、SWT.WRAP。

SWT中定义的常量都是掩码形式的,比如:

public static final int MULTI = 1 << 1;     //即二进制的10

public static final int WRAP = 1 << 6;      //即二进制的1000000

可以用“或”操作符来进行风格的组合,比如指定文本框为多行并且自动换行,只要如下调用即可:

Text txt = new Text(parent,SWT.MULTI|SWT.WRAP);

查看所有评论(0)条】

最近评论



正在载入评论列表...
热点评论