表格(TableViewer类)

 

 



第14章  表格(TableViewer类)

TableViewer表格类是JFace组件中重要且典型的一个组件,其中涉及了JFace的众多重要概念:内容器、标签器、过滤器、排序器和修改器,这些概念对后面JFace组件特别是TreeViewer的学习非常重要。从本章也可以体会到JFace非常突出的面向对象特性。

14.1  概    述

JFace是SWT的扩展,它提供了一组功能强大的界面组件,其中包含表格、树、列表、对话框、向导对话框等,从本章之后就开始专门来介绍这些JFace组件。

表格是一种在软件系统很常见的数据表现形式,特别是基于数据库的应用系统,表格更是不可缺少的界面组件。SWT的表格组件(Table类)前面已经介绍过了,但在实际项目开发中一般还是用JFace的表格组件TableViewer比较多。TableViewer组件是在SWT的Table组件基础上采用MVC模式扩展而来的,但Table并非TableViewer的父类,从图14.1两个类的谱系图就可以看出这两个类不属于同一族系。

从下面的TableViewer类源代码可以看到,TableViewer把Table作为一个实例变量,从而实现了对Table功能的扩展。

本章就如何使用表格组件TableViewer类来展开讲解,并通过一步步地创建一个完整的表格应用实例来串起表格的知识点,实例的最后界面如图14.2所示。



14.2  创建表格并显示数据

作为起步,本节将演示如何创建一个TableViewer对象,如何用TableViewer来显示数据记录,实例运行效果如图14.3所示。


14.2.1  实例的数据模型介绍

本实例用TableViewer来显示一个数据表中的3条记录,每一条记录对应某一个人的基本资料,记录有5个字段:ID号(数值型)、姓名(字符型)、性别(布尔型)、年龄(数值型)和记录建立时间(日期型)。

如何在程序中体现和操作这些数据记录呢?在过去,像ASP、PHP这类面向过程的编程模式,人们习惯了这样操作数据:从数据库中读取数据,并不对数据做任何封装,直接将数据一条条地显示在表格中。

现在用Java这种面向对象的编程语言,应该用更规范的方式来操作数据:将数据库中的记录看作一个数据对象,用一个类来表示它,数据表的字段写成类的实例变量,这样的类在Java中叫做实体类(或称数据类)。EJB和Hibernate的数据操作方式都是这样的。

数据库与表格显示之间加上了实体类,如此一来,以前的“数据表→表格显示”方式就分成了两个步骤“数据库→实体类→表格显示”。有些习惯了以前编程方式的人也许会觉得多了一个步骤太麻烦,但其实这种方式很有好处:

表格显示的代码不再和数据库表相关。例如,将数据库由Oracle移植到MySQL时就不需要更改“数据库→实体类”这个环节的代码。

零散的字段变量统一在一个类中,程序代码结构更紧凑、清晰,有利于今后代码的维护。不要小看维护问题,很多系统做好后不敢再改,害怕改动后会牵涉到其他模块,其中原因之一就是代码结构太乱、编程不规范所致。

将数据封装在一个实体类中,在数据传递时方便许多,可以将实体类作为一个参数在方法与方法之间来回传递。

14.2.2  创建数据表的实体类

下面依照表中的字段来创建一个相应的实体类,类名为PeopleEntity,代码如下所示。


14.2.3  数据的生成

由于数据操作是分两步走:“数据库→实体类→表格显示”,实体类隔离了代码对数据库的依赖,所以“数据库→实体类”这一步就不再讲解,这部分的代码与JFace组件的使用无关紧要,也不会影响表格组件的讲解。关于TableViewer和数据库结合使用方面的内容,在后面“插件项目实战”中会有详细示例。

那么如何生成实体类的对象呢?因为数据记录和实体对象相对应,新创建的实体对象就相当于一个空记录,可以用其set方法一个个地将值设入实体对象中,这样就能得到带有数据的实体对象了。

为了今后便于扩展,将创建实体对象的方法集中在一个类中,这种专门负责创建对象的类又叫对象工厂。此类的代码如下:


程序说明:

在实际应用中,getPeoples方法可由硬性生成PeopleEntity对象,改为从数据库中取出数据后生成PeopleEntity对象。

