使用 Lotus Expeditor 平台的表格控件构建动态表格应用程序

Lotus Notes 8 除了继承以往的 Notes 版本的核心功能,成为一种强大的协同软件之外,还依托 Eclipse 平台,实现了应用程序的插件化管理。 Notes 8 的用户界面是基于 Expeditor 平台中对 Eclipse 提供的 SWT 和 JFace 等插件的扩展来搭建的,其中的邮件,通讯录和日历等核心应用都广泛的使用了 Expeditor 中的动态表格控件,本文旨在介绍动态表格组件的功能特性和使用方法,读者通过阅读本文后可以在自己的 RCP 应用中的用户界面部分使用 Expeditor 的表格控件。

引言

IBM Lotus 产品组以 Lotus Expeditor 平台为基础开发了 Lotus Notes 8 系列产品。 Notes 8 除了继承以往的 Notes 版本的核心功能,成为一种强大的协同软件之外,还依托于 Eclipse 平台,成功的实现了应用程序的插件化管理,用户可以根据需求定制和扩展自己的客户端。 Notes 8 的用户界面是基于 Expeditor 平台中对 SWT 和 JFace 插件的扩展模块来搭建的,而其中邮件,通讯录和日历等核心应用都广泛的使用了 Expeditor 中的动态表格控件,本文旨在介绍 Expeditor 中的动态表格组件的功能特性和使用方法,读者通过阅读本文就可以使用 Expeditor 的表格控件来搭建自己的应用了。








Expeditor 中 custom table control 介绍

Expeditor 中的 custom table control 基于 RCP 平台中的 swt 和 jface 插件。客户端开发人员可以使用 custom table control 来搭建丰富多彩的表格应用程序。


图 1 custom table control 在应用体系结构中的位置
图 1 custom table control 在应用体系结构中的位置







Expeditor 开发环境的搭建

下载并安装 expeditor 平台 build 的 zip 文件,解压缩后运行 /desktop/install 目录下的 setupwin32.exe,执行安装。

安装完毕后,启动 eclipse3.2.2,通过菜单 Window – Preferences,选择左侧 Plug-in development 下的 target platform,在右侧选择 expeditor 安装目录下的 eclipse 目录,然后选 OK. 在左侧选择 Java – Installed JREs,选择 1.5 或更高的 JRE,选 OK.

在新的 workspace 中,import 插件 com.ibm.rcp.sample.dynamictable 。通过右键“Run As ”–“Eclipse Application”,应用程序就启动起来了。








Custom table control 主体功能和特性介绍

类似于一般的表格控件,custom table control 提供了很多基本的功能:列表数据的显示,滚动表格的表头的显示 / 隐藏,不同列的交换,列宽的调整表格单元格内容的显示,包含图片,文字等。图 2 对这些功能进行了说明。


图 2 表格基本功能演示
图 2 表格基本功能演示

除了这些最基本的功能,expeditor 的 custom table control 还提供了很多高级的功能和特性。

多种显示模式

Custom table control 提供了两种显示模式,一种为普通表格模式,如一种为树型模式 ( 图 3) 。


图 3 树型模式

水平/垂直布局

一般情况下,表格的一条记录中的多个单元格是在一行中显示的,但当表格比较窄的时候,如果一条记录的内容可以分为两行来显示,信息的呈现显得更加丰富。


图 4 垂直布局显示效果
图 4 垂直布局显示效果

后端数据的异步懒加载

当后台数据库量比较大的时候,如果第一次显示表格的时候就从数据源获取全部的数据,不仅要花费较多的时间,而且大量的数据将耗费很多的内存,使得系统运行速度变慢。所以 custom table control 采取的策略是每次只获取需要的数据,然后立即显示获取的数据,而之前获取的数据则被缓存,当缓存的数据量超过一定量的时候,对旧数据进行清理。

