SWT/JFace(一)——概述
一、SWT
1. 概述
SWT(Standard Widget Toolkit,标准图形工具箱)是一种用java开发的GUI程序的技术。与出自Sun的AWT (Abstract Widget Toolkit,抽象图形工具箱)和Swing不同,SWT是Eclipse的开发人员自行建造的。它无论在界面美观度上还是在性能上都远远超越了AWT和Swing,即使和操作系统上的本地图形界面程序相比也毫不逊色,这都源自于SWT与众不同的设计理念。
SWT技术是第一套基于Java的第三方图形工具库.它的设计思想是提供一套通用的API,使得开发出的图形程序不仅可以不加修改地在平台间移植,而且在外观上和速度上与使用C/C++等语言在操作系统平台上开发出来的本地图形程序毫无差别,还可以使用鼠标拖放操作、系统托盘等高级的系统服务。这些功能的实现,归功于它与众不同的体系架构。
要实现本地化的效果,程序一定要与本地图形操作系统发生交互,在与操作系统交互时,SWT使用了JNI技术。JNI (Java Native Interface)是Sun公司为Java语言设计的用来与C/C++程序交互的技术。简单来说,可以将它理解成将Java语言编写的接口和C语言编写的函数绑定,从而使得调用 Java接口就等于调用C函数的技术。使用JNI,也可以在C代码中反向操作Java代码的内容。
2. SWT结构浅析
SWT的基本体系结构共分三层:
- 第一层是SWT的API。外部开发者使用SWT时就是使用这些API编写SWT程序。这里包含了 SWT的控件(文本框,按钮等)、事件处理(各种事件及监听器)等和图形界面开发者关系最为密切的部分,同时也只有这部分的代码对外部开发者可见。底层C语言风格的API,在这一层上被封装为对象供外部开发者使用,但在封装的具体实现代码上,对于不同系统平台会有微小的差别。比如Win32平台的SWT在打开一个窗口时会针对WinCE和WindowsXP的特殊情况做不同的处理,而 Linux GTK图形平台的SWT则没有这段代码。但从整体来看,这部分代码在不同平台上基本相似。
- 第二层是JN1相关的代码。毎个操作系统提供的API都拥有自己定义的一系列独特的数据类型作为参数类型及返回值。这一层的代码除包含对应到c函数的Java接口外,还有这些特殊数据类型用 Java的实现。这一层的代码只在SWT内部可见,虽然也是Java编写,但其内容在每一种SWT版本中都是完全不同的。
- 第三层是使用C语言编写的操作系统本地动态链接库文件。这些库文件将操作系统上和图形界面相关的C语言的API封装成Java可以调用的形式。这一层的代码全部是用C语言编写的,在 Windows平台上编译成DLL文件,而在Linux平台上则编译成so文件。
3. API结构
3.1 组件类
组件是用户界面的组成部分。在一个SWT程序中,界面上的所有内容,如按钮、文本框等,包括窗口本身都是组件。整个图形界面是由组件构成的。SWT提供了数十种组件供开发者使用。了解有哪些组件可以使用,以及懂得它们各自适用在什么情况下,是学习SWT编程的基础。
组件分为**控件(Control)和项目(Item)**两大部分。控件是界面的主体,项目依赖于控件并为它们提供附加的功能。标签控件可以用来显示文字,按钮可以让用户单击以启动一个功能文本框控件可以显示文字,也可以让用户输入文字;单选按钮和复选框允许用户在提供的选项中做出选择,工具栏和菜单栏可以用来显示一些常用的操作选项以方便用户使用。
对话框通常是比较简单的窗口,可以用来向用户显示一些提示或警告信息。在SWT中,还可以使用系统内置的文件对话框、颜色对话框、打印对话框等功能。
有些控件是专门用来容纳、分组其他控件的,它们被称为容器控件。如窗口控件就属于容器控件。
如果需要显示一系列的数据集合,可以使用列表(List)或表格(Table),如果需要显示层次结构,可以使用**树(Tree)**控件。
3.2 布局类
另外一类和页面显示关系紧密的部分是布局类(Layout),它主要是由**布局管理器(Layout Manager)**组成。当一个布局管理器被安装到窗口上之后,它就负责着在窗口中安排控件的位置和尺寸。当窗口尺寸变化时,布局管理器会根据一定的策略重新计算这些数据,并将控件安排在适应窗口新尺寸的位置上。不同的布局管理器,就代表了安放控件的一种不同策略。
布局管理器将窗口的布局设计与窗口中控件的具体业务逻辑分离开来,使得编程工作的模块划分更为清晰,也使得程序员可以根据需要动态地改变一个窗口中控件的摆放方式。
SWT中内置了许多布局管理器,在日常开发中,这些布局可以满足绝大多数需要。如果有特殊需求,还可以自己编写布局管理器。
3.3 事件类
事件类主要负责控件的消息传递。用户单击了界面上一个按钮,在文本框中输入一段文字,或者仅仅移动一下鼠标,都会产生一个事件(Event)。事件是图形界面程序与用户交互的核心。SWT的事件处理机制采用了Java 1.1中提出的事件监听器(Event Listener)的结构,即使用Observer (观察者)的设计模式来处理事件。对某个对象上的某个事件感兴趣的话,就需要在对象上注册一个对应事件的监听器。当事件发生时,所有在对象上注册过的此事件监听器都会收到通知。
监听器是一个接口,其中包含的每个方法都对应了某种事件的发生,而其参数则包含了所发生的 事件的上下文信息。以鼠标事件MouseEvent为例,它对应的监听器是MouseListener,其中包括三个 方法,如下面代码所示。从名字可以看出,三个方法分别对应了鼠标双击、鼠标按下和鼠标放开的事 件,而其参数MouseEvent类,则包含了鼠标单击位置,哪一个按钮被按下等信息。
public void mouseDoubleClick(MouseEvent e);
public void mouseDown(MouseEvent e);
public void mouseUp(MouseEvent e);
如果希望为浏口中某一个按钮加上双击的处理函数,就要实现一个MouseListener接口,并在其 mouseDoubleClick方法中加上处理代码,然后调用Button类的addMouseListener方法,将这个监听器添加到按钮的监听器队列中。这样当鼠标双击按钮时,mouseDoubleClick方法就会被调用.这里的按钮被称为事件源(Event Source)
使用监听器的优点在于事件源不必关心具体的监听者究竟是谁。这种结构使得模块之间耦合度较低,易于维护和修改。SWT中预定义了许多事件,其中既包含如鼠标事件、键盘事件这种比较低层次的事件,又包括了文本框内容改变、复选框被选中等较为高层次的事件。开发者也同样可以定义自己的事件。
监听器 | 事件名 | 描述 |
---|---|---|
MouseListener | MouseEvent | 监听鼠标按钮按下的事件 |
MouseMoveListener | MouseEvent | 监听鼠标移动的事件 |
MouseTrackListener | MouseEvent | 监听鼠标进入、离开事件源的事件 |
KeyListener | KeyEvent | 监听按键事件 |
ControlListener | ControlEvent | 监听控件尺寸或位置改变的事件 |
DisposeListener | DisposeEvent | 监听控件被销毁的事件 |
FocusListener | FocusEvent | 监听控件得到焦点的事件 |
SelectionListener | SelectionEvent | 监听控件被选中(按钮被单击,复选框被勾选等)的事件 |
3.4 图像类与系统资源管理
图形类隐藏在SWT API的最下层,一般的开发人员很少会直接使用到它的内容。但了解这部分知识是深入理解和学习SWT的基础。图形类主要由两部分构成,即Resource和Display。
编写图形界面程序时,所有控件、图片、字体等图形对象都会耗费系统资源。因为系统上可用的资源总是有限的,为保证这些资源被最大限度地利用,程序必须负责及时释放这些资源。资源在 SWT中由Resource类的子类,如Font、Image等所使用,而分配或释放它们的操作则由Display负责和操作系统交互而完成。Display将不同的操作系统中对事件循环的实现封装成为SWT的统一事件循环机制,它还提供了丰富的方法用来访问操作系统的资源,因此可以将其视为SWT与底层操作系统交流的桥梁。
在SWT中,创建Display实例的线程被称为界面线程(UI Thread) 。界面线程具有以下特性,或者说限制。
- Display的事件循环必须在它的界面线程中执行
- 所有通过Display完成的绘图操作都只能在它的界面线程中执行
- 在同一个界面线程中,不能同时创建两个有效的Display实例
为了理解这些限制,有必要理解GUI图形界面程序运行的基本原理。GUI程序运行的机制就是针对用户在界面上的每一个操作事件做出反应,即所谓“事件驱动”。操作系统为每一个GUI程序分配了一个事件队列,用于存放属于它的事件。每当用户操作鼠标或键盘时,操作系统会根据鼠标单击的位置、当前的焦点窗口等信息决定应该把事件放到哪个队列中。而在GUI程序中,需要用一个循环不停地去读取自己的事件队列,每当检査到有新的事件时,就处理这个事件并做出适当的绘图动作反馈到用户。这就是事件循环(Event Loop) 这个循环不停地将消息传递到GUI程序的各个部分,好 像一个水泵一样,因此也被称为“消息泵”。
在目前的主流操作系统中,对事件循环的处理存在着两种模型。一种是单线程模型,这种模型只允许程序中的一个线程监听事件消息并做出绘图动作,另外一种则是允许程序中的所有线程*访问程序事件队列的多线程模型。为了保持平台兼容性,降低实现的复杂度,SWT使用单线程模型管理事件循环和绘图操作。这也正是存在上面三条限制的主要原因。
注意:试图在非UI线程中对图形界面进行操作不会导致编译错误,但却会在运行时得到一个 “ERROR_THREAD_INVALID_ACCESS” 的SWT异常。
在SWT中,没有系统检源的fl动分配与释放机制,资源的管理是由程序员手动完成的。对于资 源的使用与释放,有如下两条原则。
- 谁分配谁释放(If you created it, you dispose it)
- 释放父资源时,所有子资源会被一起释放(Disposing the parent disposes the children)
二、JFace
JFace是基于SWT的一套图形工具包,它没有为SWT提供任何新的功能,只是将一些较烦琐而且常用的图形操作封装了起来,使得开发工作更简便。JFace完全使用SWT API进行开发,并没有涉及任何SWT中平台相关的部分,所以JFace没有不同平台版本之分。
JFace的雏形是在Eclipse的开发人员编写IDE的图形界面时,为了方便而针对一些常用的图形界面开发模式(如对话框等)开发出来的一系列组件。通常用JFace写四五行代码就可以做到的事情, SWT需要写儿十行甚至百行以上。但JFace并不能完全桥代SWT,因为它只包含少数几个最常用的模式。通常的工作方式是使用SWT完成大部分工作,并在适当的时候使用JFace。为了明确什么是“适当的时候”,就需要了解JFace究竟是对哪些常用操作做了封装。
1. 查看器
査看器(Viewer)是使用MVC模式对一些复杂控件做的封装,以方便开发人员使用这些 控件。目前的査看器包括列表査看器(ListViewer)、表格査看器(TableVicwer)、**树査看器(TreeViewer)**等。
査看器将数据模型从复杂控件中抽象出来,本身作为一个控制器来监听模型的变化和控件的变化,并负责传递这些变化。开发者不需要具体操作那些夏杂控件,而只需要对数据模型进行操作,就可以改变控件显示的内容。当控件的内容在页面上被修改时,査看器收到通知后也会发给数据模型, 数据模型接到通知后可以检査这些修改并决定是否接受。査看器井没有将控件隐蔵起来,开发者可以随时通过査看器提供的访问方法(如TableViewer的getTable方法,TreeViewer的getTree方法等)得到对应的底层控件对象并直接操纵它。
2. 资源注册表
使用SWT图形资源的一个原则就是“谁申请谁释放”,然而,正如许多C++程序员经常忘记释放数组空间而导致内存泄漏一样,当程序结构复杂时,要求严格执行这一点不是一件容易的工作。资源注册表就是为了方便管理常用的图形资源而设计的,目前JFace包含了针对字体、颜色和图像资源的注册表。
JFace在JFaceResources类中为这些注册表提供了采用Singleton模式的实现。使用这些资源注册表,就可以有效地避免因为忘记样放资源而导致的系统资源泄漏问题。
3.字段帮助
字段帮助(Field Assist)的目的是为最终用户提供界面指导。在设计用户界面时,程序员通常需要为用户提供一些指导信息,例如哪些字段是必填的,字段可以接受的数值类型及取値范围等的说 明。字段帮助功能可以简化这一工作。字段帮助提供两种功能,一种是字段装饰(Decoration Field),另一种是内容建议(Content Proposal)。
字段装饰可以让开发者在控件的四角添加图片或颜色作为装饰,以提示用户字段的当前状态,例如用户输入错误时,可以显示一个错误图标,必填字段没有填写时,可以显示一个警告图标等。与査看器相同,开发者随时可以通过字段装饰访问底层的控件并直接操作它。
内容建议则允许开发者在控件附近弹出一个下拉框,并在其中提供一些内容选项供用户选择。这个提示框可以由焦点触发,也可以设置成由某个热键触发。
4. 操作与贡献
操作和贡献(Action & Contribution)是用来定制菜单和工具栏的一套机制。这套机制将菜单项/工具栏按钮和它们所触发的事件分离开来,使得对用户操作响应的控制更灵活。
Action对象封装了一个操作命令。当Action对象被添加到工具栏或菜单上面时,用户单击对应的工具栏按钮和菜单项就可以触发这个Action对象所代表的命令。为了可以显示在工具栏或菜单上, Action对象中还包含有图标、工具提示(tooltip)等用于显示的信息。
贡献由**贡献项目(Contribution Item)和贡献管理器(Contribution Manager)**两部分组成。 操作可以用来在菜单或工具栏上添加项目,而利用操作中包含的图标等信息来显示工具栏按钮或菜单项的匸作,是由贡献项目完成的。贡献管理器是包含了多个贡献项目的集合。贡献项目代表了菜单或 工具栏中的一项,贡献管理器则代表了整个菜单或工具栏。JFace的贡献管理器可以用来控制菜单.、 工具栏和状态栏。贡献管理器会通过其中包含的贡献项目的信息自动调整这些控件。
5. 对话框、向导页和偏好设置
在SWT的对话框控件基础上,JFace实现了许多定制的对话框。如带有图标的对话框、可以在系统托盘中显示的对话框、供用户输入内容的对话框等。
**向导页(WizardPage)是用来引导用户一步一步完成某个操作的界面;而偏好设置(Preference)**可以用来设计专业的用户选项卡。JFace的框架帮助开发者完成了页面切换的工作,使开发者只需要编写每一页的内容并将它们添加到框架中就可以实现向导页或偏好设置的功能。这两部分内容在Eclipse IDE中广为使用。在Eclipse中创建一个Java工程时,使用的就是向导页。
6. 数据绑定
数据绑定(Data-binding)是在JFace 3.2版中提出的概念,其实这只不过是把査看器的概念扩展到了整个界面。査看器是为了方便使用复杂控件而将其数据模型抽象出来,数据绑定技术就是将整个图形界面(一个窗口、一个对话框等)看做了一个复杂控件,并将它关联到了一个数据模型上。数据绑定功能负责监听界面上所有控件和数据模型的内容,无论哪一方发生变化时都即时通知另一方。
使用数据绑定技术,开发者在编写完图形界面,并创建绑定后,数据绑定就会负责根据变化自动同步数据模型和界面视图的内容。当开发者需要取得用户输入时,可以直接访问数据模型的对应部分并获取所关心的信息,而不需要每次都编写访问界面控件的代码,当程序要将数据展现给用户时,也只需要更新数据模型就可以了。这减轻了开发者重复编写界面访问代码的工作,从而提高了开发的效率。