表格(TableViewer类)

前面加一点自己关于TableViewer的笔记

    设置table只显示有数据的部分 放置table过大 出现过多的空行 可以通过设置table的layout。

第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功能的扩展。

public class TableViewer
 extends StructuredViewer {

private TableViewerImpl tableViewerImpl;

private Table table; //把Table类作为一个实例变量

private TableEditor tableEditor;

……

}

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

 

图14.1 谱系图

 

图14.2 本章实例的最后界面

14.2  创建表格并显示数据

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

 

图14.3 TableViewer效果图

14.2.1  实例的数据模型介绍

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

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

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

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

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

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

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

14.2.2  创建数据表的实体类

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

 

//------------- 文件名:PeopleEntity.java --------------

// 本类包含5个不同数据类型的变量,分别对应数据库表中的5个字段。变量为private型,即只能

// 由类的内部代码访问,外界只能通过这些变量相应的Setter/Getter方法来访问它们

public class PeopleEntity {

    private Long id; //唯一识别码,在数据库里常为自动递增的ID列

    private String name; //姓名

    private boolean sex; //性别 true男,flase女

    private int age; //年龄

    private Date createDate; //记录的建立日期。Date类型是java.util.Date,而不是java.sql.Date



    //以下代码为字段各自的Setter/Getter方法。参考第3.5.2节,这些方法在Eclipse可自动生成

public Long getId() { return id;}

public void setId(Long long1) {id = long1;}

public String getName() {return name;}

public void setName(String string) {name = string;}

public boolean isSex() { return sex;}

public void setSex(boolean sex) { this.sex = sex; }

public int getAge() {return age;}

public void setAge(int i) {age = i;}

public Date getCreateDate() {return createDate;}

public void setCreateDate(Date date) {createDate = date;}

}

14.2.3  数据的生成

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

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

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

 

//-----------文件名:PeopleFactory.java ----------------

//创建PeopleEntity对象的工厂,创建3个PeopleEntry对象,并装入List集合返回

public class PeopleFactory {

public static List getPeoples() { // 工厂的静态方法

List list = new ArrayList();

{ // 第1个实体类对象

PeopleEntity o = new PeopleEntity();

o.setId(new Long(1));// id字段的类型被定义成了Long,所以要转化一下

o.setName("陈刚");

o.setSex(true);

o.setAge(28);

o.setCreateDate(new Date()); // 当前日期

list.add(o);

}

{ // 第2个实体类对象

PeopleEntity o = new PeopleEntity();

o.setId(2L); // 利用JDK5.0的自动装箱功能,省了long到Long对象的转化

o.setName("周阅");

o.setSex(false);

o.setAge(18);

o.setCreateDate(new Date());

list.add(o);

}

{ // 第3个实体类对象

PeopleEntity o = new PeopleEntity();

o.setId(3L);

o.setName("陈常恩");

o.setSex(true);

o.setAge(27);

o.setCreateDate(new Date());

list.add(o);

}

return list;

}

}

程序说明:

在实际应用中,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整个数据流程的示意图。

 

图14.4 TableViewer数据流程示意图

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

 

//-------------文件名:TableViewer1.java-------------------

shell.setLayout(new FillLayout());

// 第一步:创建一个TableViewer
对象。式样:MULTI可多选、H_SCROLL有水平滚动条、V_SCROLL

// 有垂直滚动条、BORDER有边框、FULL_SELECTION整行选择

TableViewer
 tv=new TableViewer
(shell, SWT.MULTI |SWT.BORDER |SWT.FULL_SELECTION);

// 第二步:通过表格内含的Table对象设置布局方式

Table table = tv.getTable();

table.setHeaderVisible(true); // 显示表头

table.setLinesVisible(true); // 显示表格线

TableLayout layout = new TableLayout(); // 专用于表格的布局

table.setLayout(layout);

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

layout.addColumnData(new ColumnWeightData(13));// ID列宽13像素