另外,由于从数据源获取数据不需要 UI 的参与,所以可以放在独立的线程里面来完成,当前的主线程即 UI 线程可以继续响应用户的界面操作,如选择,删除,右键菜单等。这样用户在使用 Table 的过程中,后端取数据的过程不会影响用户的使用,从而提供了流畅的用户体验。如图 5 所示,红色矩形框中的进度条表示用户在使用鼠标滚轮滚动页面的时候,table 从后端获取数据。


图 5 数据懒加载
图 5 数据懒加载

可定制的行/单元格颜色

Custom table control 提供了对行和单元格颜色的定制功能。客户端程序员可以给表格设置 ItemPaintModifier 以定制某些 item 的前景色和背景色,也可以给某些列设置 CellPaintModifer,以设置该列中某些 cell 的前景色和背景色。


图 6 定制行和单元格的前景色和背景色
图 6 定制行和单元格的前景色和背景色

嵌套表格

嵌套表格是一种很有用的功能,在表格中,可能存在有多个 item(Item1, Item2 … Item I) 与某个 item(Item0) 有联系,当打开 Item0 的时候,用户同时也有察看 Item1~ItemI 的需求,这个时候,将 Item1~ItemI 放到一个 table 中,作为 Item0 所在表格的嵌套表格的 item,当打开 Item0 的时候,就可以打开这个嵌套的表格,用户可以看到 Item1~ItemI 的内容。

嵌套表格在 Notes 8 的 Mail 中得到应用,如图 7 所示,红色矩形中的右箭头表示这里有一个嵌套表格,通过点击它,可以看到图 8 中的画面,新打开的嵌套表格的表头文字为” 3 messages in this conversation: * Action Required – Notes 8.0.1 ”,说明标题为” Action Required – Notes 8.0.1 ”的 item 有三个相关的 item,都在嵌套表格中列出来了。


图 7 关闭的嵌套表格
图 7 关闭的嵌套表格

图 8 打开的嵌套表格
图 8 打开的嵌套表格

文本自动换行

在 custom table control 中,实现了单元格文字的自动换行,当调整列的宽度的时候,该列下的单元格的文字能够自动调整。从图 9 到图 10 的变化可以看出这个特性,使用 custom table control 开发新的表格应用的程序员不需要自己来实现这个功能。


图 9 改变列的宽度之前
图 9 改变列的宽度之前

图 10 改变列的宽度之后
图 10 改变列的宽度之后

css 支持

custom table control 中,支持使用 css 定制表格中元素的样式。如果使用 css 来设置表格的样式,table control 程序在渲染表格的时候,会根据当前 css 的配置,使用皮肤来绘制表格中的各个元素,如 focused item 的边框,item 的背景,单元格的文字,表格 header,表格的标题栏,文字的下划线等元素。

DND

custom table control 支持多 item 的拖拽,在表格中可以选中多个 item,然后将多个 item 一次性拖拽到目标区。如图 11 所示,首先选择三个 item, 然后按住鼠标左键进行拖拽,就可以看到这三个 item 构成的拖拽图片,在图中,这三封邮件被拽入”重要信息”文件夹中。


图 11 多个 Item 的 DND
图 11 多个 Item 的 DND

XML format

Custom table control 支持使用 xml 来设置 table 的一些参数,如单元格 padding 和列的参数。

选择模式

在 custom table control 中,提供了一种与 Notes7 兼容的,用于选择表格中 item 的模式,即 gutter 模式,在这种模式下,用户可以方便的使用鼠标单击来选择不连续的 item,而在普通模式下,选择不连续的 item 需要一些功能键的帮助才能完成。在 gutter 模式下,被选中的 item 的左边会出现一个对号作为标记,并且可以通过持续按下鼠标左键的方式来连续选择多个 item 。图 12 中表格左侧的矩形框中显示了一系列的对号,表示这一系列的 item 处于被选中的状态。


图 12 gutter 选择模式
图 12 gutter 选择模式







在应用程序中使用 custom table control 组件构建动态表格应用

Notes 8 中已经广泛的使用了 custom table control 组件,不仅在 Mail&Contacts, Calendar,还有 activity 已经得到了应用,sametime 也准备使用 custom table control 来实现联系人列表的呈现。

