12.11.6 编辑单元格內容

12.11.6 编辑单元格內容

如果用户双击JTable表格的指定单元格,系统将会开始编辑该单元格的内容。在默认情况下,系统会使用文本框来编辑该单元格的内容,包括如图12.54所示表格的图标单元格。

与此类似的是,如果用户双击JTree的节点,默认也会采用文本框来编辑节点的内容。
但如果单元格内容不是文字内容,而是如图12.54所示的图形类型时,用户当然不希望使用文本编辑器来编辑该单元格的内容,因为这种编辑方式非常不直观,用户体验相当差。为了避免这种情况,可以实现自己的单元格编辑器,从而可以给用户提供更好的操作界面。
实现JTable的单元格编辑器应该实现TableCellEditor接口,实现JTree的节点编辑器需要实现TreeCelleditor接口,TableCellEditorTreeCelleditor这两个接口有非常紧密的联系。它们有一个共同的父接口:CellEditor;而且它们有一个共同的实现类:DefaultCellEditor

关于TableCellEditorTreeCellEditor两个接口及其实现类之间的关系如图12.55所示。

从图12.55中可以看出,SwingTableCellEditor提供了DefaultCellEditor实现类(也可作为TreeCellEditor的实现类),DefaultCellEditor类有三个构造器,它们分别使用文本框、复选框和ComboBox作为单元格编辑器,其中

  • 使用文本框编辑器是最常见的情形,
  • 如果单元格的值是Boolean类型,则系统默认使用复选框编辑器(如图12.54中最右边一列所示),这两种情形都是前面见过的情形。
  • 如果想指定某列使用JComboBox作为单元格编

实现TableCellEditor接口可以开发自己的单元格编辑器,但这种做法比较烦琐:通常会使用扩展DefaultCellEditor类的方式,这种方式比较简单。

TableCellEditor接口定义了一个getTableCellEditorComponent方法,该方法返回一个Component对象,该对象就是该单元格的编辑器。

安装单元格编辑器的两种方式

一旦实现了自己的单元格编辑器,就可以为JTable对象安装该单元格编辑器,与安装单元格绘制器类似,安装单元格编辑器也有两种方式

  • 局部方式(列级):为特定列指定单元格编辑器,通过调用TableColumnsetCellEditor()方法为该列安装单元格编辑器
  • 全局方式(表级):调用JTablesetDefaultEditor()方法为该表格安装默认的单元格编辑器。该方法需要两个参数,即列类型和单元格编辑器,这两个参数表明对于指定类型的数据列使用该单元格编辑器。

局部覆盖全局

与单元格绘制器相似的是,如果有一列同时满足列级单元格编辑器和表级单元格编辑器的要求,系统将采用列级单元格编辑器。

程序

下面程序实现了一个ImageCellEditor编辑器,该编辑器由一个不可直接编辑的文本框和一个按钮组成,当用户单击该按钮时,该编辑器弹出一个文件选择器,方便用户选择图标文件。除此之外,下面程序还创建了一个基于JComboBoxDefaultcellEditor类,该编辑器允许用户通过下拉列表来选择图标。

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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
import java.io.File;
import java.awt.*;
import javax.swing.*;
import javax.swing.table.*;
import javax.swing.filechooser.*;