这里的List不是SWT组件的List,而是Java的集合类java.util.List。根据实际开发情况也可以用数组或Set、Map等代替List。

List是接口,而ArrayList是实际用的类。由于其后代码是基于List接口编写的,所以换用其他List接口的实现类,如Vector、LinkedList等,而不必修改其后的代码。面向接口编程,尽量让定义类型(如List)比实际类型(如ArrayList)更宽泛些,有利于以后的修改维护。

这里new ArrayList()使用了JDK5.0的泛型功能,关于泛型可参阅www.chengang.com.cn上的Java类文章。

在数据库编程中,Java集合类起着重要作用。一定要很熟悉各集合类在特性上的差别,这样才能根据实际开发情况作出适当的选择(集合类的详细资料可查阅Java基础书籍)。

14.2.4  在表格中显示数据

在得到由List装载的包含数据信息的实体类对象后,接下来就是使用TableViewer来显示这些数据,实现过程一般要经过如下步骤:

第一步:创建一个TableViewer对象,并在构造函数中用式样设置好表格的外观,这与其他SWT组件的用法一样。

第二步:通过表格内含的Table对象设置布局方式,一般都使用TableViewer的专用布局管理器TableLayout。该布局方式将用来管理表格内的其他组件(如TableColumn表格列)。

第三步:用TableColumn类创建表格列。

第四步:设置内容器和标签器。内容器和标签器是JFace组件中的重要概念,它们分别是IStructuredContentProvider、ITableLabelProvider两个接口的实现类,它们的作用就是定义好数据应该如何在TableViewer中显示。

第五步:用TableViewer的setInput方法将数据输入到表格。就像人的嘴巴,setInput就是TableViewer的嘴巴。

图14.4是TableViewer整个数据流程的示意图。


程序代码如下(内容器和标签器写成两个单独的类):


 


 


程序说明:TableViewer的setInput方法的参数类型是Object,所以它可以接受任何类型的参数,因此在内容器中要将参数转换过来,如(List) element。但如果setInput不是List类型的参数,程序就会出错,所以最好用element instanceof List来作一下类型判断会比较稳妥,在SWT/JFace编程中很多BUG都出在这种地方。当然,本例的setInput参数定的就是List类型,不用instanceof判断直接类型转换也没什么问题。

14.3  响应鼠标双击事件

如何让TableViewer的每一行响应鼠标的双击或单击事件呢?又如何取得被选择中的记录数据呢?本节将解决这个问题。本节实例的效果如图14.5所示,双击表格中的某条记录时弹出一个提示框,框中的文字信息显示该记录的人名。


本节实例在14.2节的代码基础上修改完成(完整代码见配书光盘的TableViewer2.java文件),具体如下。

在tv.setInput(data)一句之后,添加一个自定义方法addListener(tv),在此方法中给TableViewer添加监听器。addListener方法的代码如下:


14.4  给表格加上右键菜单(Action类、ActionGroup类、MenuManager类)

本节来给表格加上如图14.6所示的右键菜单。本节实例在前两节的代码基础上修改完成(完整代码见配书光盘的TableViewer3.java文件)。


14.4.1  Action、ActionGroup、MenuManager介绍

SWT中菜单是Menu类,本书在前面章节中已经介绍过Menu类的使用,在第11章中菜单项用MenuItem类来实现。但在实际项目中,同一种功能会有多种表现形式,例如Eclipse中的“新建”功能,它会分别出现在主菜单、主工具栏、右键菜单里。如果都是用MenuItem来实现,就需要写3份类似的代码,以后也要维护3份代码。当然也可以将事件处理写成外部类来共享代码,但名称、图像以及一些其他的信息写成外部类来共享则不太方便。

JFace包中已经对以上问题提供了解决方案,JFace提供了一个Action类,它将名称、图像、动作处理程序等集成在其中,这样就可以共享这些Action来形成菜单项、工具栏按钮等。

当然在底层最后用于Menu的还是MenuItem对象,将Action转化成MenuItem是由MenuManager(菜单管理器)来完成的。MenuManager简化了菜单的创建,一旦生成了MenuManager对象,就可以通用于菜单栏、弹出菜单、工具栏下拉菜单。

