12.11.5 绘制单元格内容

12.11.5 绘制单元格内容

前面看到的所有表格的单元格内容都是字符串,实际上表格的单元格内容也可以是更复杂的内容,JTable使用TableCellRenderer绘制单元格,Swing为该接口提供了一个实现类:DefaultTableCellRenderer,该单元格绘制器可以绘制如下三种类型的单元格值(根据其TableModelgetColumnClass方法来决定该单元格值的类型)。

  • Icon:默认的单元格绘制器会把该类型的单元格值绘制成该Icon对象所代表的图标。
  • Boolean:默认的单元格绘制器会把该类型的单元格值绘制成复选按钮
  • Object:默认的单元格绘制器在单元格内绘制出该对象的toString()方法返回的字符串

在默认情况下,如果程序直接使用二维数组或Vector来创建JTable,程序将会使用JTable的匿名内部类或DefaultTableModel充当该表格的mode对象,这两个TableModelgetColumnClass方法的返回值都是Object。这意味着,即使该二维数组里值的类型是Icon,但由于两个默认的TableModel实现类的getColumnClass方法总是返回Object,这将导致默认的单元格绘制器把Icon值当成Object值处理——只是绘制出其toString方法返回的字符串。

为了让默认的单元格绘制器可以将Icon类型的值绘制成图标,把Boolean类型的值绘制成复选框,创建JTable时所使用的TableModel绝不能采用默认的TableModel,必须采用扩展后的TableModel类,如下所示。

1
2
3
4
5
6
7
8
9
//定义一个DefaultTableCellRenderer类的子类
class ExtendedTableModel extends DefaultTableCellRenderer
{
//重写 getco1 umnclass方法,根据每列的第一个值来返回每列真实的数据类型
public Class getColumnClass(int c){
return getValueAt(0,c).getclass();
}

}

提供了上面的ExtendedTableModel类之后,程序应该先创建ExtendedTableModel对象,再利用该对象来创建Table,这样就可以保证JTablemodel对象的getColumnClass方法会返回每列真实的数据类型,默认的单元格绘制器就会将Icon类型的单元格值绘制成图标,将Boolean类型的单元格值绘制成复选框。

自定义表格单元格绘制器

如果希望程序采用自己定制的单元格绘制器,则必须实现自己的单元格绘制器,单元格绘制器必须实现TableCellRenderer接口。与前面的TreeCellRenderer接口完全相似,该接口里也只包含一个getTableCellrendererComponent()方法,该方法返回的Component将会作为指定单元格绘制的组件。

单元格绘制器模型

Swing提供了一致的编程模型,不管是JListJTree还是JTable,它们所使用的单元格绘制器都有一致的编程模型,分别需要扩展ListCellRendererTreeCellRendererTableCellRenderer,扩展这三个基类时都需要重写getXxeCellRendererComponent()方法,该方法的返回值将作为被绘制的组件

单元格绘制器的两种方式

一旦实现了自己的单元格绘制器之后,还必须将该单元格绘制器安装到指定的JTable对象上,为指定的JTable对象安装单元格绘制器有如下两种方式。

  • 局部方式(列级):调用TableColumnsetCellRenderer()方法为指定列安装指定的单元格绘制器
  • 全局方式(表级):调用JTablesetDefaultRenderer()方法为指定的JTable对象安装单元格绘制器。setDefaultRenderer()方法需要传入两个参数,即列类型和单元格绘制器,表明指定类型的数据列才会使用该单元格绘制器。

局部绘制器优先级更高

当某一列既符合全局绘制器的规则,又符合局部绘制器的规则时,局部绘制器将会负责绘制该单元格,全局绘制器不会产生任何作用。

列头单元格绘制器

除此之外,TableColumn还包含了一个setHeaderRenderer()方法,该方法可以为指定列的列头安装单元格绘制器。

程序

下面程序提供了一个ExtendedTablemodel类,该类扩展了DefaultTableModel,重写了父类的getColumnClass()方法,该方法根据每列的第一个值来决定该列的数据类型;下面程序还提供了一个定制的单元格绘制器,它使用图标来形象地表明每个好友的性别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
import java.awt.*;
import javax.swing.*;
import javax.swing.table.*;

