Java Swing表格多列排序

Java Swing JTable開(kāi)啟排序功能只需一個(gè)調(diào)用:

JTable table = new JTable();
table.setAutoCreateRowSorter(true);

但這個(gè)排序功能只支持單列排序值依,而多列排序需要自己實(shí)現(xiàn)奋渔。

本文內(nèi)容是使用sorter和renderer實(shí)現(xiàn)點(diǎn)擊表頭進(jìn)行多列排序,第一次點(diǎn)擊的列作為主排序列兽叮,后點(diǎn)擊的列作為次排序列芬骄。建議在開(kāi)始閱讀本文前可以看看官方教程《How to Use Tables》,對(duì)JTable的sorter和renderer有個(gè)概念鹦聪。

分析

TableRowSorter對(duì)象已經(jīng)提供了多列排序的功能:

TableRowSorter<TableModel> sorter 
    = new TableRowSorter<TableModel>(table.getModel());
table.setRowSorter(sorter);

List <RowSorter.SortKey> sortKeys 
    = new ArrayList<RowSorter.SortKey>();
sortKeys.add(new RowSorter.SortKey(1, SortOrder.ASCENDING));
sortKeys.add(new RowSorter.SortKey(0, SortOrder.ASCENDING));
sorter.setSortKeys(sortKeys);  // 執(zhí)行排序

上面是把第1列作為主排序列升序排序账阻,把第0列作為次排序列降序排序。

表格的表頭由JTableHeader對(duì)象維護(hù)泽本,該對(duì)象里維護(hù)著BaseTableHeaderUI對(duì)象淘太,這個(gè)對(duì)象里設(shè)置了mouseInputListener監(jiān)聽(tīng)器進(jìn)行監(jiān)聽(tīng),當(dāng)點(diǎn)擊表頭后就通知該監(jiān)聽(tīng)器調(diào)用sorter.toggleSortOrder方法進(jìn)行排序规丽。所以我們需要繼承TableRowSorter類重寫(xiě)toggleSortOrder方法實(shí)現(xiàn)自己的排序邏輯蒲牧。

另一個(gè)要考慮的就是表頭的上下箭頭顯示,用于顯示該列是升序或降序排序赌莺。

JTableHeader對(duì)象默認(rèn)使用DefaultTableCellHeaderRenderer對(duì)象作為表頭Renderer冰抢,RenderergetTableCellRendererComponent方法里設(shè)置上下箭頭圖標(biāo)并返回用于顯示表頭單元格的組件,而該方法里調(diào)用的getColumnSortOrder方法只會(huì)返回主排序列的排序順序艘狭,通過(guò)該方法的返回值只能顯示主排序列的箭頭挎扰。所以需要重寫(xiě)DefaultTableCellHeaderRenderer對(duì)象的相關(guān)方法實(shí)現(xiàn)讓多個(gè)列顯示上下箭頭,再通過(guò)JTablegetTableHeader().setDefaultRenderer(TableCellRenderer defaultRenderer)方法指定我們的表頭Renderer

實(shí)現(xiàn)

通過(guò)上面的分析巢音,我們通過(guò)編寫(xiě)自己的sorter和renderer實(shí)現(xiàn)多列排序遵倦。
代碼與相關(guān)注釋(在JDK8下測(cè)試運(yùn)行):
TableSortDemo.java

package com.test.sort;

import javax.swing.*;
import javax.swing.table.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

