昨天文章介绍JTable的TableCellEditor时提到,TableCellEditor接口中的方法暗含许多关系,如过实现不能正确建立这些关系,会导致自定义的编辑器不能正常工作。记住这些关系往往使人很苦恼。有没有办法来封装这些关系,只留下简单的编程接口?办法就是写一个TableCellEditor的子类,所谓的框架支持(Support)类,将暗含关系进行封装,将需要自定义扩展的特征留给Support子类来完成。
本文举一例子,实现如下图所示、类似于IDE界面设计工具的属性表。此例将向你演示如何自定义扩展TableCellEditor、TableCellRenderer以及TableModel,以便将这几天讲的知识串一下。

此例中属性表区别于普通表的特征包括:
1.属性值一列不同行显示不同类型的数据。
2.属性值单元格采用不同的渲染组件和编辑组件。
3.属性值单元格的编辑器和渲染组件是同一种组件。
该例子包括如下图所示八个文件:

TableCellSupport实现了TableCellEditor和TableCellRenderer两个接口,是扩展自定义渲染器及编辑器的基类。该类实现了TableCellEditor和TableCellRenderer的所有方法,封装了TableCellEditor所暗含的关系,并假定负责渲染的组件和负责编辑的组件为同一组件。org.dyno.test.impl包下的类继承该TableCellSupport类,分别实现了CheckBox、
ComboBox、
Spinner以及TextField常见类型的渲染器编辑器。要实现其他类型的自定义渲染器编辑器,可继承TableCellSupport进行扩展。
BeanProperty只是简单封装了某JavaBean属性以及渲染编辑器的对象。
BeanPropertyTable继承JTable,以BeanProperty数组作为数据源。
PropertyDemo是该例子的主类,是一个JFrame。
TableCellSupport是实现该属性表编辑器的核心类,下面是TableCellSupport的实现:
public abstract class
TableCellSupport
implements TableCellEditor, TableCellRenderer
{
//编辑器、渲染器缺省的前后背景
static Color
BACKGROUND=UIManager.getColor("TextField.background");
static Color
FOREGROUND=UIManager.getColor("TextField.foreground");
static Color
SELECTION_BACKGROUND=UIManager.getColor("TextField.selectionBackground");
static Color
SELECTION_FOREGROUND=UIManager.getColor("TextField.selectionForeground");
//渲染器、编辑器的组件,使用同一个
protected T
component;
//CellEditorListener的容器,使用WeakReference放置内存泄漏
private ArrayList>
listeners
=new ArrayList>();
//构造函数
public TableCellSupport(T
component)
{
this.component=component;
//如果是JComponent类组件,为了美观把边框去掉
if(component instanceof
JComponent)
((JComponent)component).setBorder(null);
}
//获取并配置编辑组件
public Component
getTableCellEditorComponent(JTable
table,
Object value, boolean isSelected, int row, int column)
{
//将value值设置给component,这儿调用了一个子类需要实现的方法setValueTo
setValueTo(component,
value);
//设置前后景、字体
component.setBackground(BACKGROUND);
component.setForeground(FOREGROUND);
component.setFont(table.getFont());
return component;
}
//获取当前编辑器的值,以component中的值为准
public
Object getCellEditorValue()
{
//调用了一个子类需要实现的方法getValueFrom从component获取当前正在编辑的值
return getValueFrom(component);
}
//根据事件anEvent判断是否可编辑,直接返回true,如有特殊需求子类可以覆盖改变
public boolean isCellEditable(EventObject anEvent)
{
return
true;
}
//根据事件anEvent判断是否可选,直接返回true,如有特殊需求子类可以覆盖改变
public boolean shouldSelectCell(EventObject anEvent)
{
return
true;
}
//停止编辑
public boolean
stopCellEditing()
{
try{
//调用通常子类需要覆盖的方法:checkComponentValue,该方法通过抛出异常来声明发生何中错误
checkComponentValue(component);
//通过检查,说明有效,触发事件通知编辑停止事件
fireEditingStopped();
//返回true标识成功
return
true;
}catch(Exception
e){
//说明有错,错误信息被包含在Exception的message中,显示该信息。
JOptionPane.showMessageDialog(component,
e.getMessage(), "Error Input",
JoptionPane.ERROR_MESSAGE);
//返回false标识失败
return
false;
}
}
//取消编辑
public void
cancelCellEditing()
{
//通常直接发出通知即可
fireEditingCanceled();
}
//添加CellEditorListener
public void
addCellEditorListener(CellEditorListener l)
{
listeners.add(new
WeakReference(l));
}
//删除CellEditorListener
public void
removeCellEditorListener(CellEditorListener l)
{
listeners.remove(new
WeakReference(l));
}
//获取并配置渲染组件
public Component
getTableCellRendererComponent(
JTable table, Object value, boolean
isSelected,
boolean hasFocus, int row, int column)
{
//设置组件的值
setValueTo(component,
value);
//设置字体、前后背景
component.setFont(table.getFont());
if(isSelected){
component.setBackground(SELECTION_BACKGROUND);
component.setForeground(SELECTION_FOREGROUND);
}else{
component.setBackground(BACKGROUND);
component.setForeground(FOREGROUND);
}
//返回该组件
return component;
}
//触发编辑停止操作事件,注意这儿是protected方法,允许子类调用
protected void
fireEditingStopped(){
ChangeEvent e=new
ChangeEvent(component);
for(WeakReference
ref:listeners){
CellEditorListener
l=ref.get();
l.editingStopped(e);
}
}
//触发编辑取消操作,允许子类调用
protected void
fireEditingCanceled(){
ChangeEvent e=new
ChangeEvent(component);
for(WeakReference
ref:listeners){
CellEditorListener
l=ref.get();
l.editingCanceled(e);
}
}
//检查编辑器组件component内的值是否有效,对于希望检查有效性的需要覆盖此方法
protected void checkComponentValue(T component)throws
Exception{
}
//将value设置到编辑组件component内,子类必须实现的抽像方法
protected abstract void setValueTo(T component, Object
value);
//从编辑组件component内获取正在编辑的值,子类必须实现的抽象方法
protected abstract Object getValueFrom(T
component);
}
通过封装,TableCellSupport实现了大部分自定义渲染器和编辑器的功能,继承TableCellSupport的类要实现以下方法:
1.带有以组件为参数的构造函数,还负责可能的事件注册,注册可能导致编辑完成事件的处理器。
2.setValueTo:将给定的值设置到给定的组件内
3.getValueFrom:从指定组件内获取当前编辑的值
3.如需要判断编辑器的值是否有效,还要覆盖checkComponentValue,该项为可选做项
现在它们之间不在暗含什么关系,需要实现和覆盖的方法的含义也非常清晰了。
(待续)