new TableColumn(table, SWT.NONE).setText("ID号");

layout.addColumnData(new ColumnWeightData(40));

new TableColumn(table, SWT.NONE).setText("姓名");

layout.addColumnData(new ColumnWeightData(20));

new TableColumn(table, SWT.NONE).setText("性别");

layout.addColumnData(new ColumnWeightData(20));

new TableColumn(table, SWT.NONE).setText("年龄");

layout.addColumnData(new ColumnWeightData(60));

new TableColumn(table, SWT.NONE).setText("记录建立时间");

// 第四步:设置内容器和标签器

tv.setContentProvider(new TableViewerContentProvider());

tv.setLabelProvider(new TableViewerLabelProvider());

// 第五步:用TableViewer
的setInput方法将数据输入到表格

Object data = PeopleFactory.getPeoples();

tv.setInput(data);

 

 

//-------------文件名:TableViewerContentProvider.java-------------------

//内容器。由此类对输入到表格的数据进行筛选和转化。此类要实现接口的3种方法,其中           

//getElements是主要方法,另外两个方法很少用到,空实现就行了

public class TableViewerContentProvider implements IStructuredContentProvider {

// 对输入到表格的数据集合进行筛选和转化。输入的数据集全部要转化成数组,每一个数组元素

//就是一个实体类对象,也就是表格中的一条记录

public Object[] getElements(Object element) {

// 参数element就是通过setInput(Object input)输入的对象input

// 本例中输入给setInput是List集合

if (element instanceof List)// 加一个List类型判断

return ((List) element).toArray(); // 将数据集List转化为数组

else

return new Object[0]; // 如非List类型则返回一个空数组

}



// 当TableViewer
对象被关闭时触发执行此方法

public void dispose() {}



// 当TableViewer
再次调用setInput()时触发执行此方法

public void inputChanged(Viewer v, Object oldInput, Object newInput) {}

}

 

 

//-------------文件名:TableViewerLabelProvider.java-------------------

//标签器。如果说内容器是对输入表格的数据集作处理,那么标签器则是对数据集中的单个实体对象

//进行处理和转化,由标签器来决定实体对象中的字段显示在表格的哪一列中

public class TableViewerLabelProvider implements ITableLabelProvider {

//创建几个图像

private Image[] images = new Image[] { 

new Image(null, "icons/refresh.gif"), 

new Image(null, "icons/star.jpg"), 

new Image(null, "icons/moon.jpg") };

// 由此方法决定数据记录在表格的每一列显示什么文字。 element参数是一个实体类对象

// col是当前要设置的列的列号,0是第一列

public String getColumnText(Object element, int col) {

PeopleEntity o = (PeopleEntity) element; // 类型转换

if (col == 0)// 第一列要显示什么数据

return o.getId().toString();

if (col == 1)

return o.getName();

if (col == 2)

return o.isSex() ? "男" : "女";

if (col == 3)

return String.valueOf(o.getAge()); // 将int型转为String型

if (col == 4)

return o.getCreateDate().toString();

return null; // 方法可以返回空值

}



// getColumnText方法用于显示文字,本方法用于显示图片

public Image getColumnImage(Object element, int col) {

PeopleEntity o = (PeopleEntity) element;

// 只让“陈刚”这条记录显示图片

if (o.getName().equals("陈刚")||o.getName().equals("周阅")) {

if (col == 0)// 第一列要显示的图片

return images[0];

if (col == 2)//根据性别显示不同的图标

return o.isSex() ? images[1] : images[2];

}

return null; // 方法可以返回空值

}



// 当TableViewer
对象被关闭时触发执行此方法

public void dispose() {

//别忘了SWT组件的原则:自己创建,自释放

for (Image image : images) {

image.dispose();

}

}



// -------------以下方法很少使用,先不用管,让它们空实现-----------------

public boolean isLabelProperty(Object element, String property) {return false;}

public void addListener(ILabelProviderListener listener) {}

public void removeListener(ILabelProviderListener listener) {}

}

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

