定制SWT部件
定制SWT部件
Translated By xiaotaoliang 04.12
偶尔你会发现SWT提供的控件都不能满足应用程序的需要,在这种情况下,你可能想要通过实现自定义的部件来扩展SWT。SWT本身提供了一个包,org.eclipse.swt.custom,其包含的定制控件就不属于SWT控件核心集,但却是实现工作台平台所必需的。
控件
|
目的
|
样式
|
事件
|
CCombo
|
类似Combo控件,但通过定制绘制以允许使用没有边框的组合框,开发这个类是为了在表格的单元格中使用组合框。
|
BORDER, FLAT, READ_ONLY
|
Dispose, Control*, Selection
|
CLabel
|
类似Label类,但支持可省略的文本剪辑,还支持背景色的渐变效果,这种效果在活动的工作台视图中也能看到。不支持文本自动换行。
|
CENTER, LEFT, RIGHT, SHADOW_IN, SHADOW_OUT, SHADOW_NONE
|
Dispose, Control*
|
CTabFolder
|
类似TabFolder类,但支持增强的可视化标签配置(顶部或底部)和边框。
|
BORDER, FLAT, BOTTOM, TOP
|
Dispose, Control*, Selection
|
CTabItem
|
可被选择的用户界面对象,对应一个CtabFolder对象中的标签页
|
|
Dispose, Control*
|
SashForm
|
复合控件,可以把子控件安排成行或列的布局并使用框格分隔,用户也因此可以改变其大小。
|
BORDER, HORIZONTAL, VERTICAL
|
Dispose, Control*
|
ScrolledComposite
|
复合控件,可以卷动其中的内容,可选地,可以拉伸其内容以填充有效的空间。
|
BORDER, H_SCROLL, V_SCROLL
|
Dispose, Control*
|
StyledText
|
可编辑的控件,允许用户输入文本。控件中的文本段可以有指定的颜色(前景色或背景色)和字体样式(粗体或规则体)
|
BORDER, FULL_SELECTION, MULTI, SINGLE, WRAP, READ_ONLY
|
Dispose, Control*, ExtendedModify, LineGetBackground, LineGetSegments, LineGetStyle, Modify, Selection, Verify, VerifyKey
|
TableTree
|
可被选择的控件,显示用户可以选中的有继承层次的项目列表,项目呈现为具有多列的行,每列显示项目的不同属性。
|
BORDER, SINGLE, MULTI, CHECK, FULL_SELECTION
|
Dispose, Control*, Selection, DefaultSelection, Collapse, Expand
|
TableTreeItem
|
可被选择的用户界面对象,代表一个TableTree对象中的一个在继承层次中的树节点。
|
|
|
ViewForm
|
复合控件,安排成水平的三个子控件的布局,允许程序控制布局和边框参数。用于在工作台中实现视图的“标签、工具条、菜单”的组合条。
|
BORDER, FLAT
|
Dispose, Control*
|
备注:Control* = 从Control类继承下来的方法: FocusIn, FocusOut, Help, KeyDown, KeyUp, MouseDoubleClick, MouseDown, MouseEnter, MouseExit, MouseHover, MouseUp, MouseMove, Move, Paint, Resize
在实现定制的部件之前,你要考虑几个重要问题:
- 是否存在现成的SWT控件,可以作为你想要的控件的简化或普通的版本?如果存在,考虑使用现成的控件并结束你的应用程序的开发。你以后总会有机会写一个与简化版本的控件API兼容的定制控件的。
- 部件是否需要可移植?如果是,是否所有平台都存在相应的本地部件?如果你写平台本地代码不存在问题,可以为各个平台提供一种你的应用所支持的本地实现。如果不是所有平台都存在相应的本地部件,你还得提供可移植的实现代码。
- 部件功能能否通过扩展现有控件的行为实现?如果是,请考虑使用现成控件并把增强的行为包装到控件中去。
- 从用户的角度来看,你的部件是否包含其他控件?深入考虑这个问题是有意义的,因为它影响控件的实现和API。
一旦你决定了需要一个定制控件,并且决定要在哪些平台上得到支持,就可以为你的部件考虑若干实现技术。这些技术可以混合、匹配使用,视相应的基础平台有何有效支持而定。
本地实现
如果你的应用需要一个SWT未提供的本地部件,你需要为此写一个本地的实现。这可以是平台部件,第三方部件或其他平台共享库中的部件。
每个平台的SWT实现都会带有一个共享库(比如windows中的一个动态连接库)和一个JAR包(java class文件打包)。共享库包含SWT所需的所有本地功能函数,但并不意味着包含平台上所有的功能集。要公开SWT所未公开的本地功能或本地部件,你需要写自己的共享库。
如果你的代码实现中含有某个平台的本地代码和另一个平台的可移植代码,要确保在代码中的本地部件调用的是平台共享库、可移植部件调用的是JAR包。
如果你的代码实现中含有某个平台的本地代码和另一个平台的可移植代码,要确保在代码中的本地部件调用的是平台共享库、可移植部件调用的是JAR包。
要实现本地部件,你必须了解Java本地接口(JNI)-- 共享库中的部件API和C代码形式的基础操作系统平台API。
基本的实现过程是:决定哪些本地部件的API 要在JAVA API中公开,并编写java代码调用本地代码来实现相应行为。必须写JNI的C代码来调用共享库接口。
当构建你的本地部件实现的时候,遵循SWT实现的设计原则是一个好主意,比如,你的JNI本地代码应该一一映射到共享库中的API调用。
扩展一个已存在的部件
如果你的新部件在概念或实现上类似一个已存在的部件,你可以考虑包装这个已存在的SWT部件,这种技术在TableTree类的使用中被使用到。要包装一个部件,你要创建一个Composite或Canvas部件的子类(视你的控件是否有子对象而定),在定制控件的构造函数中,创建被包装的部件。结果创造出来的部件是100%Java代码可移植的,因为在你的实现中调用的是被包装的部件的API。
相对于从头开始开发而言,包装一个部件通常是实现定制部件的较简单的方式,然而,你必须要小心设计新部件的API,以下是一些重要的提示:
要考虑你的部件是否 “是一种”被包装的部件,还是它只是使用了后者的一种实现。比如说,一个TableTree对象并不是一种Table对象,它不能通过行序号引用数据项。TableTree对象只是使用了表格的外观并增加了树的行为。如果你包装一个部件纯粹为了实现上的原因,那么你的API不应跟被包装的部件的API相似。
尽可能地减少方法和事件的数量,不要重新实现被包装的部件的全部API,否则如果被包装的部件的API在将来的版本中修改了的话,你会不停地遇到错误和异常。大多数部件都支持的方法,如setFont, setForeground, setBackground,应该在定制部件中提供。
If you find yourself implementing most of the wrapped widget's API, consider exposing the
如果发现已经实现了被包装部件的大部分API,则应考虑在API层次上把被包装部件暴露出来,即让应用程序代码直接使用被包装的部件,在这种情况下,你或许要重新考虑是否提供一个新的部件会更有意义。不要“假装” 成为一个被包装部件,而是作为一个对部件增加行为的“适配器”去实现你的特性或许更好一些。(JFace的视图遵循这个模式)
备注:这里只集中讨论如何通过包装一个部件去扩展其行为。我们更鼓励通过对一个部件进行子类化来达到扩展目的,因为这样你的部件就依赖于父类的实现。
定制绘制实现
在某些情形中,你可能没有任何本地代码或已存在的部件帮助你实现新部件。这意味着你不得不使用SWT的图形调用自己来画出部件。虽然这项技术可能会变得非常复杂,但有个优点,就是可以产生完全可移植的实现。
自绘控件是通过对Canvas或Composite类进行子类化实现的,并使用以下规则:
- 如果部件不会有任何子部件,就从Canvas子类化。这意味着你不想让应用程序为你的部件创建子对象,也不想创建任何子对象去实现你的部件。Canvas用于实现两种控件:简单控件—如带样式的标签、以及复杂一些的部件,如带样式的文本编辑器,在两种情况下,部件完全是在内部使用图形调用来实现的,没有为应用添加任何子对象。
- 如果从应用程序的角度看你的部件需要有子部件,或者是你需要创建子部件去实现你的定制部件,则从Composite类子类化。Composite的使用情形是你需要把部件组合在一起去创建新部件,比如使用文本框和列表框实现一个组合列表框控件,还有的情形是你需要实现这样一种部件,它开始没有子部件但允许调用方为它增加子部件,ViewForm控件是一个例子。SashForm部件则体现了两种情形:它内部的实现使用了框格部件(sash),也允许客户新增它们自己的子部件.
在一个自绘控件中,内部状态是存放在java对象的成员变量中的。根据部件所需定义API和样式。
自绘控件的内部实现通常包括以下主要任务:
- 在构造函数中创建需要用到的所有图形对象并将其存放与成员变量中,为canvas或composite的dispose事件注册一个监听器以便可以在部件销毁后释放这些对象。
- 为canvas或composite的注册一个paintListener监听器,根据你的设计需要绘制部件,对于复杂部件,很多工作集中于优化这个绘制过程,通过计算以及只重绘确实需要重绘的区域。
- 确保任何影响部件外观的API调用都会触发部件的重绘动作。一般来说,当你知道必须重绘部件的时候可以使用redraw方法来摧毁部件,比直接调用内部绘制代码要好。这也给了平台机会去重新安排你的绘制,如果你的绘制任务还附带了其他的挂起等待的绘制任务的话,使所有绘制任务依次通过一个程序出入口完成,这也使你的代码更加流畅。
- 如果你的部件在API中定义了事件,要判断哪些底层的Canvas或Composite事件会触发你的部件的事件。例如,如果你有一个点击事件,你需要对canvas对象注册一个鼠标事件并且执行某些计算(比如点击测试)来决定canvas的鼠标事件会否触发你的部件事件。
在org.eclipse.swt.custom包中的很多部件实现都使用这种方式。一个简单的例子可以在Clabel中找到。