JTable 核心未內建排序功能,Sun 官方 Tutorial 提供了 TableSorter 參考實作,支援單列/多列排序、表頭點擊切換、組合鍵多欄排序、自定義比較器、排序狀態圖標顯示。本文提供完整可編譯源碼、使用示例與核心原理深度解析。
// 1. 建立模型
TableModel myModel = new MyTableModel();
// 2. 包裝排序器
TableSorter sorter = new TableSorter(myModel);
// 3. JTable 使用排序器而非原模型
JTable table = new JTable(sorter);
// 4. 綁定表頭 (啟用點擊排序、圖標顯示)
sorter.setTableHeader(table.getTableHeader());
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.util.List;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;
/**
* TableModel 裝飾器:為任意 TableModel 添加排序功能
* 不複製數據,維護 view-to-model 索引映射
* @version 2.0 2004-02-27 (Sun Tutorial 版本)
*/
public class TableSorter extends AbstractTableModel {
protected TableModel tableModel;
public static final int DESCENDING = -1;
public static final int NOT_SORTED = 0;
public static final int ASCENDING = 1;
private static final Directive EMPTY_DIRECTIVE = new Directive(-1, NOT_SORTED);
// 預設比較器
public static final Comparator COMPARABLE_COMPARATOR = (o1, o2) -> ((Comparable) o1).compareTo(o2);
public static final Comparator LEXICAL_COMPARATOR = (o1, o2) -> o1.toString().compareTo(o2.toString());
private Row[] viewToModel;
private int[] modelToView;
private JTableHeader tableHeader;
private MouseListener mouseListener;
private TableModelListener tableModelListener;
private Map<Class, Comparator> columnComparators = new HashMap<>();
private List<Directive> sortingColumns = new ArrayList<>();
public TableSorter() {
this.mouseListener = new MouseHandler();
this.tableModelListener = new TableModelHandler();
}
public TableSorter(TableModel tableModel) {
this();
setTableModel(tableModel);
}
public TableSorter(TableModel tableModel, JTableHeader tableHeader) {
this();
setTableHeader(tableHeader);
setTableModel(tableModel);
}
// --- 模型管理 ---
private void clearSortingState() {
viewToModel = null;
modelToView = null;
}
public TableModel getTableModel() { return tableModel; }
public void setTableModel(TableModel tableModel) {
if (this.tableModel != null) {
this.tableModel.removeTableModelListener(tableModelListener);
}
this.tableModel = tableModel;
if (this.tableModel != null) {
this.tableModel.addTableModelListener(tableModelListener);
}
clearSortingState();
fireTableStructureChanged();
}
// --- 表頭綁定 ---
public JTableHeader getTableHeader() { return tableHeader; }
public void setTableHeader(JTableHeader tableHeader) {
if (this.tableHeader != null) {
this.tableHeader.removeMouseListener(mouseListener);
TableCellRenderer defaultRenderer = this.tableHeader.getDefaultRenderer();
if (defaultRenderer instanceof SortableHeaderRenderer) {
this.tableHeader.setDefaultRenderer(((SortableHeaderRenderer) defaultRenderer).tableCellRenderer);
}
}
this.tableHeader = tableHeader;
if (this.tableHeader != null) {
this.tableHeader.addMouseListener(mouseListener);
this.tableHeader.setDefaultRenderer(new SortableHeaderRenderer(this.tableHeader.getDefaultRenderer()));
}
}
// --- 排序狀態管理 ---
public boolean isSorting() { return !sortingColumns.isEmpty(); }
private Directive getDirective(int column) {
for (Directive d : sortingColumns) if (d.column == column) return d;
return EMPTY_DIRECTIVE;
}
public int getSortingStatus(int column) { return getDirective(column).direction; }
private void sortingStatusChanged() {
clearSortingState();
fireTableDataChanged();
if (tableHeader != null) tableHeader.repaint();
}
public void setSortingStatus(int column, int status) {
Directive directive = getDirective(column);
if (directive != EMPTY_DIRECTIVE) sortingColumns.remove(directive);
if (status != NOT_SORTED) sortingColumns.add(new Directive(column, status));
sortingStatusChanged();
}
protected Icon getHeaderRendererIcon(int column, int size) {
Directive directive = getDirective(column);
if (directive == EMPTY_DIRECTIVE) return null;
return new Arrow(directive.direction == DESCENDING, size, sortingColumns.indexOf(directive));
}
public void cancelSorting() {
sortingColumns.clear();
sortingStatusChanged();
}
// --- 比較器註冊 ---
public void setColumnComparator(Class<?> type, Comparator<?> comparator) {
if (comparator == null) columnComparators.remove(type);
else columnComparators.put(type, comparator);
}
protected Comparator<?> getComparator(int column) {
Class<?> columnType = tableModel.getColumnClass(column);
Comparator<?> comparator = columnComparators.get(columnType);
if (comparator != null) return comparator;
if (Comparable.class.isAssignableFrom(columnType)) return COMPARABLE_COMPARATOR;
return LEXICAL_COMPARATOR;
}
// --- 索引映射核心 ---
private Row[] getViewToModel() {
if (viewToModel == null) {
int n = tableModel.getRowCount();
viewToModel = new Row[n];
for (int i = 0; i < n; i++) viewToModel[i] = new Row(i);
if (isSorting()) Arrays.sort(viewToModel);
}
return viewToModel;
}
public int modelIndex(int viewIndex) { return getViewToModel()[viewIndex].modelIndex; }
private int[] getModelToView() {
if (modelToView == null) {
int n = getViewToModel().length;
modelToView = new int[n];
for (int i = 0; i < n; i++) modelToView[modelIndex(i)] = i;
}
return modelToView;
}
// --- TableModel 接口轉發 ---
public int getRowCount() { return tableModel == null ? 0 : tableModel.getRowCount(); }
public int getColumnCount() { return tableModel == null ? 0 : tableModel.getColumnCount(); }
public String getColumnName(int column) { return tableModel.getColumnName(column); }
public Class<?> getColumnClass(int column) { return tableModel.getColumnClass(column); }
public boolean isCellEditable(int row, int column) { return tableModel.isCellEditable(modelIndex(row), column); }
public Object getValueAt(int row, int column) { return tableModel.getValueAt(modelIndex(row), column); }
public void setValueAt(Object aValue, int row, int column) { tableModel.setValueAt(aValue, modelIndex(row), column); }
// --- 內部類 ---
private class Row implements Comparable<Row> {
int modelIndex;
Row(int index) { this.modelIndex = index; }
public int compareTo(Row o) {
int row1 = this.modelIndex, row2 = o.modelIndex;
for (Directive d : sortingColumns) {
int col = d.column;
Object v1 = tableModel.getValueAt(row1, col);
Object v2 = tableModel.getValueAt(row2, col);
int cmp = 0;
if (v1 == null && v2 == null) cmp = 0;
else if (v1 == null) cmp = -1;
else if (v2 == null) cmp = 1;
else cmp = getComparator(col).compare(v1, v2);
if (cmp != 0) return d.direction == DESCENDING ? -cmp : cmp;
}
return 0;
}
}
private class TableModelHandler implements TableModelListener {
public void tableChanged(TableModelEvent e) {
if (!isSorting()) { clearSortingState(); fireTableChanged(e); return; }
if (e.getFirstRow() == TableModelEvent.HEADER_ROW) { cancelSorting(); fireTableChanged(e); return; }
int col = e.getColumn();
if (e.getFirstRow() == e.getLastRow() && col != TableModelEvent.ALL_COLUMNS
&& getSortingStatus(col) == NOT_SORTED && modelToView != null) {
int viewIdx = getModelToView()[e.getFirstRow()];
fireTableChanged(new TableModelEvent(TableSorter.this, viewIdx, viewIdx, col, e.getType()));
return;
}
clearSortingState();
fireTableDataChanged();
}
}
private class MouseHandler extends MouseAdapter {
public void mouseClicked(MouseEvent e) {
JTableHeader h = (JTableHeader) e.getSource();
int viewCol = h.getColumnModel().getColumnIndexAtX(e.getX());
if (viewCol == -1) return;
int modelCol = h.getColumnModel().getColumn(viewCol).getModelIndex();
int status = getSortingStatus(modelCol);
if (!e.isControlDown()) cancelSorting();
status += e.isShiftDown() ? -1 : 1;
status = (status + 4) % 3 - 1; // 循環 -1,0,1
setSortingStatus(modelCol, status);
}
}
private static class Arrow implements Icon {
private boolean descending; private int size; private int priority;
Arrow(boolean descending, int size, int priority) { this.descending = descending; this.size = size; this.priority = priority; }
public void paintIcon(Component c, Graphics g, int x, int y) {
Color color = c == null ? Color.GRAY : c.getBackground();
int dx = (int) (size / 2 * Math.pow(0.8, priority));
int dy = descending ? dx : -dx;
y += 5 * size / 6 + (descending ? -dy : 0);
int shift = descending ? 1 : -1;
g.translate(x, y);
g.setColor(color.darker()); g.drawLine(dx / 2, dy, 0, 0); g.drawLine(dx / 2, dy + shift, 0, shift);
g.setColor(color.brighter()); g.drawLine(dx / 2, dy, dx, 0); g.drawLine(dx / 2, dy + shift, dx, shift);
if (descending) g.setColor(color.darker().darker()); else g.setColor(color.brighter().brighter());
g.drawLine(dx, 0, 0, 0);
g.setColor(color); g.translate(-x, -y);
}
public int getIconWidth() { return size; }
public int getIconHeight() { return size; }
}
private class SortableHeaderRenderer implements TableCellRenderer {
private TableCellRenderer tableCellRenderer;
SortableHeaderRenderer(TableCellRenderer r) { this.tableCellRenderer = r; }
public Component getTableCellRendererComponent(JTable table, Object value, boolean sel, boolean focus, int row, int col) {
Component c = tableCellRenderer.getTableCellRendererComponent(table, value, sel, focus, row, col);
if (c instanceof JLabel) {
JLabel l = (JLabel) c;
l.setHorizontalTextPosition(JLabel.LEFT);
int modelCol = table.convertColumnIndexToModel(col);
l.setIcon(getHeaderRendererIcon(modelCol, l.getFont().getSize()));
}
return c;
}
}
private static class Directive { int column, direction; Directive(int c, int d) { column = c; direction = d; } }
}
TableModel 引用viewToModel (View 行號 → Model 行號) 映射陣列getValueAt 等調用轉發前經 modelIndex(viewRow) 轉換sortingColumns:有序列表,每個 Directive 含欄索引與方向viewToModel 首次訪問時構建並排序 (Arrays.sort)clearSortingState() 失效快取modelToView 反向索引按需構建,用於事件精確轉發| 事件類型 | 處理策略 |
|---|---|
| 結構變更 (HEADER_ROW) | 取消排序、轉發結構變更事件 |
| 單儲存格變更、非排序欄、有反向索引 | 精確映射到 View 行、轉發細粒度事件 (效能優化) |
| 其他 (插入/刪除/排序欄變更/無反向索引) | 失效快取、轉發 fireTableDataChanged() (全量刷新) |