下文将给出一个基于 custom table control 的 tree viewer 的范例。程序运行时可以得到图 13 所示的程序界面:


图 13 用 custom table control 实现的 tree viewer 应用
图 13 用 custom table control 实现的 tree viewer 应用

将从下列几个方面分别进行介绍 :

  • 建立 RCP 应用程序,创建视图扩展
  • 实现 model 层代码
  • 创建 ContentProvider
  • 创建 LabelProvider
  • 在视图中创建 table
  • 使用 PaintModifier 来定制 table 的样式
  • 使用 css 定制 table 的样式
  • 实现过滤器
  • 实现排序

建立 RCP 应用程序,创建视图扩展

新建插件项目,名称为 com.ibm.rcp.sample.tabletree,在” Plug-in content ”页中的” Rich Client Application ”部分,选择” create a rich client application ”,然后选择下一步,在” Templates ”页,选择” Create a plug-in using one of the templates ” , 选择” RCP application with a view ” , 点击” Finish ”。

编辑元数据配置文件 MANIFEST.MF,将 Require-Bundle 属性修改为:

 
Require-Bundle: org.eclipse.ui,
org.eclipse.core.runtime,
com.ibm.rcp.swtex,
com.ibm.rcp.jfaceex,
com.ibm.rcp.ui.css

实现 model 层代码

与 jface 里的 tree viewer 一样,custom table control 同样基于 MVC 的架构。首先,实现模型层,也就是树型结构所依赖的实体类。

在我们构造的树中,存在四种类型的实体,Box, book,game, 以及 gamebook 。这四种实体共同具有一个特点,它们都可以有父亲,而且都为 box 类型。于是我们建立一个接口 IEmbedable,代码如下 :

public interface IEmbedable { 
public Box getParent();
}

然后实现其他的几个实体类,其中 Book 类是 Gamebook 类的父类,在 Box 类中,有四个类型为 List 的对象,表示当前 Box 对象的孩子中 box,game,book,gamebook 的列表对象。

创建 Content Provider

CollectionContentProvider 是 custom table control 的内容提供者,当 table 需要获取新的数据时,将调用 CollectionContentProvider 的 getElement() 等方法从数据源获取数据并执行对 UI 的更新。因此,CollectionContentProvider 不仅获取了数据,还执行后续的界面更新任务。在 CollectionContentProvider 中,封装了对接口 IPagedContentProvider 和接口 ITreePagedContentProvider 进行了封装,用于获取后端数据。当 table 是普通列表模式时,封装的是 IPagedContentProvider 的实现,当 table 是树模式时,封装的是 ITreePagedContentProvider 的实现。

CollectionContentProvider 在获取数据时,将启动一个独立的 Job 来获取数据,在这个 job 中,调用 IPagedContentProvider 接口实现类的实例的一系列方法获取数据,然后在 UI 线程中更新 UI 。在本文的样例程序中,MyTreePagedContentProvider 实现了 ITreePagedContentProvider 接口。

这个 content provider 类有一个成员变量用来保存给 TableViewer 设置的 input 对象,当 TableViewer 的 input 发生改变的时候,这个 input 对象也随之得到改变,这是通过 inputChanged 方法来实现的。

 
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
inputElement = newInput;
}

本应用程序提供的树 tree 没有提供翻页的功能,因此,只需要实现方法 getInitialPage 即可 :

 
public PagedContent getInitialPage(int count) throws CpException {
return getPage(0, count);
}

public PagedContent getPage(int offset, int count) throws CpException {
PagedContent pagedContent = null;
// 获取树的内容
List items = getTreeInput();

if (null == items || offset != 0) {
return null;
}

// 构造 PagedContent 对象并返回
Object[] bjects = items.toArray(new Object[items.size()]);
pagedContent = new PagedContent();
pagedContent.setObjects(objects);
pagedContent.setTotalObjectCount(items.size());
pagedContent.setStartingOffset(offset);
pagedContent.setSourceLastModified(new Date());
return pagedContent;
}