14.3  响应鼠标双击事件

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

 

图14.5 鼠标响应的效果图

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

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

 

private void addListener(TableViewer
 tv) {

// 鼠标双击事件监听

tv.addDoubleClickListener(new IDoubleClickListener() {

public void doubleClick(DoubleClickEvent event) {

//得到表格的选择对象,里面封装了表格中被选择的记录信息

IStructuredSelection selection = (IStructuredSelection) event.getSelection();

// 得到所选择的第一条实体对象(表格可以有多选),并进行类型转换

PeopleEntity o = (PeopleEntity) selection.getFirstElement();

// 弹出一个提示框

MessageDialog.openInformation(null, "提示", o.getName());

}

});



// 选择事件(单击)监听

tv.addSelectionChangedListener(new ISelectionChangedListener() {

public void selectionChanged(SelectionChangedEvent event) {

// 事件处理代码……(略)

}

});

}

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

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

 

图14.6 右键菜单的效果图

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。

 

//--------文件名:MyActionGroup.java----------

public class MyActionGroup extends ActionGroup {

private TableViewer
 tv;



// 在Action要使用到TableViewer
对象,所以通过构造函数把它传进来

public MyActionGroup(TableViewer
 tv) {

this.tv = tv;

}



// 生成菜单Menu,并将两个Action传入

public void fillContextMenu(IMenuManager mgr) {

// 加入两个Action对象到菜单管理器

MenuManager menuManager = (MenuManager) mgr;

menuManager.add(new OpenAction());

menuManager.add(new RefreshAction());

// 生成Menu并挂在表格table上。menu和table两个对象互为对方的参数

Table table = tv.getTable();

Menu menu = menuManager.createContextMenu(table);

table.setMenu(menu);

}



// “打开”的Action类

private class OpenAction extends Action {

public OpenAction() {setText("打开");}

public void run() {// 继承自Action的方法,动作代码写在此方法中

IStructuredSelection selection = (IStructuredSelection) tv.getSelection();

PeopleEntity o = (PeopleEntity) selection.getFirstElement();

if (o == null)

MessageDialog.openInformation(null, null, "请先选择记录");

else

MessageDialog.openInformation(null, null, o.getName());

}

}



// “刷新”的Action类

private class RefreshAction extends Action {

public RefreshAction() {setText("刷新");}

public void run() {

tv.refresh(); //表格的刷新方法,界面会重新读取数据并显示

}

}

}

14.4.3  在主程序中使用ActionGroup、MenuManager

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

 

//生成一个ActionGroup对象,并调用fillContextMenu方法将按钮注入到菜单对象中

MyActionGroup actionGroup = new MyActionGroup(tv); 

actionGroup.fillContextMenu(new MenuManager());

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

 

图14.7 程序分析图

14.5  表格的排序(ViewerSorter类)

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

14.5.1  编写排序器ViewerSorter

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

 

//------文件名:MySorter.java-----------