public class TableCellRendererTest
{
JFrame jf = new JFrame("使用单元格绘制器");
JTable table;
// 定义二维数组作为表格数据
Object[][] tableData =
{
new Object[]{"李清照" , 29 , "女"
, new ImageIcon("icon/3.gif") , true},
new Object[]{"苏格拉底", 56 , "男"
, new ImageIcon("icon/1.gif") , false},
new Object[]{"李白", 35 , "男"
, new ImageIcon("icon/4.gif") , true},
new Object[]{"弄玉", 18 , "女"
, new ImageIcon("icon/2.gif") , true},
new Object[]{"虎头" , 2 , "男"
, new ImageIcon("icon/5.gif") , false}
};
// 定义一维数据作为列标题
String[] columnTitle = {"姓名" , "年龄" , "性别"
, "主头像" , "是否中国人"};
public void init()
{
// 以二维数组和一维数组来创建一个ExtendedTableModel对象
ExtendedTableModel model = new ExtendedTableModel(columnTitle
, tableData);
// 以ExtendedTableModel来创建JTable
table = new JTable( model);
table.setRowSelectionAllowed(false);
table.setRowHeight(40);
// 获取第三列
TableColumn lastColumn = table.getColumnModel().getColumn(2);
// 对第三列采用自定义的单元格绘制器
lastColumn.setCellRenderer(new GenderTableCellRenderer());
// 将JTable对象放在JScrollPane中,并将该JScrollPane显示出来
jf.add(new JScrollPane(table));
jf.pack();
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.setVisible(true);
}
public static void main(String[] args)
{
new TableCellRendererTest().init();
}
}
class ExtendedTableModel extends DefaultTableModel
{
// 重新提供一个构造器,该构造器的实现委托给DefaultTableModel父类
public ExtendedTableModel(String[] columnNames , Object[][] cells)
{
super(cells , columnNames);
}
// 重写getColumnClass方法,根据每列的第一个值来返回其真实的数据类型
public Class getColumnClass(int c)
{
return getValueAt(0 , c).getClass();
}
}
// 定义自定义的单元格绘制器
class GenderTableCellRenderer extends JPanel
implements TableCellRenderer
{
private String cellValue;
// 定义图标的宽度和高度
final int ICON_WIDTH = 23;
final int ICON_HEIGHT = 21;
public Component getTableCellRendererComponent(JTable table
, Object value , boolean isSelected , boolean hasFocus
, int row , int column)
{
cellValue = (String)value;
// 设置选中状态下绘制边框
if (hasFocus)
{
setBorder(UIManager.getBorder("Table.focusCellHighlightBorder"));
}
else
{
setBorder(null);
}
return this;
}
// 重写paint()方法,负责绘制该单元格内容
public void paint(Graphics g)
{
// 如果表格值为"男"或"male",则绘制一个男性图标
if (cellValue.equalsIgnoreCase("男")
|| cellValue.equalsIgnoreCase("male"))
{
drawImage(g , new ImageIcon("icon/male.gif").getImage());
}
// 如果表格值为"女"或"female",则绘制一个女性图标
if (cellValue.equalsIgnoreCase("女")
|| cellValue.equalsIgnoreCase("female"))
{
drawImage(g , new ImageIcon("icon/female.gif").getImage());
}
}
// 绘制图标的方法
private void drawImage(Graphics g , Image image)
{
g.drawImage(image, (getWidth() - ICON_WIDTH ) / 2
, (getHeight() - ICON_HEIGHT) / 2 , null);
}
}

上面程序中没有直接使用二维数组和一维数组来创建JTable对象,而是采用ExtendedTableModel对象来创建JTable对象(如第一段粗体字代码所示)。ExtendedTableModel类重写了父类的getColumnClass方法,该方法将会根据每列实际的值来返回该列的类型(如第二段粗体字代码所示)。
程序提供了一个GenderTableCellRenderer类,该类实现了Tablecellrenderer接口,可以作为单元格绘制器使用。该类继承了JPanel容器,重写getTableCelIRendererComponent()方法时返回this,这表明它会使用JPanel对象作为单元格绘制器。
读者可以将ExtendedTableModel补充得更加完整——主要是将DefaultTableModel中的几个构造器重新暴露出来,以后程序中可以使用ExtendedTablemodel类作为JTablemodel类,这样创建的JTable就可以将Icon列、Boolean列绘制成图标和复选框。
运行上面程序,会看到如图12.54所示的效果。