private List getTreeInput() {
List items = null;
if (inputElement instanceof MyCollectionInput){
Box rootBox = Util.getRootBox();
rootBox.setIsOpen(true);
items = rootBox.getContents();
}
else if (inputElement instanceof Box) {
Box box = (Box) inputElement;
items = box.getContents();
}

return items;
}

当获取整棵树的内容的时候,input 的类型为 MyCollectionInput,此时提供给 table 的数据是树的根节点的所有孩子。当 input 的类型为 box 时,当前的动作是打开一个 box 节点,该方法返回这个 box 节点的所有孩子。

创建 LabelProvider

LabelProvider 用来提供 table 中的显示元素,显示元素包括表格单元格的图片文字,表格的背景色前景色,以及字体。

方法 getColumnImage(Object element, int colIndex) 用来获取某个单元格的图片,element 为表格中的一个 item 所显示的对象,如 Box,Game 等; colIndex 是列的索引,用来定位单元格。方法 getColumnText(Object element, int columnIndex) 用来获取某个单元格的文字。这两个方法是在接口 ITableLabelProvider 中定义。

方法 getForeground(Object element, int columnIndex) 用来获取某个单元格的前景色,方法 getBackground(Object element, int columnIndex) 用来获取某个单元格的背景色。这两个方法是在接口 ITableColorProvider 中定义。

方法 getFont(Object element, int columnIndex) 用来获取某个单元格所用的字体,在 ITableFontProvider 中定义。

创建 TableViewer

使用 TableViewer 可以方便的实现 SWT 控件和业务模型之间的连接,并以 MVC 的模式组织应用程序。 custom table control 组件中已经提供了 TableViewer 的实现,应用程序员可以在此基础上进行功能的扩展,实现定制的 TableViewer 类。在本应用中,存在一个特定的需求:当成功地获取 table 的数据后,展开原本处于展开状态的 box 节点。于是重载了方法 asynFetchCompleted,实现了类 MyTableViewer.java:

 
protected void asyncFetchCompleted(Object[] objs) {
if(objs == null) {
return;
}

for(int i = 0;i < objs.length; i ++) {
Object element = objs[i];
if (element instanceof Box) {
Box box = (Box) element;
expandItem(box, box.isOpen());
}
}
}

在视图中创建表格

进行了前面的准备工作后,着手创建 table 。在视图类 View.java 中,实现 createPartControl 方法,注意下面的代码:

 
// 构造 STable 实例
STable table = getSTable(_treeViewerComposite);
// 构造 TableViewer 实例
tableViewer = new MyTableViewer(table);
// 创建 table 的列
createDefaultTableColumns(table);
// 设置 content provider
CollectionContentProvider cp
= new CollectionContentProvider(new MyTreePagedContentProvider());
tableViewer.setContentProvider(cp);
// 设置 label provider
tableViewer.setLabelProvider(new MyLabelProvider());
// 设置 input 给 TableViewer
tableViewer.setInput(new MyCollectionInput());
……
// 设置 listener
hookListeners();