public class TableCellEditorTest
{
JFrame jf = new JFrame("使用单元格编辑器");
JTable table;
// 定义二维数组作为表格数据
Object[][] tableData =
{
new Object[]{"李清照" , 29 , "女" , new ImageIcon("icon/3.gif")
, new ImageIcon("icon/3.gif") , true},
new Object[]{"苏格拉底", 56 , "男" , new ImageIcon("icon/1.gif")
, new ImageIcon("icon/1.gif") , false},
new Object[]{"李白", 35 , "男" , new ImageIcon("icon/4.gif")
, new ImageIcon("icon/4.gif") , true},
new Object[]{"弄玉", 18 , "女" , new ImageIcon("icon/2.gif")
, new ImageIcon("icon/2.gif") , true},
new Object[]{"虎头" , 2 , "男" , new ImageIcon("icon/5.gif")
, 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);
// 为该表格指定默认的编辑器
table.setDefaultEditor(ImageIcon.class, new ImageCellEditor());
// 获取第五列
TableColumn lastColumn = table.getColumnModel().getColumn(4);
// 创建JComboBox对象,并添加多个图标列表项
JComboBox<ImageIcon> editCombo = new JComboBox<>();
for (int i = 1; i <= 10; i++)
{
editCombo.addItem(new ImageIcon("icon/" + i + ".gif"));
}
// 设置第五列使用基于JComboBox的DefaultCellEditor
lastColumn.setCellEditor(new DefaultCellEditor(editCombo));
// 将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 TableCellEditorTest().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();
}
}
// 扩展DefaultCellEditor来实现TableCellEditor类
class ImageCellEditor extends DefaultCellEditor
{
// 定义文件选择器
private JFileChooser fDialog = new JFileChooser(); ;
private JTextField field = new JTextField(15);
private JButton button = new JButton("...");
public ImageCellEditor()
{
// 因为DefaultCellEditor没有无参数的构造器
// 所以这里显式调用父类有参数的构造器。
super(new JTextField());
initEditor();
}
private void initEditor()
{
field.setEditable(false);
// 为按钮添加监听器,当用户单击该按钮时,
// 系统将出现一个文件选择器让用户选择图标文件。
button.addActionListener(e -> browse());
// 为文件选择器安装文件过滤器
fDialog.addChoosableFileFilter(new FileFilter()
{
public boolean accept(File f)
{
if (f.isDirectory())
{
return true;
}
String extension = Utils.getExtension(f);
if (extension != null)
{
if (extension.equals(Utils.tiff)
|| extension.equals(Utils.tif)
|| extension.equals(Utils.gif)
|| extension.equals(Utils.jpeg)
|| extension.equals(Utils.jpg)
|| extension.equals(Utils.png))
{
return true;
}
else
{
return false;
}
}
return false;
}
public String getDescription()
{
return "有效的图片文件";
}
});
fDialog.setAcceptAllFileFilterUsed(false);
}
// 重写TableCellEditor接口的getTableCellEditorComponent方法
// 该方法返回单元格编辑器,该编辑器是一个JPanel,
// 该容器包含一个文本框和一个按钮
public Component getTableCellEditorComponent(JTable table
, Object value , boolean isSelected , int row , int column) // ①
{
this.button.setPreferredSize(new Dimension(20, 20));
JPanel panel = new JPanel();
panel.setLayout(new BorderLayout());
field.setText(value.toString());
panel.add(this.field, BorderLayout.CENTER);
panel.add(this.button, BorderLayout.EAST);
return panel;
}
public Object getCellEditorValue()
{
return new ImageIcon(field.getText());
}
private void browse()
{
// 设置、打开文件选择器
fDialog.setCurrentDirectory(new File("icon"));
int result = fDialog.showOpenDialog(null);
// 如果单击了文件选择器的“取消”按钮
if (result == JFileChooser.CANCEL_OPTION)
{
// 取消编辑
super.cancelCellEditing();
return;
}
// 如果单击了文件选择器的“确定”按钮
else
{
// 设置field的内容
field.setText("icon/" + fDialog.getSelectedFile().getName());
}
}
}
class Utils
{
public final static String jpeg = "jpeg";
public final static String jpg = "jpg";
public final static String gif = "gif";
public final static String tiff = "tiff";
public final static String tif = "tif";
public final static String png = "png";
// 获取文件扩展名的方法
public static String getExtension(File f)
{
String ext = null;
String s = f.getName();
int i = s.lastIndexOf('.');
if (i > 0 && i < s.length() - 1)
{
ext = s.substring(i + 1).toLowerCase();
}
return ext;
}
}

上面程序中实现了一个ImageCellEditor编辑器,程序中的粗体字代码将该单元格编辑器注册成Imagelcon类型的单元格编辑器,如果某一列的数据类型是ImageIcon,则默认使用该单元格编辑器。ImageCellEditor扩展了DefaultCellEditor基类,重写getTableCellEditorComponent方法返回一个JPanel,该JPanel里包含一个文本框和一个按钮。
除此之外,程序中的粗体字代码还为最后一列安装了一个基于JComboBoxDefaultCellEditor
运行上面程序,双击倒数第3列的任意单元格,开始编辑该单元格,将看到如图12.56所示的窗口。

双击第5列的任意单元格,开始编辑该单元格,将看到如图12.57所示的窗口。

通过图12.56和图12.57可以看岀,如果单元格的值需要从多个枚举值之中选择,则使用DefaultCellEditor即可。使用自定义的单元格编辑器则非常灵活,可以取得单元格编辑器的全部控制权。