`

应用模式设计基于 TableViewer 的应用框架

阅读更多

引言

表格显示器(本文中“表格显示器”泛指 Eclipse JFace 的 TableViewer)是 Eclipse JFace 中的 Viewer(包括 TableViewer,TreeViewer,TextViewer 和 ListViewer 等)之一。JFace Viewer 应用 MVC 模式设计,将领域模型填充到对应的组件(Control)中,如 TableViewer 将数据填充到 Table 组件中。JFace TableViewer 正如其名字一样,是 SWT Table 的显示器,它内部封装了 SWT Table 类。TableViewer 提供了一套逻辑及相关的接口和类,从而保证开发人员能够以较高的效率编写基于 table(表格)的界面设计和开发。

在插件开发和 RCP 开发等基于 Eclipse 的软件开发过程中,TableViewer 使用非常广泛,它以表格的形式显示数据,开发人员能够定制显示内容的样式(字体样式:粗体,斜体等等),在同一列中即显示图片又显示文字和对表格中的每列排序等等;同时,还可以对 TableViewer 进行编辑,用户的修改可以立即反应到 TableViewer 中,实现所见即所得的效果。

如图 1 所示为 Eclipse3.4 版本自带的“Open Type”功能打开的查找类型界面,图中“Matching items”下的 Viewer 即为 TableViewer(表头设置为不显示)我们除了可以显示该资源关联的图片外,还能够以不同的字体样式显示同一行中的文字。


图 1. Eclipse3.4 版本的“Open Type”查找界面
图 1. Eclipse3.4 版本的“Open Type”查找界面 

如图 2 所示为 RSA 7.5.2 版本中运行 Model Search 查找匹配模型的结果集视图,它用表格的形式显示结果集,该 TableViewer 的表头(两列的列头)设置为可见的,并且可以对每列进行排序。


图 2. IBM RSA7.5.2 中 Model Search 的查找结果集视图
图 2. IBM RSA7.5.2 中 Model Search 的查找结果集视图 

因为表格显示器采用 MVC 模式设计,因此,使用 TableViewer 显示数据时,需要与内容提供者(Content Provider)和标签提供者(LabelProvider)协同工作。内容提供者将输入的领域模型数据转换为数组的形式,原领域模型可以用任何类型表示,如 List,Hash 等等,通过内容提供者封装以后则为对应类型的数组。标签提供者则把内容提供者封装的数据以指定的内容和格式显示在表格上(Eclipse JFace Viewer 框中,自动把数组的一个元素映射到表格中的一行,也即通过 TableViewer 的 selection 里面封装的对象即为数组中对应的对象)。

总体而言,想要深入理解表格显示器,并且开发基于 TableViewer 的应用程序,我们必须掌握三方面的内容:表格显示器 TableViewer 及其封装的 Table 提供的事件,内容提供者 Content Provider 和标签提供者 LabelProvider。本文第三节给出的例子便是基于这三方面的内容,并且结合 SingleTon,Template,Decorator 和 Mediator 等设计模式提供了一个具有较好扩展性和伸缩性的框架。下面从概念上介绍这三方面内容,下一小节将会进行详细介绍。

TableViewer 及其封装的 Table 提供的事件

TableViewer 及 Table 作为 Eclipse 的 Widget,因此它们具有一些共性事件:拖拽事件(Drag and Drop,简称 DND),鼠标相关联的事件(如 Mouse move, Mouse down 等),单击和双击事件,按键事件(如 Key press, Key down 等),Control,Open 事件和选择事件(表格中可以选择单元格或一行或多行)等等。掌握这些事件是我们增强 TableViewer 功能的基础,也有助于我们深入理解 TableViewer 的设计思想。

内容提供者 Content Provider

内容提供者用于规范领域模型数据的输入,配合标签提供者把这些领域模型数据显示在表格显示器上。对于 TableViewer 这样结构化的表格显示器,用户自定义的内容提供者一般实现 IStructuredContentProvider 接口。如果我们输入的模型数据本身就是数组类型,那么可以直接使用 Eclipse 提供的 ArrayContentProvider 类。

标签提供者 LabelProvider

标签提供者用于把内容提供者的数据显示在表格显示器上,通常标签提供者逐个获取数组中的领域模型数据,然后根据最开始我们创建表格显示器时指定的列把这些数据填充到每一个单元格中。Eclipse 针对表格显示器,定义了其标签提供者的接口 ITableLabelProvider,当然我们也可以针对每列设置相应的标签提供者,读者可以参考 CellLabelProvider 和 ColumnLabelProvider。

TableViewer 相关的方法、接口和类

至此,我们对 JFace TableViewer 有了感观的认识。由上一节我们可知,要深入理解 TableViewer,我们需要从三个方面去掌握:TableViewer 和 Table 提供的事件、内容提供者、标签提供者,下面将进行详细讲述。

TableViewer 和 Table 提供的事件

TableViewer 和 Table 作为界面上的组件(Control),同其他组件一样,提供了丰富的事件,利用这些事件一方面可以增强用户体验,同时能够提供强大的功能。

TableViewer 的事件

1. IDoubleClickListener 双击 TableViewer 事件

这个事件比较简单,当我们双击表格显示器的时候,就会触发该事件,通常该事件用于打开某个对象,如双击 RSA Model Search 的查找结果集视图的表格显示器时,会打开该元素的属性页。同一个 IDoubleClickListener 对象,如果重复多次通过 TableViewer 的 addDoubleClickListener 方法注册该事件,那么只有第一次的会起作用,但是我们可以定义多个不同 IDoubleClickListener 对象并注册,那么当发生双击事件时,TableViewer 会按照这些事件注册的先后顺序通知它们。

2. DND(DragSourceListener 和 DropTargetListener)拖拽事件

Drag and Drop(DND)是 Eclipse 为用户在一个或多个 SWT 应用之间重置部件或数据传输上提供的简单快捷的机制。DragSource 是数据传输过程中的数据提供者,而 DropTarget 是数据接收者。DND 本身是一个很大的话题,有许多值得深入理解的知识,用整篇文章来写多可以,因此,本文主要侧重在概念上介绍 TableViewer 的 DND 事件。

TableViewer 拖拽方式包括复制方式(DROP_COPY)、链接方式(DROP_LINK)和移动方式(DROP_MOVE),当然也可以是不支持的方式(DROP_NONE),在定义支持的拖拽事件时,可以同时支持所有的这些方式,采用或(OR)运算符实现,以下是这些类型的鼠标的图标。


图 3. 不同类型的拖拽事件鼠标的图案
图 3. 不同类型的拖拽事件鼠标的图案 

3. ISelectionChangedListener

该事件相对比较简单,当 Selection 改变时被触发,如下为注册该事件的代码,我们只需实现 selectionChanged 方法即可,通过 event 可以获取 selection 及 selection 提供者。


清单 1. TableViewer 的 Selection Changed 事件
				
 fTableViewer.addSelectionChangedListener(new ISelectionChangedListener(){ 
    public void selectionChanged(SelectionChangedEvent event) { 
                
    } 
            
 }); 

4. IOpenListener 事件

当 selection 打开的时候会触发该事件,如双击事件。如果我们同时定义了 selection change,double click 和 selection open 事件,那么其执行顺序依次为 selection change, double click 和 selection open 事件。TreeViewer 中该事件用的比较多,如当前 selection 为非叶子节点时,那么该事件可以展开该节点,显示器子节点;当前 selection 为叶子节点时,那么可以打开对应的对象(如果叶子节点为 java 文件,那么我们可以用 java 编辑器打开该文件)。

Table 的事件

1. org.eclipse.swt.events.MouseMoveListener 鼠标移动事件

当鼠标位置发生移动时触发该事件,通过 table 类的 addMouseMoveListener/ removeMouseMoveListener 方法注册 / 注销该事件。

2. org.eclipse.swt.events.ControlListener 事件

当我们移动表格中一列的位置(列的顺序发生改变)或宽度时触发该事件。通过 table 类的 addControlListener / removeControlListener 方法注册 / 注销该事件 . 注意,虽然表格中列的顺序发生了变化,但是对于当前的 Table 实例而言,通过 getColumns() 获取得到 TableColumn 数组中列并没有发生变化,只是界面显示上的变化 ,这个对于我们实现表格中列的位置和宽度持久化非常重要。

3. org.eclipse.swt.events.MouseListener 事件

鼠标按钮相关的事件,包括鼠标双击、鼠标按下和鼠标抬起(包括鼠标上的任何键:左键,中键和右键)。通过 table 类的 addMouseListener / removeMouseListener 方法注册 / 注销该事件。

4. org.eclipse.swt.events.MouseTrackListener 事件

鼠标跟踪事件,包括鼠标进入 Table、鼠标移出 Table 和鼠标停留在 Table 上事件。通过 table 类的 addMouseTrackListener / removeMouseTrackListener 方法注册 / 注销该事件。

5. org.eclipse.swt.events.MouseWheelListener 事件

鼠标滚动时触发该事件。通过 table 类的 addMouseWheelListener / removeMouseWheelListener 方法注册 / 注销该事件。

内容提供者(Content provider)

Eclipse 针对 TableViewer,TreeViewer,ListViewer 等这类显示结构化数据的 Viewer 提供了内容提供者接口 org.eclipse.jface.viewers.IStructuredContentProvider,同时,针对数组和集合(Collection)类型的领域模型数据提供了类 org.eclipse.jface.viewers.ArrayContentProvider,它实现了 IStructuredContentProvider 接口。下面介绍一下类 ArrayContentProvider 中的关键方法。

getElements 方法把领域模型数据转换为数组的形式输出,如下代码为其函数声明。

public Object[] getElements(Object inputElement)

当有新的模型数据输入,方法 inputChanged 将被调用,如下代码为其函数声明

public void inputChanged(Viewer viewer, Object oldInput, Object newInput)

标签提供者(Label provider)

Eclipse 针对 TableViewer,定义了其标签提供者的接口 ITableLabelProvider。下面两个方法在实现该接口必须重写,getColumnImage 获取该列的图片;getColumnText 获取该列的内容;参数 element 则为表示表格中每一行的对象。


清单 2. 标签提供者类的两个重要方法
				
 public Image getColumnImage(Object element, int columnIndex); 
 public String getColumnText(Object element, int columnIndex); 

ITableLabelProvider 接口是最基本的接口,Eclipse 还提供了其他一些功能丰富的标签提供者,如 org.eclipse.jface.viewers.StyledCellLabelProvider 能够设置单元格中的字体样式,我们可以在该类的 update 方法通过 StyleRange 设置字体样式。

编程实践

基于 TableViewer 的应用框架

在介绍本文的框架之前,首先简单地说明设计该框架应用到的重要设计模式:Decorator,SingleTon,Mediator 和 Template 模式。

Decorator 模式可以动态地给一个对象增加其他职责。本文中把 TableViewer 的一个或多个功能看成是一个 Decorator,我们可以动态的选择 Decorator 来构建具有不同功能的 TableViewer,这些 Decorator 之间不存在任何关系,以保证框架的扩展性。

SingleTon 模式属于管理实例化过程的设计模式家族。Singleton 是一个无法实例化的对象。在任何时候,我们只会创建一个 Singleton(对象)实例,如果实例不存在,通过创建类的新实例的方法建立一个类来执行这个模式;如果存在类的一个实例,只会返回那个对象的一个引用,本文中的服务提供者类 TableServiceProvider 就是一个 SingleTon 类,所有基于本文框架的应用共享这个服务提供者类。

Mediator 模式用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显示地互相引用,从而使其耦合松散,而且可以独立地改变他们之间的交互。在本文的框架中,每一个 Decorator 类对应有一个类 *Service 提供改 Decorator 运行期间需要用到的任何服务,但是,每个 Decorator 并不直接调用 *Service 类,而是通过 Mediator 类 TableServiceProvider 类进行中转,然后再访问相应的服务类,可以降低 Decorator 累和 Service 类的耦合,并提高代码利用率。

Template 模式定义一个操作中的算法的骨架,而将一些可变部分的实现延迟到子类中,该模式使得子类可以不改变一个算法的结构即可重新定义该算法的某些特定的步骤。本框架表格显示器的超类 AbstractTable 定义了需要用到的所有方法,子类采用 Template 模式实现其中的方法。

如图 4 为采用 Decorator 模式、SingleTon 模式、Mediator 模式和 Template 等模式设计基于 TableViewer 的应用框架,其主要思想是把 TableViewer 的每一个或若干个功能作为一个 Decorator 进行设计(,同时通过 Mediator 模式设计一个服务提供者类,使得 Decorator 类需要的各种数据及其特定于应用需求的服务都通过该类获取,降低了 Decorator 类和用户需求的耦合度,提高了框架的可扩展性和可伸缩性。下面具体介绍该框架中涉及到的类和接口。

1. 定义一个抽象超类(对应于图中的 AbstractTable 类),它封装了基于 TableViewer 的应用需要的大部分方法,开发人员可以根据应用需求增加或减少相应的方法 / 属性。AbstractTable 类采用 Template 模式设计,表格基本类(定义参见 2)和所有的 Decorator 类均实现了该类中的抽象方法并重写其中某些非抽象方法。

2. 定义一个基于特定需求类,本文称为基本表格显示器类,(对应于图中的 BGCommonTable 类),该类继承 AbstractTable,并实现其中声明的方法或重载定义的方法;开发人员可以根据自己的需求,定义所需的属性和方法等等。

3. 采用 Decorator 模式定义一系列的 Decorator 类,用于提供功能强大的 TableViewer。图中定义了三种 Decorator:TableSortDecorator(用于对表格显示器中的列排序),TableContextMenuDecorator(用于给表格显示器注册上下文菜单)和 TableHyperLinkDecorator(用于给表格显示器中的列注册超链接)。为了尽可能使各个 Decorator 专注于自己的功能,因此我们可以定义一个抽象 Decorator 类 AbstractTableDecorator,它提供了与特定的 Decorator 无关的一些方法和属性。

4. 针对每一种 Decorator,本框架定义了一个相应的服务类(如图中的 ITableSortService,IHyperLinkService 等),用于 Decorator 类和表格显示器基本类运行时需要的数据等服务;在本文的框架中,Decorator 类并不直接从服务类中获取服务,而是通过服务提供者 TableServiceProvider 类获取这些服务。为了提高服务类的使用效率,服务类只关注服务本身,而不实现如何注册和使用该服务,如 IContextMenuService 提供了添加菜单项到菜单管理器中和为菜单添加事件的方法,当并没有如何把菜单注册到哪个组件(Control)和什么时候弹出该菜单(这些操作有服务提供者类完成)。本文定义的服务提供者类 TableServiceProvider,采用 Mediator 模式设计,用于提供 AbstractTable 子类所需要的服务。


图 4. 基于 TableViewer 应用框架
图 4. 基于 TableViewer 应用框架 

下面详细介绍该框架设计到接口、类及它们如何协同工作,从而保证框架的扩展性和伸缩性,提供强大的表格显示效果。本文的例子要提供一个可保存表格的状态(记住列的顺序及宽度)、可以对每列排序的、具有上下文菜单的和表格中的指定列设置为超链接的表格显示器框架。

本框架定义的 AbstractTable 类采用组合的方式实现 TableViewer 类(TableViewer 类作为 AbstractTable 的一个属性),而不是采用继承方式。本文把“保存列的位置及其宽度”作为表格显示器的基本功能,因此并没有作为一个 Decorator,读者可以视自己的需求 ,哪些作为表格显示器的基本功能,哪些应该作为一个 Decorator 抽象出来。

超类 AbstractTable

AbstractTable 类是基本表格显示器类和 Decorator 类的基类,它定义了 TableViewer 可以提供的功能,这些功能反映在类的属性或方法中。通常而言,我们会记录表格显示器的输入、表格显示器的内容提供者和标签提供者,当前的 Selection 及 Selection 提供者。下面为 AbstractTable 类中的一些关键代码,定义了一个类 TableColumnStyle 用于表示每列的属性,如列的宽度,名称,是否可以移动和调整宽度等等,同时,方法 createTableArea 用于创建 TableViewer;etInput 方法设置出入参数为表格显示器的输入;decorateForTable 方法用于为 TableViewer 添加相应的功能;所有的 Decorator 类需要重写该方法,而表格显示器基本类不需要;createTableColumns 用于为表格显示器创建列,该方法只在表格显示器基本类中实现,Decorator 类只需调用基本类的该方法即可,不需要其他实现。另外,提供 addBasicListener 方法添加一些表格显示器常用的基本事件 ,如 selection 改变事件,doubleClick 双击事件等等;getTableServiceProvider 方法用于获取当前的服务者,因此 AbstractTable 的所有子类直接通过该函数即可返回基于该子类的服务。


清单 3. 基于 TableViewer 的抽象类
				
 public abstract class AbstractTable { 
    protected TableViewer fTableViewer; 
    protected Composite fControlParent; 
    protected Object fInput; 
    protected ISelection fCurrentSelection; 
    protected TableColumnInfo[] fColumnsInfo = null;    
    protected IBaseLabelProvider fLabelProvider = null; 
    protected IContentProvider fContentProvider = null; 
    protected ISelectionProvider fSelectionProvider = null; 

    public abstract void setInput(Object input); 
    protected abstract TableColumn[] createTableColumns(Table aTable); 
    protected void decorateForTable() {} 
    protected void handleSelectionChanged(SelectionChangedEvent event) {} 
    protected void handleDoubleClick(DoubleClickEvent event){}    
    
    protected TableViewer createTableViewer(Composite parent) { 
        return new TableViewer(parent, SWT.FULL_SELECTION 
                | SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER | SWT.MULTI); 
    } 
    protected void setTableLayout(final Table aTable) {} 
    protected TableServiceProvider getTableServiceProvider(){ 
      return TableServiceProvider.getInstance(
                            this.getTableViewer(), this.getTableService()); 
    } 
    public void createTableArea() { 
        fTableViewer = createTableViewer(fControlParent); 
        final Table aTable = fTableViewer.getTable(); 
        setTableLayout(aTable); 
        createTableColumns(aTable);            
        decorateForTable();        
        setTableColumnFormat(); 
        addBasicListener();                
        setContentAndLabelProvider(); 
    } 
    protected static class TableColumnStyle { 
        public final int columnIndex; 
        public final int style; 
        public final String label; 
        public int width; 
        public final boolean moveable; 
        public final boolean resizable; 
        public TableColumnStyle(int index, int columnStyle, 
                String columnLabel, int columnWidth, 
                boolean columnMoveable, boolean columnResizable) { 
            this.columnIndex = index; 
            this.style = columnStyle; 
            this.label = columnLabel; 
            this.width = columnWidth; 
            this.moveable = columnMoveable; 
            this.resizable = columnResizable; 
        } 
    } 
 } 

正如前面所讲,为了使特定的 Decorator 专注于自己的需求,因此我们提供了一个抽象的 Decorator 类 AbstractTableDecorator 实现了 AbstractTable 中的大部分方法,同时它有一个指向 AbstractTable 的引用,在 Decorator 的构造函数中进行初始化,用于获取在 Decorator“链表”中的前驱(如下代码所示,Decorator 类会记录起前一个 Decorator 对象,因此,读者可以根据需求创建一个 Decorator 链表,从而在运行时获取这些 Decorator 对象)。下面为 AbstractTableDecorator 类的部分关键代码,具体的 Decorator 类需要实现抽象方法 doDecoration。


清单 4. 基于 TableViewer 的所有 Decorator 抽象父类
				
 public abstract class AbstractTableDecorator extends AbstractTable { 
    protected AbstractTable fMyTable; 

    public AbstractTableDecorator(AbstractTable prevTable, Object tableService) { 
        super(prevTable.getControlparent(), tableService); 
        fMyTable = prevTable; 
    } 
    protected TableColumn[] createTableColumns(Table table) { 
        return fMyTable.createTableColumns(table); 
    } 
    protected abstract void doDecoration();    
    protected void decorateForTable(){ 
        fMyTable.setTableViewer(getTableViewer()); 
        fMyTable.decorateForTable(); 
        doDecoration(); 
    } 

 } 

服务类及服务提供者类

在分析基本表格显示器类和各个具体的 Decorator 类之前,首先分析服务提供者和各种服务类,因为采用 Mediator 模式后,基本表格显示器类和 Decorator 类需要使用的数据及操作都通过服务提供者获取。

考虑到表格显示器基本类有可能不需要实现其中的所有方法,本文为其定义的服务接口为一个抽象类 TableBasicService,它定义了两个方法:Selection 发生改变时调用的服务 handleSelectionChanged 方法和双击表格时调用的服务 handleDoubleClick 方法。开发人员可以根据自己的需求,增加或减少其中的方法(注意,此时需要修改 TableServiceProvider 类中的相应方法)。


清单 5. 基于 TableViewer 的基本服务接口
				
 public abstract class TableBasicService { 
 public void handleSelectionChanged(
      SelectionChangedEvent event, IStructuredSelection selection){}    
 public void handleDoubleClick(DoubleClickEvent event){} 
 } 

表格排序的服务接口 ITableSortService 提供了一个方法 compareColumn,用于比较某列中两个对象的大小,开发人员只需提供该方法的实现,服务提供者会创建一个 Sorter 类,并且赋值给 TableViewer,实现排序功能。


清单 6. 基于 TableViewer 的排序服务接口
				
 public interface ITableSortService { 
    public int compareColumn(Viewer viewer, Object e1, Object e2, int columnIndex); 
 } 

上下文菜单的服务接口 IContextMenuService 提供两个方法:fillContextMenu 方法用于动态的把当前需要显示的菜单项加入到菜单管理器,而方法 extendMenu 则用于增强 Menu 的功能,如注册菜单事件 Menu.addMenuListener 等等。


清单 7. 基于 TableViewer 的上下文菜单服务接口
				
 public interface IContextMenuService { 
 public boolean fillContextMenu(IMenuManager manager, TableViewer tableViewer); 
 public void extendMenu(Menu menu, final TableViewer tableViewer); 
 } 

表格显示器单元格中内容超链接服务接口 IHyperLinkService,它提供了三个方法,getCellTextArray 方法用于获取某个单元格中的内容,由于我们一个单元格中有可能有多个超链接,因此,我们需要以数组的形式返回这些超链接字符串。方法 getObjectAtPosition 用于获取当前位置的超链接表示的对象,通过该对象我们可以链接到对应的位置。方法 getStringWidth 则用于获取需要超链接字符串的长度。


清单 8. 基于 TableViewer 的超链接服务接口
				
 public interface IHyperLinkService { 
    public String[] getCellTextArray(Object rowElement, int columnIndex); 
    public Object  getObjectAtPosition(Object rowElement, int columnIndex, int location); 
    public int[] getStringWidth(Shell shell, String[] strArray, int columnIndex); 
 } 

当然,我们也可以针对表格显示器基本类(BGCommonTable)提供相应的服务,如图中的 TableBasicService 类,这里不介绍它,如果读者感兴趣,可以参考前面讲的服务类设计并实现自己的类。下面分析如何使用服务提供者 TableServiceProvider 类如何同这些服务接口协同工作提供相应的服务。该类采用 singleton 模式设计,并且根据 AbstractTable 的具体类型,动态的设置当前的 TableViewer 和服务对象。下面为该类的一些关键代码。可能读者会问,为什么要使用 TableServiceProvider,而不直接通过这些服务接口直接获取相应的服务?一方面,是为了提供一个统一的接口,另一方面,我们可以把一个特定于服务的代码在 TableServiceProvider 中实现,从而减少代码冗余,如文中的 createContextMenu 方法,它提供了两种方式创建上下文菜单。


清单 9. TableViewer 服务提供类
				
 public class TableServiceProvider {    
    public static TableServiceProvider getInstance(TableViewer table, Object service){ 
        if(null == fServiceProvider){ 
            fServiceProvider = new TableServiceProvider(table, service); 
        }else{ 
            fServiceProvider.setCurrentService(table, service); 
        }        
        return fServiceProvider;        
        
    }    
    public boolean createContextMenu(
              IWorkbenchPartSite partSite, final IContextMenuService menuService){ 
        MenuManager menuMgr = new MenuManager("#PopupMenu"); //$NON-NLS-1$ 
        menuMgr.setRemoveAllWhenShown(true); 
        menuMgr.addMenuListener(new IMenuListener() { 
            public void menuAboutToShow(IMenuManager manager) { 
                menuService.fillContextMenu(manager, fTableViewer); 
            } 
        }); 
        final Menu menu = menuMgr.createContextMenu(fTableViewer.getControl()); 
        menuService.extendMenu(menu, fTableViewer); 
        fTableViewer.getControl().setMenu(menu); 
        partSite.registerContextMenu(menuMgr, fTableViewer);        
        return true; 
    }    
    public Menu createContextMenu(final IContextMenuService menuService){ 
        MenuManager menuMgr = new MenuManager(); 
        menuService.fillContextMenu(menuMgr, fTableViewer);    
        final Menu menu = menuMgr.createContextMenu(fTableViewer.getControl()); 
        menuService.extendMenu(menu, fTableViewer); 
        menu.setVisible(true);                
        return menu;        
    } 

表格显示器基本类和 Decorator 类

本框架中的表格显示器基本类的关键代码如下,其中 createTableViewer 方法用于创建表格;setInput 方法用于设置表格的输入数据;setSelectionProvider 方法用户设置 selection 提供者;如果开发人员需要保存列宽和列的顺序,那么可以实现 getTableColumnsInfo 和 setTableColumnsInfo 方法,前者用于读取出当前各列的宽度和次序,后者用于保存当前割裂的宽度和次序。开发人员可以自己决定如何存储这些数据,比如说 DialogSetting。


清单 10. 表格显示器基本类
				
 public class BGCommonTable extends AbstractTable { 

    public BGCommonTable(Composite parent, TableBasicService service) { 
        super(parent, service); 
    } 
    protected TableViewer createTableViewer(Composite parent) { 
        return new TableViewer(parent, SWT.VIRTUAL | SWT.BORDER 
           | SWT.FULL_SELECTION | SWT.H_SCROLL | SWT.V_SCROLL | SWT.MULTI); 
    } 
    protected TableColumn[] createTableColumns(Table table) { 
        TableColumnStyle[] COLUMN_IN_TABLE = new TableColumnStyle[] { 
        new TableColumnStyle(NAME_COLUMN_INDEX, SWT.LEFT, 
                NAME_COLUMN_TEXT, 300, true, true), 
        new TableColumnStyle(CONTEXT_COLUMN_INDEX, SWT.LEFT, 
                CONTEXT_COLUMN_TEXT, 400, true, true) }; 
        TableColumn[] columns = new TableColumn[COLUMN_IN_TABLE.length]; 
        for (int i = 0; i < COLUMN_IN_TABLE.length; i++) { 
            columns[i] = new TableColumn(table, COLUMN_IN_TABLE[i].style); 
            columns[i].setText(COLUMN_IN_TABLE[i].label); 
            columns[i].setWidth(COLUMN_IN_TABLE[i].width); 
            columns[i].setMoveable(COLUMN_IN_TABLE[i].moveable); 
            columns[i].setResizable(COLUMN_IN_TABLE[i].resizable); 
        } 
        return columns; 
    } 
    public void setSelectionProvider(ISelectionProvider selectionProvider) { 
        fSelectionProvider = selectionProvider; 
    } 
    public void setInput(Object input) { 
        if (input instanceof BGAssignedTermsItem[]) { 
            BGAssignedTermsItem[] objArray = (BGAssignedTermsItem[]) input; 
            clear(); 
            getTableViewer().setItemCount(0); 
            getTableViewer().setInput(objArray); 
            getTableViewer().setItemCount(objArray.length); 
        } 
    } 
    protected TableColumnInfo[] getTableColumnsInfo() {} 
    protected void setTableColumnsInfo(TableColumnInfo[] columnsInfo) {} 
 } 

下面给出各个 Decorator 的关键代码实现,TableHyperLinkDecorator 用于给表格显示器中特定列的内容可以设置超链接,当然也可以经过逻辑控制使得特定的单元格(Cell)的内容为超链接。本文给出超链接的基本思想是:首先通过 org.eclipse.jface.viewers.StyledCellLabelProvider 标签提供者类在显示表格中的内容时(调用 update(ViewerCell cell) 方法),设置需要超链接内容的字体样式(本文中把字体颜色设置为蓝色,读者可以根据自己的需求设置为其他颜色,还可以加下划线等等,可以参考类 >>);然后注册 Table 的鼠标事件,当鼠标移动到需要超链接的内容时,改变字体的样式(本文中把字体衍射设置为红色);最后,当用户点击这些超链接的后,用户可以定义相应的动作(Action)或命令(Command)进行超链接。


清单 11. 基于 TableViewer 的超链接 Decorator 类
				
 public class TableHyperLinkDecorator extends AbstractTableDecorator { 
    public TableHyperLinkDecorator(AbstractTable table, 
                          IHyperLinkService linkService, int[] array) { 
        super(table); 
        fLinkService = linkService; 
        fLinkColumnIndexArray = array; 
        fObjectSelectionViewer = new ObjectSelectionProvider(); 
    } 
    protected void doDecoration() { 
        fTable = fTableViewer.getTable(); 
        fServiceProvider = TableServiceProvider.getInstance(fTableViewer);        
        fTable.addMouseMoveListener(new MouseMoveListener() {        
            public void mouseMove(MouseEvent e) { 
                handleMouseMove(e); 
            } 
        }); 
        fTable.addMouseListener(new MouseListener() { 
            
            public void mouseDoubleClick(MouseEvent e) { 
                handleDoubleClick(e); 
            }            
            public void mouseDown(MouseEvent e) { 
                handleMouseDown(e); 
            }                
            public void mouseUp(MouseEvent e) {} 

        }); 
        fTable.addListener(SWT.EraseItem, new Listener() { 
            public void handleEvent(Event event) { 
                handleEraseItemEvent(event); 
            } 
        }); 
        fTable.addMouseTrackListener(new MouseTrackListener() {        
            public void mouseEnter(MouseEvent e) {}            
            public void mouseExit(MouseEvent e) { 
                handleMouseExit(e); 
            }            
            public void mouseHover(MouseEvent e) {} 
        }); 
    } 

下面给出 TableSortDecorator 类,用于对表格显示器的列进行排序,用户可以传递一个排序列的索引数组告诉 Decorator 哪些列需要排序,默认对所有列排序。该 Decorator 类调用服务提供者的 createSorter 获取相应的排序算法,如下为该类的部分关键代码。


清单 12. 基于 TableViewer 的排序 Decorator 类
				
 public class TableSortDecorator extends AbstractTableDecorator { 

    protected AbstractViewerSorter fSorter; 
    protected int[] fSortColumnIndexArray; 
    public TableSortDecorator(AbstractTable table, AbstractViewerSorter sorter ) { 
        this(table, sorter, null); 
    } 
    public TableSortDecorator(AbstractTable table, AbstractViewerSorter sorter, 
            int[] sortColumnIndexArray) { 
        super(table); 
        fSorter = sorter; 
        fSortColumnIndexArray = sortColumnIndexArray; 
    }    
    protected void doDecoration() { 
               Listener sortListener = new Listener() { 
            public void handleEvent(Event e) { 
                // determine new sort column and direction 
                TableColumn sortColumn = aTable.getSortColumn(); 
                TableColumn currentColumn = (TableColumn) e.widget; 
                int dir = aTable.getSortDirection(); 
                if (sortColumn == currentColumn) { 
                    dir = (dir == SWT.UP ? SWT.DOWN : SWT.UP); 
                } else { 
                    aTable.setSortColumn(currentColumn); 
                    dir = SWT.UP; 
                } 
                // sort the data based on column and direction 
                int sortIdentifier = 0; 
                
                for(int i = 0; i < len; ++i ){ 
                    if(currentColumn == columns[i]){ 
                        sortIdentifier = i; 
                        break; 
                    } 
                } 
                
                aTable.setSortDirection(dir); 
           viewer.setSorter(getTableServiceProvider().createSorter(sortIdentifier, dir)); 
            } 
        }; 

        for(int i : fSortColumnIndexArray){ 
            columns[i].addListener(SWT.Selection, sortListener); 
        } 
        for(int i : fSortColumnIndexArray){ 
            columns[i].addListener(SWT.Selection, sortListener); 
        } 
    } 
 } 

下面介绍提供上下文菜单的 Decorator,该类用于静态 / 动态的添加菜单。它提供了两种方式供实现:如果提供了 IWorkbenchPartSite,那么直接注册到该 Site,否则当用户鼠标右键按下时弹出菜单,如下所示为该类的部分关键代码。


清单 13. 基于 TableViewer 的上下文菜单 Decorator 类
				
 public class TableContextMenuDecorator extends AbstractTableDecorator { 
    protected IWorkbenchPartSite fPartSite = null; 
    private static final int MOUSE_RIGHT = 3; 
    public TableContextMenuDecorator( AbstractTable table, 
                    IContextMenuService menuService, IWorkbenchPartSite partSite) { 
        super(table, menuService); 
        fPartSite = partSite; 
    } 

    public TableContextMenuDecorator( AbstractTable table,
                       IContextMenuService menuService) { 
        this(table, menuService, null); 
    }    
    protected void doDecoration() { 
        if(null != fPartSite){ 
          getTableServiceProvider().createContextMenu(fPartSite); 
        }else{            
          getTableViewer().getTable().addMouseListener(new MouseListener(){ 
            public void mouseDoubleClick(MouseEvent e) {} 
            public void mouseDown(MouseEvent e) { 
              if(e.button == MOUSE_RIGHT){ 
                Menu menu = getTableServiceProvider().createContextMenu();  
                                      getTableViewer().getControl().setMenu(menu); 
               }                    
            } 
            public void mouseUp(MouseEvent e) {} 
            
          }); 
        } 
    } 
 } 

总结

至此为止,已经把 Eclipse JFace TableViewer 涉及的事件、接口和类进行了详细的说明,并且以实例的形式给出了基于 TableViewer 的应用框架,相信读者已经有清晰的认识。因此,如果开发人员想为 TableViewer 增加新的功能,只需基于该框架,然后添加一个服务类和 Decorator 类,并把服务注册到服务提供者 TableServiceProvider 中即可,具有非常好的应用性。


参考资料

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics