JTable 的编辑和表现
在文档中对JTable 解释是:用来显示和编辑规则的二维单元表。也就是说JTable的类型定义决定了它是一个规则的二维单元表,但是对于二维单元表内单元格的显示和编辑组件的选择又是极其灵活的.
有如下两个接口:
TableCellEditor
Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column);
TableCellRenderer
Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)
所有实现这两个接口的对象都可以配置到JTable.JTable自身定义了单元格的状态:表现或编辑.
当JTabel处于表现状态时它会调用
ableCellRenderer的Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)请求表现组件.
当JTabel处于编辑状态时它会调用TableCellEditor的Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column);请求编辑组件.
这样我们实现TableCellEditor,TableCellRenderer这两个接口就可以灵活的控制表格单元格的编辑和显示.
当然为了方便swing已经定义了这两个接口的默认实现,如DefaultCellEditor,DefaultTableCellRenderer.
虽然我们有了灵活控制编辑和表现的接口,但是如何控制编辑和表现状态的转换呢!
首先单元格可不可以编辑由表格的模型控制。因为通常可不可以编辑取决于数据内容,所以将它定义在模型中是合理的!
TableModel 接口有如下一个方法:
boolean isCellEditable(int rowIndex, int columnIndex) ;
JTabel会通过他来确定可不可以编辑。
在可编辑的情况下,通过JTabel的 boolean editCellAt(int row, int column) ,可以启动单元格进入编辑状态。那么如何控制从编辑状态退出呢?
这个稍微复杂一点,因为从编辑状态退出还很可能意味着要将编辑器的内容放入表格模型。
这里有一个接口
public interface CellEditor{
void addCellEditorListener(CellEditorListener l)
Object getCellEditorValue()
void removeCellEditorListener(CellEditorListener l)
void cancelCellEditing()
boolean stopCellEditing()
.................
}
TableCellEditor继承自它,也就是说swing对单元格编辑器作了更抽象的定义。因为不仅仅是表格需要编辑。这里有个监听器的注册方法,看一下CellEditorListener的定义
public interface CellEditorListener extends EventListener
{
void editingCanceled(ChangeEvent e)
void editingStopped(ChangeEvent e)
}
很显然编辑状态结束的关键在CellEditor上。你可以主动发出事件通知CellEditorListener说结束了。也可以由外部调用
void cancelCellEditing()
boolean stopCellEditing()
触发。而CellEditorListener得到通知后通常可以调用
Object getCellEditorValue()来获得编辑器的值。例如JTable 就实现了CellEditorListener,当你将你的TableCellEditor设置到JTable时,JTable就会注册上去。
有了灵活控制编辑和表现的接口,也有了控制编辑和表现状态的转换机制.不过完全从这些接口开始构建一套自己的实现,也是很累的。显然swing已经有一套比较通用的实现。
首先看一下对于控制编辑和表现状态的转换,JTable有自己的一套定义,在外部事件触发下单元格编辑和表现状态的转换,比如在单元格上双击 会使得该单元格进入编辑状态,当编辑状态的单元格失去焦点时,该单元格离开编辑状态进入表现状态。在同一时刻只有一个单元格可以进入编辑状态等。
再来看一下DefaultTableCellRenderer和DefaultCellEditor
DefaultTableCellRenderer继承JLabel实现TableCellRenderer 接口。也就是说表格通常的单元格表现都是JLabel组件。
这个实现其实有一个巧妙之处,在实现
Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
。。。。。。。。。。。
return this;
}这个方法中
最后一句return this;表明不管单元格有多少,JLabel对象一直只有一个,只是针对不同的单元格JLabel对象的状态不一样,但实例只有一个,俭省很多资源。
DefaultCellEditor继承AbstractCellEditor,而AbstractCellEditor 实现了TableCellEditor。这里顺便讲一下,swing对很多接口的实现都有这两个层次,对于default的都是一个可用的实现,而 Abstract多是抽象类,他只实现了接口的一部分,而这一部分通常都是很通用的。如果觉得default不能满足要求,而觉得实现整个接口又麻烦, Abstract的就很有用。
DefaultCellEditor有三个构造函数:
DefaultCellEditor(JCheckBox checkBox)
DefaultCellEditor(JComboBox comboBox)
DefaultCellEditor(JTextField textField)
因为编辑不像表现那么单纯,通常使用编辑器的对象(如JTable)都要获得编辑器的值,然而不同编辑器的对外接口又是非常繁多的,所以CellEditor有这样一个方法
Object getCellEditorValue() ;也就是说使用编辑器的对象(如JTable)不管实际编辑器有多繁杂,它就只通过Object getCellEditorValue()这个方法获取值。那么当你要把你的编辑器用到比如JTable上,那么就要考虑如何将你的编辑器接口适配到 JTable期望的Object getCellEditorValue() 上。
DefaultCellEditor的构造函数就是一个提供了可以将三种编辑器进行适配。其实这是一种适配器模式。也就是DefaultCellEditor可以适配三种编辑器。
忽略了一个很重要的问题,就是如何将我们的编辑器或表现器注册到JTable上?
这个看是很简单的问题,其实也并非想象当中那么简单。
先看一下JTable提供的明显的注册接口
void setCellEditor(TableCellEditor anEditor) ;
void setDefaultEditor(Class<?> columnClass, TableCellEditor editor)
void setDefaultRenderer(Class<?> columnClass, TableCellRenderer renderer)
第一个接口很显然整个表格单元格的编辑器将由这个注册的编辑器接管。
后面两个是基于数据类型进行配置的,也就是说这种数据类型的单元格编辑器将由注册的编辑器接管。
那么如何决定数据类型呢?看表格模型TableModel里有一个方法
Class<?> getColumnClass(int columnIndex);
很明显和是否可编辑一样,数据类型由模型决定。
除此之外还有另外的注册方法,那就是表格本身也是有其他元素组成,在JTable中下一级元素是列,TableColumn。它有这两个方法
void setCellEditor(TableCellEditor cellEditor)
void setCellRenderer(TableCellRenderer cellRenderer)
可以将编辑器和表现器直接注册在列上,那么这一列的编辑或表现将由你注册的东西接管。