另外,Action写成一个个的类会很零乱,JFace又提供了一个ActionGroup类用于统一管理Action,然后让外界程序通过ActionGroup来访问Action。当然,并非一定要使用ActionGroup类来管理Action,只是用它会更好。

14.4.2  创建Action和ActionGroup

以下代码演示了如何创建Action、ActionGroup,以及如何使用MenuManager。


14.4.3  在主程序中使用ActionGroup、MenuManager

MyActionGroup类封装了Action以及Action和菜单Menu之间的交互代码。最后,只需将以下代码加入到shell.open()语句之前即可。


程序说明:图14.7说明了在程序中是如何创建右键菜单的,在主程序生成一个MenuManager对象传给ActionGroup对象,然后再通过ActionGroup内部的createContextMenu方法生成一个菜单对象Menu,最后用Menu的add()方法将Action加入。


14.5  表格的排序(ViewerSorter类)

本节实例将实现表格的单击表头排序功能(以ID、姓名两字段的排序为例),本节实例在前面几节的代码基础上修改完成(完整代码见配书光盘的TableViewer4.java文件)。

14.5.1  编写排序器ViewerSorter

TableViewer是根据排序器ViewerSorter中的设置来进行排序的,所以编写ViewerSorter类是排序的关键。编写排序器的代码如下:


程序说明:排序器的代码虽多,但要点就两个。

q 要用MySorter类生成4个不同的排序对象:ID列的升序、ID列的降序、姓名列的升序、姓名列的降序,那么MySorter首先就要解决如何生成这4个不同的排序对象。方法就是让不同的列对应不同的int值,而int值的正负数对应升、降序,然后根据MySorter类构造函数传入的int值就可以判断生成不同的排序器对象。另外,由于MySorter是无状态类,所以多个表格可以安全地共享MySorter所提供的4个排序器对象。

排序的算法由MySorter类的compare方法负责,它实际调用的是JDK中同类型对象之间进行比较的compareTo方法,TableViewer则根据MySorter返回的int值来进行记录的排序

14.5.2  为表格列添加事件监听器

表格列是TableColumn对象,把原来新增ID列和姓名列的4句代码修改如下:


14.6  给表格加上工具栏(ToolBarManager类)

如图14.8所示,本节将给表格加上一个工具栏。本节实例在前几节的代码基础上修改完成(完整代码见配书光盘的TableViewer5.java文件)。


和14.4节给表格加上右键菜单的方法相似,也是用ActionGroup、Action类。不同的是,菜单用MenuManager,这里的工具栏则用ToolBarManager。此实例分成如下几步完成。

14.6.1  创建Action类并填充进工具栏

将图14.8中的按钮都写成一个个的Action类,关于Action类的写法在14.4节已经讲过,只需依样扩充MyActionGroup类中的Action的个数即可,而刷新按钮则和刷新菜单共用RefreshAction。MyActionGroup的代码修改示意如下:


程序说明:在表格中,界面中显示的数据和setInput(Object input)传入的input对象是分离的。也就是说如果input对象中的记录数据发生改变,要调用表格的tv.refresh()或tv.update(Object element, String[] properties)才能在界面上也显示新的数据。refresh、update两个更新界面的方法中:前者是全面更新;后者是只更新某一条记录(element)在界面上的显示,后者的第二个参数甚至还可以指定更新哪几个字段的界面显示,显然后者更新效率要高些。

对于新增记录和删除记录则TableViewer有add和remove方法可用,不过由于前面所说的界面数据和input数据分离,在tv.add、tv.remove之后,勿忘input.add、input.remove。

14.6.2  用ViewForm做布局调整

在上一步创建好ActionGroup中的Action后,接下来就是要在界面中加上工具栏。先要将布局用ViewForm类来调整一下,ViewForm也是继承自Composite的一个容器。原先表格是建立在Shell之上的,现在要在Shell上再插入一个ViewForm容器,以它为基座将工具栏和表格创建于其中,如图14.9所示。

将原主程序中的open()方法修改如下,其他代码不变:



14.7  带复选框的表格(CheckboxTableViewer类)

带复选框的表格如图14.10所示,它具有如下功能:

单击“全选”按钮时,将表格中的所有记录选中(选中复选框)。

