Java Swing表格多列排序

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

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

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

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

分析

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

TableRowSorter sorter 
    = new TableRowSorter(table.getModel());
table.setRowSorter(sorter);

List  sortKeys 
    = new ArrayList();
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 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 extends TableRowSorter {
    public MultiColumnTableRowSorter(M model) {
        super(model);
    }

    @Override
    public void toggleSortOrder(int column) {
        checkColumn(column);
        if (isSortable(column)) {
            List keys = new ArrayList(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;
        }
    }
}

运行效果:


Java Swing表格多列排序_第1张图片
demo.png

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

你可能感兴趣的:(Java Swing表格多列排序)