public class MySorter extends ViewerSorter {

// 每列对应一个不同的常量,正数表示要升序、相反数表示要降序

private static final int ID = 1;

private static final int NAME = 2;

//给外界使用排序器对象

public static final MySorter ID_ASC=new MySorter(ID);

public static final MySorter ID_DESC=new MySorter(-ID);

public static final MySorter NAME_ASC=new MySorter(NAME);

public static final MySorter NAME_DESC=new MySorter(-NAME);

// 当前所要排序的列,取自上面的ID、NAME两值或其相反数

private int sortType;



// 构造函数用private,表示不能在外部创建MySorter对象

private MySorter(int sortType) {

this.sortType = sortType;

}



// 最关键的比较方法compare,改写自ViewerSorter。方法返回值是一个int值:正数则

//obj1移到obj2之前;零则obj1和obj2的位置不动;负数则obj1移到obj2之后

public int compare(Viewer viewer, Object obj1, Object obj2) {

// 传入两条记录(实体类),然后依列给出它们的先后顺序

PeopleEntity o1 = (PeopleEntity) obj1;

PeopleEntity o2 = (PeopleEntity) obj2;

switch (sortType) {

case ID: {

Long l1 = o1.getId();

Long l2 = o2.getId();

// Long的compareTo方法返回值有三个可能值1,0,-1: 

//如l1>l2则返回1;如l1=l2则返回0;如l1<l2则返回-1

return l1.compareTo(l2);

}

case -ID: {

Long l1 = o1.getId();

Long l2 = o2.getId();

return l2.compareTo(l1);

}

case NAME: {

String s1 = o1.getName();

String s2 = o2.getName();

return s1.compareTo(s2);

}

case -NAME: {

String s1 = o1.getName();

String s2 = o2.getName();

return s2.compareTo(s1);

}

}

return 0;

}

}

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

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句代码修改如下:

 

// layout.addColumnData(new ColumnWeightData(13));// ID列宽13像素

// new TableColumn(table, SWT.NONE).setText("ID号");

// layout.addColumnData(new ColumnWeightData(40));

// new TableColumn(table, SWT.NONE).setText("姓名");



layout.addColumnData(new ColumnWeightData(13));

TableColumn col1 = new TableColumn(table, SWT.NONE);

col1.setText("ID号");

col1.addSelectionListener(new SelectionAdapter() {

boolean asc = true; // 记录上一次的排序方式,默认为升序

public void widgetSelected(SelectionEvent e) {

// asc=true则ID的升序排序器,否则用降序

tv.setSorter(asc ? MySorter.ID_ASC : MySorter.ID_DESC);

asc = !asc;// 得到下一次排序方式

}

});



layout.addColumnData(new ColumnWeightData(40));

TableColumn col2 = new TableColumn(table, SWT.NONE);

col2.setText("姓名");

col2.addSelectionListener(new SelectionAdapter() {

boolean asc = true;

public void widgetSelected(SelectionEvent e) {

tv.setSorter(asc ? MySorter.NAME_ASC : MySorter.NAME_DESC);

asc = !asc;

}

});

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

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

 

图14.8 工具栏效果图

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

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

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

 

//------文件名:MyActionGroup.java-----------

……(省略了原来的老代码)



private class AddAction extends Action {

public AddAction() {

setHoverImageDescriptor(getImageDesc("project.gif"));// 正常状态下的图标

setText("增加");

}

public void run() {

PeopleEntity o = createPeople();// 创建一个新实体对象

tv.add(o);// 增加到表格界面中

List list = (List) tv.getInput();

list.add(o); // 增加到数据模型的List容器中

// ....向数据库增加记录(略)

}

private PeopleEntity createPeople() {// 创建一个新实体对象

PeopleEntity o = new PeopleEntity();

o.setId(5L);

o.setName("新人");

o.setSex(true);

o.setAge(15);

o.setCreateDate(new Date());

return o;

}

}



private class RemoveAction extends Action {

public RemoveAction() {

setHoverImageDescriptor(getImageDesc("remove.gif"));// 正常状态下的图标

// 按钮无效状态下的图标。不设也可以,当按钮失效时会自动使正常图片变灰

setDisabledImageDescriptor(getImageDesc("disremove.gif"));

setText("删除");

}

// 这里演示了如何从表格中删除所选的多个记录

public void run() {

    IStructuredSelection s = (IStructuredSelection) tv.getSelection();// 得到选择的对象集

if (s.isEmpty()) {// 判断是否有选择

MessageDialog.openInformation(null, "提示", "请先选择");

return;

}

for (Iterator it = s.iterator(); it.hasNext();) {

PeopleEntity o = (PeopleEntity) it.next();

tv.remove(o);// 从表格界面上删除

List list = (List) tv.getInput();

list.remove(o); // 从数据模型的List容器中删除

// ....,从数据库中删除记录(略)

}

}

}