在 getSTable 方法中构造了 STable 的实例,设置了一些属性并返回。
private STable getSTable(Composite parent) {
STable table = new STable(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
// 不显示 table header
table.setHeaderVisible(false);
// 显示 focus 的边框
table.setShowFocusBorder(true);
… ..
// 设置 twistie 列是否显示,twistie 列广泛用于 tree 模式,用于提供展开树中父亲节点的图标
table.setTwistieVisible(true);
// 设置单元格的 padding 属性
table.setCellPaddingBottom(1);
table.setCellPaddingTop(0);
table.setCellPaddingRight(0);
table.setCellPaddingLeft(0);
return table;
}

在 createDefaultTableColumns 方法中创建 table 的列
private void createDefaultTableColumns(STable table) {
// 创建第 0 列为 twistie 列
table.createTwistieColumn(0);
STableColumn twistieColumn = table.getTwistieColumn();
// 设置列宽
twistieColumn.setWidth(300);
// 设置列是否可以 resize
twistieColumn.setResizable(true);
// 设置 twistie 列的图片提供器
twistieColumn.setTwistieImageModifier(new TwistieModifier());

twistieColumn.setLeftAlighedImagesInTwistieColumn(true);
// 设置列的字体
twistieColumn.setColumnFont(new Font(Display.getCurrent(), "Tohoma", 8, SWT.BOLD));
}

TwistieModifier 类提供了树中父亲节点中的 twistie 图片,

private class TwistieModifier implements ITwistieImageModifier {
public Image getExpandedImage(Object element) {
Image img = null;
if (element instanceof Box) {
img = Util.getImage(Util.IMG_BOX);
}
return img;
}

public Image getCollapsedImage(Object element) {
Image img = null;
if (element instanceof Box) {
img = Util.getImage(Util.IMG_BOX);
}
return img;
}
}

使用 PaintModifier 来定制 table 的样式

在 custom table control 中,可以给 table 设置行颜色提供器和单元格颜色提供器来定制 table 中行和单元格的前景色和背景色,以及字体的风格。颜色提供器遵循统一的接口 :

 
public interface IPaintModifier extends IColorProvider, IFontStyleProvider {
void dispose();
}

  • IColorProvider 接口定义了获取前景色和背景色的方法;
  • IFontStyleProvider 接口定义了获取字体风格的方法;

给 table 设置行颜色提供器:

table.setItemPaintModifer(new ItemPaintModifer()); 

给 table 设置单元格颜色提供器是通过调用 STableColumn 对象的方法来实现的 :

tableColumn.setCellPaintModifier(new CellPaintModifier()); 

使用 css(cascaded style. sheet ) 定制 table 的样式

Custom table control 支持使用 css 来定制样式,这样有利于实现 table 风格的灵活变换。在 table 组件中实现了类 StyledTableSkin,该类使用 css 中配置的样式来绘制 table 中的文字,背景,线条等界面元素。

为了支持使用 css,table 需要实现 StyledWidget 接口及该接口中的 getStyledElements 方法,该方法返回一组的 StyleElement 对象,styleElement 对象的方法 setStyles 用于设定 control 的样式属性。

下面是从本文的样例程序的 css 文件中摘取的两段配置信息 :

 
tableBox > row > primarySelection{
background-color:
rgb(127,175,237),
rgb(98,148,214),
100%;
color: white;
}

上面这段配置表示本样式元素是 row 的子元素,元素名为 primarySelection,没有 pseudoClassName,则使用默认值 normal,父元素是 row 。

 
tableBox > row:focus {
border-color: rgb(56,112,176);
border-width: 1;
}

上面这段配置表示本样式元素是 row 元素,pseudoClassName 为 focus 表示该样式元素描述了 focus 的 row 的样式信息,父元素是 table.

这里的 css 定义需要和 STable.getStyledElements() 方法的实现相匹配,StyleEngine 的对象会调用 css 配置文件的处理引擎来读取 css 中的配置信息,并以属性名称为 key,属性的 value 为 value 存放在 Map 对象中,传给 StyledElement 供其使用。如下面的代码所示 :

 
public void setStyles(Map styles) {
skin.setStyles(this, styles);
control.redraw();
}

在 custom table control 中,skin 的实现类是 StyledTableSkin,该类的 setStyles 方法会根据 styledElement 的具体参数 (element name, pseudoClassName) 从存储样式的 Map 对象中获取指定 key 值的样式的详细信息,如颜色,字体,边框的宽度等,并赋给 StyledTableSkin 中相应的变量。在序列图的第 8 步中,表格会调用 StyledTableSkin 提供的方法来绘制表格的界面,当 StyledTableSkin 绘制界面时,使用的颜色,字体等参数就是从 css 中读取的参数。

在本文提供的程序中,也简单的使用了 css,将 css 文件放在工程的 formats 目录下。在 STableBoxView.createPartControl() 方法中设置 table 使用的 css 配置文件。

 
STable table = getSTable(_treeViewerComposite);
setStyle(table);

private void setStyle(STable table) {
try {
URL pluginRoot = Activator.getDefault().getBundle().getEntry("/");
URL url = new URL(pluginRoot, "formats/tableBox.css");
table.setData("class", "tableBox");
StyleManager.getInstance().style(table);
StyleManager.getInstance().parse(url);
}
catch (Exception e) {
e.printStackTrace();
}
}

实现排序

在表格中,比较常见的需求是根据列进行排序;在 custom table control 中,不提供排序的业务逻辑的实现,排序由使用 table control 开发的应用程序来实现。实现排序的一般方法是给表格的列添加 selection listener,当表格的某一列的 select 事件发生时,table column 会发送 selection 事件给应用程序,应用程序收到事件后会执行对数据的排序,并重新给 TableViewer 设置 input, 之后 TableViewer 会执行刷新,并获取排序后的数据。


图 14 实现排序
图 14 实现排序

在本文提供的例子程序中,采用了在菜单中添加菜单项来实现排序。

修改 ApplicationActionBarAdvisor.java,添加一个成员变量

 
private Action sampleSorter;

然后在 makeActions 方法中,实例化该变量,并实现排序的逻辑。

 
sampleSorter = new Action("Sample Sorter") {
public void run() {
doSortingWork(sampleSorter);
}
};
sampleSorter.setChecked(false);

protected void doSortingWork(Action sampleSorter2) {
TableViewer tableViewer = getTableViewer();

if(tableViewer != null)
{
if(sampleSorter2.isChecked())
{
Box rootBox = Util.getRootBox();
Box newRootBox = sortBox(rootBox);
newRootBox.setIsOpen(rootBox.isOpen());
tableViewer.setInput(newRootBox);
} else {
tableViewer.setInput(new MyCollectionInput());
}
}
}

在 sortBox 方法中,对 box 节点下的孩子进行排序,得到排好序的一棵树,并将新树的根节点作为 model 传给 table viewer,之后 table viewer 执行 refresh 操作,重新构造 UI 。这一点和图 14 中的描述是一致的。

实现过滤

在本文提供的例子程序中,也实现了对树中节点的过滤功能,当用于过滤的 action 被触发时,过滤后的 model 会传给 table viewer,用来显示过滤后的内容。

 
sampleFilter = new Action("Sample Filter") {
public void run() {
doFilteringWork(sampleFilter);
}
};

protected void doFilteringWork(Action sampleFilter) {
TableViewer tableViewer = getTableViewer();

if(tableViewer != null)
{
if(sampleFilter.isChecked()){
tableViewer.setInput(Util.getFilteredContent());
} else {
tableViewer.setInput(new MyCollectionInput());
}
}
}








Custom Table Control 与 SWT Table 功能的比较


表 1 SWT 标准表格 / 数控件和 Custom table control 的功能比较
特征 表格 / 树 Custom Table control
垂直布局
单元格文字自动换行
包含多列的树
Item 的拖拽
单元格字体颜色的设置
调节列的宽度
表格构造的 xml 配置
隐藏 / 显示详细信息
快速查找单元格文字的支持
多种 item 选择模式 否 ( 仅支持一种选择模式 ) 是 ( 支持两种选择模式 )
使用级联样式表配置表格的样式








结论

Custom table control 作为 Lotus Notes 8 中多种表格应用程序的基础组件,提供了丰富,通用而且强大的功能,满足了 Notes 和 Expeditor 平台对树型和表格型 UI 组件的需求。









下载

描述名字大小下载方法
本文样例代码stable.vs.tree.zip69 KBHTTP
关于下载方法的信息


参考资料

学习

讨论


关于作者


王威,IBM 中国有限公司软件开发中心工程师,近两年来致力于 Notes/Expeditor 平台的客户端组件的设计和开发。熟悉的领域还包含基于 J2EE 架构的 Server 端和 Web 客户端技术。

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/14751907/viewspace-539565/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/14751907/viewspace-539565/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值