public class TableSortDemo extends JPanel {
    public TableSortDemo() {
        super(new GridLayout(1, 0));

        JTable table = new JTable(new AbstractTableModel() {
            private String[] columnNames = {"First Name",
                    "Last Name",
                    "Sport",
                    "# of Years",
                    "Vegetarian"};
            private Object[][] data = {
                    {"Kathy", "Smith",
                            "Snowboarding", new Integer(10), new Boolean(false)},
                    {"John", "Doe",
                            "Rowing", new Integer(3), new Boolean(true)},
                    {"Sue", "White",
                            "Knitting", new Integer(2), new Boolean(false)},
                    {"Kathy", "White",
                            "Speed reading", new Integer(20), new Boolean(true)},
                    {"Joe", "Brown",
                            "Pool", new Integer(10), new Boolean(true)}
            };

            public int getColumnCount() {
                return columnNames.length;
            }

            public int getRowCount() {
                return data.length;
            }

            public String getColumnName(int col) {
                return columnNames[col];
            }

            public Object getValueAt(int row, int col) {
                return data[row][col];
            }

            public Class getColumnClass(int c) {
                return getValueAt(0, c).getClass();
            }
        });
        table.setPreferredScrollableViewportSize(new Dimension(500, 70));
        table.setFillsViewportHeight(true);

        // 設(shè)置自己的Sorter
        MultiColumnTableRowSorter<TableModel> tableRowSorter = new MultiColumnTableRowSorter<>(table.getModel());
        table.setRowSorter(tableRowSorter);

        // 設(shè)置自己的表頭Render
        table.getTableHeader().setDefaultRenderer(new MutilColumnTableCellHeaderRenderer());

        // 設(shè)置監(jiān)聽(tīng),輸出排序列調(diào)試信息
        table.getTableHeader().addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                if (e.getClickCount() % 2 == 1 && SwingUtilities.isLeftMouseButton(e)) {
                    JTableHeader header = table.getTableHeader();
                    RowSorter sorter;
                    if ((sorter = table.getRowSorter()) != null) {
                        int columnIndex = header.columnAtPoint(e.getPoint());
                        if (columnIndex != -1) {
                            for (Object key: sorter.getSortKeys()) {
                                RowSorter.SortKey sortKey = (RowSorter.SortKey)key;
                                System.out.print(sortKey.getColumn() + ":" + sortKey.getSortOrder().name() + "  |  ");
                            }
                            System.out.println("\n--------------");
                        }
                    }
                }
            }
        });

        JScrollPane scrollPane = new JScrollPane(table);
        add(scrollPane);
    }

    private static void createAndShowGUI() {
        JFrame frame = new JFrame("TableSortDemo");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        TableSortDemo newContentPane = new TableSortDemo();
        newContentPane.setOpaque(true); //content panes must be opaque
        frame.setContentPane(newContentPane);

        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGUI();
            }
        });
    }
}

MultiColumnTableRowSorter.java

package com.test.sort;

import javax.swing.*;
import javax.swing.table.TableModel;
import javax.swing.table.TableRowSorter;
import java.util.ArrayList;
import java.util.List;

public class MultiColumnTableRowSorter<M extends TableModel> extends TableRowSorter<M> {
    public MultiColumnTableRowSorter(M model) {
        super(model);
    }

    @Override
    public void toggleSortOrder(int column) {
        checkColumn(column);
        if (isSortable(column)) {
            List<SortKey> keys = new ArrayList<SortKey>(getSortKeys());
            SortKey sortKey;
            int sortIndex;
            for (sortIndex = keys.size() - 1; sortIndex >= 0; sortIndex--) {
                if (keys.get(sortIndex).getColumn() == column) {
                    break;
                }
            }
            if (sortIndex == -1) {
                // Key doesn't exist
                sortKey = new SortKey(column, SortOrder.ASCENDING);
                keys.add(sortKey);
            }
            else {
                SortKey key = keys.get(sortIndex);
                if(key.getSortOrder() == SortOrder.ASCENDING){
                    key = new SortKey(key.getColumn(), SortOrder.DESCENDING);
                    keys.set(sortIndex, key);
                }
                else if(key.getSortOrder() == SortOrder.DESCENDING){
                    keys.remove(sortIndex);
                }
            }

            if (keys.size() > getMaxSortKeys()) {
                keys = keys.subList(getMaxSortKeys(), keys.size());
            }
            setSortKeys(keys);
        }
    }

    private void checkColumn(int column) {
        if (column < 0 || column >= getModelWrapper().getColumnCount()) {
            throw new IndexOutOfBoundsException(
                    "column beyond range of TableModel");
        }
    }
}

MutilColumnTableCellHeaderRenderer.java(因?yàn)?code>sun.swing.table.DefaultTableCellRenderer的getColumnSortOrder方法是靜態(tài)方法不能被覆蓋官撼,所以直接復(fù)制DefaultTableCellRenderer類的代碼修改getColumnSortOrder方法):