// 自定义方法。生成Action对象,并通过工具栏管理器ToolBarManager填充进工具栏

public void fillActionToolBars(ToolBarManager actionBarManager) {

// 创建Action对象,一个按钮对应一个个的Action

Action refreshAction = new RefreshAction();

Action addAction = new AddAction();

Action removeAction = new RemoveAction();

// 将按钮通过工具栏管理器ToolBarManager填充进工具栏,如果用add(action)

// 也是可以的,只不过只有文字没有图像。要显示图像需要将Action包装成

// ActionContributionItem,在这里我们将包装的处理过程写成了一个方法

actionBarManager.add(createContributionItem(refreshAction));

actionBarManager.add(createContributionItem(addAction));

actionBarManager.add(createContributionItem(removeAction));

actionBarManager.update(true);// 更新工具栏,否则工具栏不显示任何按钮

}



// 将Action包装成ActionContributionItem类的方法。实际上Action加入到

// ToolBarManager或MenuManager里时,也做了ActionContributionItem的包装,

// 大家看它ToolBarManager的add(IAction)的源代码即知

private IContributionItem createContributionItem(IAction action) {

ActionContributionItem aci = new ActionContributionItem(action);

aci.setMode(ActionContributionItem.MODE_FORCE_TEXT);// 显示图像+文字

return aci;

}



// 得到一个图像的ImageDescriptor对象

private ImageDescriptor getImageDesc(String fileName) {

try {

URL url = new URL("file:icons/" + fileName);

return ImageDescriptor.createFromURL(url);

} catch (MalformedURLException e) {

e.printStackTrace();

}

return null;

}

程序说明:在表格中,界面中显示的数据和setInput(Objectinput)传入的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()方法修改如下,其他代码不变:

 

shell.setLayout(new FillLayout());

ViewForm viewForm = new ViewForm(shell, SWT.NONE); //布局基座ViewForm

viewForm.setLayout(new FillLayout());

final TableViewer
 tv = new TableViewer
