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<PeopleEntity> getPeoples() { // 工厂的静态方法
List<PeopleEntity> list = new ArrayList<PeopleEntity>();
{ // 第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<PeopleEntity>()使用了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 instanceof List来作一下类型判断会比较稳妥,在SWT/JFace编程中很多BUG都出在这种地方。当然,本例的setInput参数定的就是List类型,不用instanceof判断直接类型转换也没什么问题。