package com.test.sort;

import sun.swing.DefaultLookup;

import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.plaf.UIResource;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.JTableHeader;
import java.awt.*;
import java.io.Serializable;
import java.util.List;


public class MutilColumnTableCellHeaderRenderer extends DefaultTableCellRenderer implements UIResource {
    private boolean horizontalTextPositionSet;
    private Icon sortArrow;
    private MutilColumnTableCellHeaderRenderer.EmptyIcon emptyIcon = new MutilColumnTableCellHeaderRenderer.EmptyIcon();

    public MutilColumnTableCellHeaderRenderer() {
        this.setHorizontalAlignment(0);
    }

    public void setHorizontalTextPosition(int textPosition) {
        this.horizontalTextPositionSet = true;
        super.setHorizontalTextPosition(textPosition);
    }

    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
        Icon icon = null;
        boolean var8 = false;
        if (table != null) {
            JTableHeader header = table.getTableHeader();
            if (header != null) {
                Color var10 = null;
                Color var11 = null;
                if (hasFocus) {
                    var10 = DefaultLookup.getColor(this, this.ui, "TableHeader.focusCellForeground");
                    var11 = DefaultLookup.getColor(this, this.ui, "TableHeader.focusCellBackground");
                }

                if (var10 == null) {
                    var10 = header.getForeground();
                }

                if (var11 == null) {
                    var11 = header.getBackground();
                }

                this.setForeground(var10);
                this.setBackground(var11);
                this.setFont(header.getFont());
                var8 = header.isPaintingForPrint();
            }

            if (!var8 && table.getRowSorter() != null) {
                if (!this.horizontalTextPositionSet) {
                    this.setHorizontalTextPosition(10);
                }

                SortOrder var12 = getColumnSortOrder(table, column);
                if (var12 != null) {
                    switch(var12) {
                        case ASCENDING:
                            icon = DefaultLookup.getIcon(this, this.ui, "Table.ascendingSortIcon");
                            break;
                        case DESCENDING:
                            icon = DefaultLookup.getIcon(this, this.ui, "Table.descendingSortIcon");
                            break;
                        case UNSORTED:
                            icon = DefaultLookup.getIcon(this, this.ui, "Table.naturalSortIcon");
                    }
                }
            }
        }

        this.setText(value == null ? "" : value.toString());
        this.setIcon(icon);
        this.sortArrow = icon;
        Border var13 = null;
        if (hasFocus) {
            var13 = DefaultLookup.getBorder(this, this.ui, "TableHeader.focusCellBorder");
        }

        if (var13 == null) {
            var13 = DefaultLookup.getBorder(this, this.ui, "TableHeader.cellBorder");
        }

        this.setBorder(var13);
        return this;
    }

    public static SortOrder getColumnSortOrder(JTable table, int columnIndex) {
        SortOrder sortOrder = null;
        if (table != null && table.getRowSorter() != null) {
            List sortKeys = table.getRowSorter().getSortKeys();
            columnIndex = table.convertColumnIndexToModel(columnIndex);
            if (sortKeys.size() > 0) {
                for(Object sortKey:sortKeys){
                    if(columnIndex == ((RowSorter.SortKey)sortKey).getColumn()){
                        sortOrder = ((RowSorter.SortKey)sortKey).getSortOrder();
                        break;
                    }
                }
            }
            return sortOrder;
        } else {
            return sortOrder;
        }
    }

    public void paintComponent(Graphics var1) {
        // 若配置TableHeader.rightAlignSortArrow為true梧躺,表頭單元格里的箭頭將居右顯示
        boolean var2 = DefaultLookup.getBoolean(this, this.ui, "TableHeader.rightAlignSortArrow", false);
        if (var2 && this.sortArrow != null) {
            this.emptyIcon.width = this.sortArrow.getIconWidth();
            this.emptyIcon.height = this.sortArrow.getIconHeight();
            this.setIcon(this.emptyIcon);
            super.paintComponent(var1);
            Point var3 = this.computeIconPosition(var1);
            this.sortArrow.paintIcon(this, var1, var3.x, var3.y);
        } else {
            super.paintComponent(var1);
        }

    }

    private Point computeIconPosition(Graphics var1) {
        FontMetrics var2 = var1.getFontMetrics();
        Rectangle var3 = new Rectangle();
        Rectangle var4 = new Rectangle();
        Rectangle var5 = new Rectangle();
        Insets var6 = this.getInsets();
        var3.x = var6.left;
        var3.y = var6.top;
        var3.width = this.getWidth() - (var6.left + var6.right);
        var3.height = this.getHeight() - (var6.top + var6.bottom);
        SwingUtilities.layoutCompoundLabel(this, var2, this.getText(), this.sortArrow, this.getVerticalAlignment(), this.getHorizontalAlignment(), this.getVerticalTextPosition(), this.getHorizontalTextPosition(), var3, var5, var4, this.getIconTextGap());
        int var7 = this.getWidth() - var6.right - this.sortArrow.getIconWidth();
        int var8 = var5.y;
        return new Point(var7, var8);
    }

    private class EmptyIcon implements Icon, Serializable {
        int width;
        int height;

        private EmptyIcon() {
            this.width = 0;
            this.height = 0;
        }

        public void paintIcon(Component var1, Graphics var2, int var3, int var4) {
        }

        public int getIconWidth() {
            return this.width;
        }

        public int getIconHeight() {
            return this.height;
        }
    }
}

