Java Swing表格多列排序

Java Swing JTable开启排序功能只需一个调用:

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

但这个排序功能只支持单列排序,而多列排序需要自己实现。

本文内容是使用sorter和renderer实现点击表头进行多列排序,第一次点击的列作为主排序列,后点击的列作为次排序列。建议在开始阅读本文前可以看看官方教程《How to Use Tables》,对JTable的sorter和renderer有个概念。

分析

TableRowSorter对象已经提供了多列排序的功能:

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);  // 执行排序

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

表格的表头由JTableHeader对象维护,该对象里维护着BaseTableHeaderUI对象,这个对象里设置了mouseInputListener监听器进行监听,当点击表头后就通知该监听器调用sorter.toggleSortOrder方法进行排序。所以我们需要继承TableRowSorter类重写toggleSortOrder方法实现自己的排序逻辑。

另一个要考虑的就是表头的上下箭头显示,用于显示该列是升序或降序排序。

JTableHeader对象默认使用DefaultTableCellHeaderRenderer对象作为表头RendererRenderergetTableCellRendererComponent方法里设置上下箭头图标并返回用于显示表头单元格的组件,而该方法里调用的getColumnSortOrder方法只会返回主排序列的排序顺序,通过该方法的返回值只能显示主排序列的箭头。所以需要重写DefaultTableCellHeaderRenderer对象的相关方法实现让多个列显示上下箭头,再通过JTablegetTableHeader().setDefaultRenderer(TableCellRenderer defaultRenderer)方法指定我们的表头Renderer

实现

通过上面的分析,我们通过编写自己的sorter和renderer实现多列排序。
代码与相关注释(在JDK8下测试运行):
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);

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

        // 设置自己的表头Render
        table.getTableHeader().setDefaultRenderer(new MutilColumnTableCellHeaderRenderer());

        // 设置监听,输出排序列调试信息
        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(因为sun.swing.table.DefaultTableCellRenderergetColumnSortOrder方法是静态方法不能被覆盖,所以直接复制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;
        }
    }
}

运行效果:


demo.png

图中以第0列为主排序列,第1列为次排序列进行排序。(若需要实现通过显示1、2、3表明主排序列和次排序列,重写RenderergetTableCellRendererComponent方法)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342