首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 程式設計入門

JTable 排序功能完整實現:TableSorter 源碼深度解析與使用指南

     編輯:Java 程式設計入門

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());

TableSorter 完整源碼 (JDK 5+ 泛型版)


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; } }
}

核心設計原理解析

1. 裝飾器模式,零數據複製