運(yùn)行效果:


demo.png

圖中以第0列為主排序列,第1列為次排序列進(jìn)行排序傲绣。(若需要實(shí)現(xiàn)通過(guò)顯示1掠哥、2、3表明主排序列和次排序列秃诵,重寫(xiě)RenderergetTableCellRendererComponent方法)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末续搀,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子顷链,更是在濱河造成了極大的恐慌目代,老刑警劉巖屈梁,帶你破解...
    沈念sama閱讀 221,198評(píng)論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異榛了,居然都是意外死亡在讶,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門(mén)霜大,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)构哺,“玉大人,你說(shuō)我怎么就攤上這事战坤∈锴浚” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,643評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵途茫,是天一觀的道長(zhǎng)碟嘴。 經(jīng)常有香客問(wèn)我,道長(zhǎng)囊卜,這世上最難降的妖魔是什么娜扇? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,495評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮栅组,結(jié)果婚禮上雀瓢,老公的妹妹穿的比我還像新娘。我一直安慰自己玉掸,他們只是感情好刃麸,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著司浪,像睡著了一般泊业。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上断傲,一...
    開(kāi)封第一講書(shū)人閱讀 52,156評(píng)論 1 308
  • 那天脱吱,我揣著相機(jī)與錄音智政,去河邊找鬼认罩。 笑死,一個(gè)胖子當(dāng)著我的面吹牛续捂,可吹牛的內(nèi)容都是我干的垦垂。 我是一名探鬼主播,決...
    沈念sama閱讀 40,743評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼牙瓢,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼劫拗!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起矾克,我...
    開(kāi)封第一講書(shū)人閱讀 39,659評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤页慷,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體酒繁,經(jīng)...
    沈念sama閱讀 46,200評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡滓彰,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了州袒。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片揭绑。...
    茶點(diǎn)故事閱讀 40,424評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖郎哭,靈堂內(nèi)的尸體忽然破棺而出他匪,到底是詐尸還是另有隱情,我是刑警寧澤夸研,帶...
    沈念sama閱讀 36,107評(píng)論 5 349
  • 正文 年R本政府宣布邦蜜,位于F島的核電站,受9級(jí)特大地震影響亥至,放射性物質(zhì)發(fā)生泄漏畦徘。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評(píng)論 3 333
  • 文/蒙蒙 一抬闯、第九天 我趴在偏房一處隱蔽的房頂上張望井辆。 院中可真熱鬧,春花似錦溶握、人聲如沸杯缺。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,264評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)萍肆。三九已至,卻和暖如春胀屿,著一層夾襖步出監(jiān)牢的瞬間塘揣,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,390評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工宿崭, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留亲铡,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,798評(píng)論 3 376
  • 正文 我出身青樓葡兑,卻偏偏與公主長(zhǎng)得像奖蔓,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子讹堤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評(píng)論 2 359