简介
为了表格的展示友好性,通常我们会给表格数据增加序号列;实际业务需求中,表格中的行对应的数据并不仅仅是展示在表格上的部分,还需要存储一些额外数据。使用默认的DefaultTableModel
有许多缺点:
- 每个表格都需要修改列标题和数据,增加序号。
- 需要自己额外维护一套逻辑来存储行展示数据之外的数据。
- 当表格行进行拖拽后,序号以及表格额外数据部分不能自动调换。
对DefaultTableModel进行扩展
自定义的RowNumberTableModel
,增加了rowDatas
成员,每次返回指定数据当前所在的行。并增加了RowOrderChangeListener
,使用者可以监听行变化事情,在行发生变更后,执行自己的业务逻辑。构造函数以及addRow
方法,都增加了Object data变量,支持给每行绑定一个数据,并提供了getRowData
方法,获取该行所绑定的数据。
public class RowNumberTableModel extends DefaultTableModel {
private List rowDatas;
private RowOrderChangeListener rowOrderChangeListener;
public RowNumberTableModel(Object[][] data, String[] columns) {
super(data, columns);
}
public <T> RowNumberTableModel(Object[][] data, String[] columns, List<T> rowDatas) {
this(data, columns);
if (rowDatas != null) {
this.rowDatas = rowDatas;
}
}
public void setRowOrderChangeListener(RowOrderChangeListener rowOrderChangeListener) {
this.rowOrderChangeListener = rowOrderChangeListener;
}
@SuppressWarnings("unchecked")
public <T> T getRowData(int rowIndex) {
if (rowDatas.size() > rowIndex) {
return (T)rowDatas.get(rowIndex);
}
return null;
}
@SuppressWarnings("unchecked")
public <T> List<T> getRowDatas() {
return rowDatas;
}
public void swapRowData(int from, int to) {
Object fromData = rowDatas.get(from);
Object toData = rowDatas.get(to);
rowDatas.set(from, toData);
rowDatas.set(to, fromData);
if (rowOrderChangeListener != null) {
rowOrderChangeListener.afterRowOrderChange(this, from, to);
}
}
@Override
public void addRow(Object[] data) {
super.addRow(data);
if (this.rowDatas != null) {
rowDatas.add(null);
}
}
public void addRow(Object[] data, Object rowData) {
super.addRow(data);
if (this.rowDatas != null) {
rowDatas.add(rowData);
}
}
@Override
public void removeRow(int row) {
super.removeRow(row);
if (rowDatas != null) {
rowDatas.remove(row);
}
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
if (columnIndex == 0) {
return rowIndex + 1;
} else {
return super.getValueAt(rowIndex, columnIndex - 1);
}
}
@Override
public int getColumnCount() {
return super.getColumnCount() + 1;
}
@Override
public String getColumnName(int column) {
if (column == 0) {
return "#";
} else {
return super.getColumnName(column - 1);
}
}
@Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
return columnIndex != 0;
}
@Override
public void setValueAt(Object value, int rowIndex, int columnIndex) {
if (columnIndex == 0) {
return;
}
super.setValueAt(value, rowIndex, columnIndex - 1);
}
@FunctionalInterface
public interface RowOrderChangeListener {
void afterRowOrderChange(RowNumberTableModel model, int from, int to);
}
自定义数据更新
编辑JTable的单元格时,系统会自动帮忙调用TableModel的setValueAt方法,对表格绑定的数据进行更新,但是我们自己扩展的行数据并不会自动被更新,有以下几种方式可以解决这个问题。
- 在自定义的TableModel类中,增加CellDataChangeListener接口,重写setValueAt方法。
public class MyTableModel extends DefaultTableModel {
private List rowDatas;
private CellDataChangeListener cellDataChangeListener;
@Override
public void setValueAt(Object value, int rowIndex, int columnIndex) {
super.setValueAt(value, rowIndex, columnIndex - 1);
Object rowData = getRowData(rowIndex);
if (this.cellDataChangeListener != null && rowData != null) {
this.cellDataChangeListener.afterCellDataChange(value, columnIndex, rowData);
}
}
@FunctionalInterface
public interface CellDataChangeListener {
void afterCellDataChange(Object value, int columnIndex, Object rowData);
}
}
- 自定义CellEditor,实现getCellEditorValue方法,在其内部对rowData进行更新。
public class TextFieldCellEditor extends DefaultCellEditor {
private final JTextField textField;
private MyRowData rowData;
private int column;
public TextFieldCellEditor() {
super(new JTextField());
this.textField = (JTextField) getComponent();
}
@Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
textField.setText(Optional.ofNullable(value).map(String::valueOf).orElse(null));
this.rowData = ((MyTableModel)table.getModel()).getRowData(row);
this.column = column;
return textField;
}
@Override
public Object getCellEditorValue() {
Object value = textField.getText();
// 更新行绑定的数据
if (column = xxx) {
this.rowData.setXxx(value);
}
return value;
}
}
可以看到,方案二导致代码复用率较低,优先还是推荐方案一通过TableModel回调的方式完成自定义行数据的更新。