单击“全不选”按钮时,取消所有选择(取消选中复选框)。

单击“删除”按钮时,将所有选中复选框的记录删除。


本节实例在前几节的代码基础上修改完成(完整代码见配书光盘的TableViewer6.java文件)。要完成此实例需要如下几个步骤。

14.7.1  使用表格的复选框式样

(1)在创建TableViewer对象时多加一个SWT.CHECK式样,表格变为复选框式。复选框式的表格要取得选中的记录,还需要增加一个CheckboxTableViewer对象来辅助表格的使用,因为仅TableViewer对象无法取得选中的记录。


(2)修改创建MyActionGroup的语句,将CheckboxTableViewer的ctv对象作构造函数的第二个参数传入,因为MyActionGroup中的Action需要用到此对象。


这一步完成后,因为还没有对MyActionGroup类作相应改动,Eclipse会显示错误。下面开始修改MyActionGroup类。

14.7.2  修改MyActionGroup类

在原有MyActionGroup类的代码中作如下几处修改:

新增一个用于接受CheckboxTableViewer对象的构造函数。

增加“全选”和“全不选”两个Action类,并相应修改fillActionToolBars方法。

修改“删除”的RemoveAction,改由CheckboxTableViewer来取得选中的记录。因为前几节的程序也用到RemoveAction,为了兼容,所以RemoveAction原有的处理代码还不能废弃掉。可以加一个表格是否为复选框式样的判断,以决定使用哪种删除处理代码。

具体代码如下所示:(和原代码相同部分用省略号代替)


14.8  让表格可直接编辑(CellEditor类、ICellModifier接口)

前面仅仅是显示表格数据,本节谈谈如何修改数据。本节实例效果如图14.11所示。本节实例在前几节的代码基础上修改完成(完整代码见配书光盘的TableViewer7.java文件)。


 

14.8.1  设置编辑组件CellEditor

首先在TableViewer主程序前部的变量定义区中创建一个静态公用的字符串数组,它们就是修改“姓名”列时出现在下拉框中的值。


接着给表格列添加编辑组件CellEditor,在tv.setInput(data)语句之后,加入如下程序块:


程序说明:表格设置的列别名在修改器MyCellModifier类中要用到。和设置列别名一样,设置列的CellEditor编辑组件也是用数组的方式,其数组序号和列序号一一对应。

14.8.2  创建修改器ICellModifier

修改器MyCellModifier是最重要的一个类,也是最复杂的一个类,编程时一不小心就容易出BUG。其代码如下所示:


程序说明:

当单击一个可修改表格列时,首先执行canModify方法来决定是否编辑这条记录,如果它返回true才会接着去执行getValue方法,并通过getValue方法决定编辑组件的显示值。接着用户在表格上显示的编辑组件里进行值的修改,修改完成后,将修改值传入到modify方法,在此方法中自己编程把新值更新到表格显示。

在感观上单元格编辑组件似乎是表格的一部分,但实际上它是作为单独组件叠加在表格上的,加上编辑组件种类复杂,所以才要MyCellModifier这样的类来作为编辑组件和表格组件的中间人,进行数据处理和传递。

14.9  其他使用技巧

14.9.1  表格记录的过滤

建立一个继承自ViewerFilter的类,称之为过滤器类。下面的实例建立了一个过滤器,此过滤器的作用是在表格中只显示姓名叫“陈刚”的记录。


表格使用过滤器的语句如下所示,也可以把它写入某Action的run方法中:


14.9.2  控制表格的当前选择行

可以将以下语句写在某个事件代码中,例如写在Action的run方法中。

(1)向下移动,到底后又回到第一行。


(2)向上移动,到第一行后又回到最末尾一行。


14.9.3  给表格的单元格设置背景色

如下语句将使第1行第2列的单元格背景色变为红色(要加在tv.setInput()方法后面)。


14.9.4  加快TableItem和记录之间的查找速度

用以下语句可以在TableViewer内部为数据记录和TableItem之间的映射创建一个哈希表,这样可以加快TableItem和记录之间的查找速度,这条语句必须加在setInput方法之前。


public class TableViewer extends StructuredViewer {private TableViewerImpl tableViewerImpl;private Table table; //把Table类作为一个实例变量private TableEditor tableEditor;……}