前述
在java的swing开发中,JTable必然是一个常用的UI组件,而在JTable中常用的操作就是动态增删数据列。
不知道在使用JTable动态增删列的时候有没有发现,对TableColumn设置属性后再增加列会导致TableColumn的属性失效,下面就来对这个问题一探究竟。
TableColumn属性
TableColumn是JTable的列对象,通过对其设置属性(例如宽度,宽度拉伸操作等)可以控制整列的UI操作。
TableColumn常用的属性设置方法如下
方法 | 作用 |
---|---|
setHeaderValue | 设置表头 |
setHeaderRenderer | 设置自定义表头渲染器 |
setCellRenderer | 设置自定义单元格渲染器 |
setCellEditor | 设置自定义单元格编辑器 |
setWidth | 设置宽度 |
setPreferredWidth | 设置首选宽度(优先使用) |
setMinWidth | 设置最小宽度(拉伸时使用) |
setMaxWidth | 设置最大宽度(拉伸时使用) |
setResizable | 设置是否允许拉伸 |
失效场景
// 创建表对象
JTable table=new JTable();
BaseTableModel tableModel = getModel();
// 增加表列
tableModel.addColumn("列1")
// 获取列对象
TableColumn tableColumn=table.getColumnModel().getColumn(0);
// 设置列对象属性
// 设置列单元格渲染器,将此列的渲染组件全部修改为复选框
tableColumn.setCellRenderer(new TableCellRenderer(){
@Override
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus,
int row, int column){
return new JCheckBox();
}
});
// 再次添加表列
tableModel.addColumn("列2")
tableModel.addColumn("列3")
执行上述代码,会发现列1的渲染器未生效,如果将列2和列3的添加语句注释掉,则渲染器失效,可以推断出是后续添加的列导致了之前的列属性失效,但这是为什么了,让我们来一探究竟。
原因分析
对tableModel.addColumn进行跟踪分析,可以看到如下情况:
这是DefaultTableModel的addColumn方法实现
public void addColumn(Object columnName, Vector columnData) {
columnIdentifiers.addElement(columnName);
if (columnData != null) {
int columnSize = columnData.size();
if (columnSize > getRowCount()) {
dataVector.setSize(columnSize);
}
justifyRows(0, getRowCount());
int newColumn = getColumnCount() - 1;
for(int i = 0; i < columnSize; i++) {
Vector row = (Vector)dataVector.elementAt(i);
row.setElementAt(columnData.elementAt(i), newColumn);
}
}
else {
justifyRows(0, getRowCount());
}
fireTableStructureChanged();
}
上述代码中,前面都是想tableModel中增加列的扩容操作,可以看到最后有一句代码fireTableStructureChanged(),跟踪下去可以看到下列代码:
public void fireTableChanged(TableModelEvent e) {
// Guaranteed to return a non-null array
Object[] listeners = listenerList.getListenerList();
// Process the listeners last to first, notifying
// those that are interested in this event
for (int i = listeners.length-2; i>=0; i-=2) {
if (listeners[i]==TableModelListener.class) {
((TableModelListener)listeners[i+1]).tableChanged(e);
}
}
}
上述代码仅是TableModel对表结构修改后的一个事件通知,并无具体操作,那么我们继续深入,跟踪到事件通知的监听器里看看。
public void tableChanged(TableModelEvent e) {
if (e == null || e.getFirstRow() == TableModelEvent.HEADER_ROW) {
// The whole thing changed
clearSelectionAndLeadAnchor();
rowModel = null;
if (sortManager != null) {
try {
ignoreSortChange = true;
sortManager.sorter.modelStructureChanged();
} finally {
ignoreSortChange = false;
}
sortManager.allChanged();
}
if (getAutoCreateColumnsFromModel()) {
// This will effect invalidation of the JTable and JTableHeader.
createDefaultColumnsFromModel();
return;
}
resizeAndRepaint();
return;
}
// 剩余代码是针对排序修改和行修改的操作,此处忽视...
}
上面代码是跟踪到JTable的监听器方法,TableModel通过事件通知触发此方法,在这个方法中,可以看到有一句代码createDefaultColumnsFromModel(),根据字面意思是从表模型中创建默认的表列?究竟如何,还得进入细细探究。
public void createDefaultColumnsFromModel() {
TableModel m = getModel();
if (m != null) {
// Remove any current columns
TableColumnModel cm = getColumnModel();
while (cm.getColumnCount() > 0) {
cm.removeColumn(cm.getColumn(0));
}
// Create new columns from the data model info
for (int i = 0; i < m.getColumnCount(); i++) {
TableColumn newColumn = new TableColumn(i);
addColumn(newColumn);
}
}
}
public void addColumn(TableColumn aColumn) {
if (aColumn.getHeaderValue() == null) {
int modelColumn = aColumn.getModelIndex();
String columnName = getModel().getColumnName(modelColumn);
aColumn.setHeaderValue(columnName);
}
getColumnModel().addColumn(aColumn);
}
终于真相大白了,原来每一次动态增加表列,TableColumnModel都会清空之前的列结构,然后从表模型中重新读取添加新的列,而每次的添加都只会从表模型中读取列头写入,所以就导致了后续添加表列后会导致之前的表列属性失效的问题。
解决方案
如果确实有上述动态增删表列的场景,可以在Table对象外缓存一份TableColumn的属性副本,每次增删表列后,重新对表列设置属性,则可以解决该问题。
不得不说,这个问题的出现是真的坑,没想到JTable内部对表列的增删是这么实现的。