(viewForm, SW… //父容器由shell改为viewForm

//……和上一节相同的代码(省略)

//创建工具栏

ToolBar toolBar = new ToolBar(viewForm, SWT.FLAT); // 创建一个ToolBar容器

ToolBarManager toolBarManager = new ToolBarManager(toolBar); // 创建一个toolBar的管理器

actionGroup.fillActionToolBars(toolBarManager); //将Action通过toolBarManager注入ToolBar中

// 设置表格和工具栏在布局中的位置

viewForm.setContent(tv.getControl()); // 主体:表格

viewForm.setTopLeft(toolBar); // 顶端边缘:工具栏

shell.open();

 

图14.9 布局示意图

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

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

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

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

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

 

图14.10 带复选框的表格

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

14.7.1  使用表格的复选框式样

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

 

final TableViewer
 tv = new TableViewer
(viewForm, SWT.CHECK | SWT.MULTI | SWT.……

final CheckboxTableViewer ctv = new CheckboxTableViewer(tv.getTable());

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

 

MyActionGroup actionGroup = new MyActionGroup(tv, ctv);

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

14.7.2  修改MyActionGroup类

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

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

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

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

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

 

public class MyActionGroup extends ActionGroup {

    private TableViewer
 tv;

private CheckboxTableViewer ctv; //新增的语句



public MyActionGroup(TableViewer
 v) {

    this.tv = v;

}



    //新增一个构造函数

    public MyActionGroup(TableViewer
 v, CheckboxTableViewer ctv) {

        this.tv = v;

        this.ctv = ctv;

    }



…… 



    //修改原来的“删除”Action的run方法

    private class RemoveAction extends Action {

        …… 

        public void run() {

  if (ctv != null) {

     Object[ ] checkObj = ctv.getCheckedElements(); // 取得打勾的记录

if (checkObj.length == 0) {// 判断是否有勾选复选框

MessageDialog.openInformation(null, "提示", "请先勾选记录");

return;

}

for (int i = 0; i < checkObj.length; i++) {

PeopleEntity o = (PeopleEntity) checkObj[i];

ctv.remove(o);// 从表格界面上删除

List list = (List) tv.getInput();

list.remove(o);// 从数据模型的List容器中删除

// ....,从数据库中删除记录(略)

}

         } else {

IStructuredSelection s = (IStructuredSelection) tv.getSelection();

if (s.isEmpty()) {// 判断是否有选择

MessageDialog.openInformation(null, "提示", "请先选择");

return;

}

for (Iterator it = s.iterator(); it.hasNext();) {

PeopleEntity o = (PeopleEntity) it.next();

tv.remove(o);// 从表格界面上删除

List list = (List) tv.getInput();

list.remove(o); // 从数据模型的List容器中删除

// ....,从数据库中删除记录(略)

}

}            

        }

}



……



//新增的“全选”Action

    private class SelectAllAction extends Action {

        public SelectAllAction() {

            setHoverImageDescriptor(getImageDesc("selectall.gif"));

            setText("全选");

        }

        public void run() {

            if (ctv != null) ctv.setAllChecked(true); //将所有复选框打勾

        }

}



//新增的“全不选”Action

    private class DeselectAction extends Action {

        public DeselectAction() {

            setHoverImageDescriptor(getImageDesc("deselect.gif"));

            setText("全不选");

        }

        public void run() {

            if (ctv != null) ctv.setAllChecked(false); //取消所有复选框打勾

        }

    }



    //修改此方法将“全选”、“全不选”加入

    public void fillActionToolBars(ToolBarManager actionBarManager) {

        …… 

        Action selAllAction = new SelectAllAction();

        Action deselAction = new DeselectAction();

        …… 

        actionBarManager.add(createContributionItem(selAllAction));

        actionBarManager.add(createContributionItem(deselAction));

        actionBarManager.update(true); // 更新工具栏,否则工具栏不显示任何按钮

    }

}

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

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

 

图14.11 CellEditor效果图

 

14.8.1  设置编辑组件CellEditor

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

 

public static String[] NAMES = { "老张", "小红", "陈刚", "周阅", "陈常恩" };

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

 

// 定义每一列的别名

tv.setColumnProperties(new String[] { "id", "name", "sex", "age", "createdate" });

// 设置每一列的单元格编辑组件CellEditor

CellEditor[] cellEditor = new CellEditor[5];

cellEditor[0] = null;

cellEditor[1] = new ComboBoxCellEditor(tv.getTable(), NAMES, SWT.READ_ONLY);

cellEditor[2] = new CheckboxCellEditor(tv.getTable());

cellEditor[3] = new TextCellEditor(tv.getTable());

cellEditor[4] = null;

tv.setCellEditors(cellEditor);

tv.setCellModifier(new MyCellModifier(tv)); // 设置表格的修改器MyCellModifier

Text text = (Text) cellEditor[3].getControl();// 设置第4列只能输入数值

text.addVerifyListener(new VerifyListener() { // 以下代码说明参阅第8.4节“文本框”,完全一样

public void verifyText(VerifyEvent e) {

String inStr = e.text;

if (inStr.length() > 0) {

e.doit = NumberUtils.isDigits(inStr);

}

}

});

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

14.8.2  创建修改器ICellModifier

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

 

//------------- 文件名:MyCellModifier.java --------------

public class MyCellModifier implements ICellModifier {

private TableViewer
 tv;



public MyCellModifier(TableViewer
 tv) {

this.tv = tv;

}



// 判断是否可以修改某条记录的某一字段。这里返回true表示都可以修改

// 参数element是表格记录对象,也就是PeopleEntity对象

// 参数property是列别名。该值不会有CellEditor为null的列,也就是说它不可能为id,createdate

public boolean canModify(Object element, String property) {

return true;

}



// 此方法决定当单击单元格出现CellEditor时应该显示什么值。参数说明参考canModify方法

// 每种CellEditor要求返回的数据类型都是各不相同的,类型不对应就会出错

public Object getValue(Object element, String property) {

PeopleEntity o = (PeopleEntity) element;

if (property.equals("name")) {

// ComboBoxCellEditor要求返回姓名在下拉框中的索引值

return getNameIndex(o.getName());

} else if (property.equals("sex")) {

// CheckboxCellEditor要求返回当前值对应的布尔值

return o.isSex();

} else if (property.equals("age")) {

// TextCellEditor要求返回当前值对应的字符串

return String.valueOf(o.getAge());

}

throw new RuntimeException("错误的列别名:" + property);

}



private int getNameIndex(String name) {

for (int i = 0; i < TableViewer7.NAMES.length; i++) {

if (TableViewer7.NAMES[i].equals(name))

return i;

}

return -1;

}



// 从CellEditor取值得此单元格的值

// 参数element是表格行对象TableItem,其getData()方法可取得PeopleEntity

// 参数property是列别名

// 参数value是修改后的新值。每种CellEditor的value的数据类型各不相同

public void modify(Object element, String property, Object value) {

TableItem item = (TableItem) element;

PeopleEntity o = (PeopleEntity) item.getData();

// 根据新的修改值更新PeopleEntity对象的数据

if (property.equals("name")) {

// ComboBoxCellEditor的value是其索引值

Integer comboIndex = (Integer) value;

String newName = TableViewer7.NAMES[comboIndex];

o.setName(newName);

} else if (property.equals("sex")) {

// CheckboxCellEditor的value是布尔值

Boolean newValue = (Boolean) value;

o.setSex(newValue);

} else if (property.equals("age")) {

// TextCellEditor的value就是文本框里的字符

String newValue = (String) value;

if (newValue.equals(""))// 如果不修改

return;

int newAge = Integer.parseInt(newValue);

o.setAge(newAge);

} else {

throw new RuntimeException("错误的列别名:" + property);

}

// 更新对象在表格上的界面显示。也可以用tv.refresh()全面更新界面,但太浪费效率

tv.update(o, null);

}

}

程序说明:

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

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

14.9  其他使用技巧

14.9.1  表格记录的过滤

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

 

//------------- 文件名:MyFilter.java --------------

public class MyFilter extends ViewerFilter {

// 参数viewer在本例中就是TableViewer
对象

// 参数parentElement 在本例中是一个包含全部记录的Object数组

// 参数element 当前传入的记录,需要判断是否过滤它

// 返回值=false则此记录(element)不显示。true为显示

public boolean select(Viewer viewer, Object parentElement, Object element) {

PeopleEntity o = (PeopleEntity) element;

return o.getName().equals("陈刚");

}

}

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

 

tv.addFilter(new MyFilter());  //tv是TableViewer
对象

14.9.2  控制表格的当前选择行

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

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

 

Table table = tv.getTable();

int i = table.getSelectionIndex(); //取得当前所选行的序号,如没有则返回-1

table.setSelection(i + 1); //当前选择行移下一行

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

 

Table table = tv.getTable();

int i = table.getSelectionIndex();

if (i > 0) //是否超过第一行

table.setSelection(i - 1); //向上移

else {

int count = table.getItemCount();  //总的行数

table.setSelection(count - 1);

}

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

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

 

Table table = tv.getTable(); //tv是一个TableViewer
对象

TableItem item = table.getItem(0); //得到第1行

Color color =Display.getDefault().getSystemColor(SWT.COLOR_RED);//红色

item.setBackground(1, color); //设置此行的第2列为红色

table.redraw(); //重画界面

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

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

 

tv.setUseHashlookup(true);


原址:表格(TableViewer)类

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值