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
对象作为表头Renderer
,Renderer
的getTableCellRendererComponent
方法里设置上下箭头图标并返回用于显示表头单元格的组件,而该方法里调用的getColumnSortOrder
方法只会返回主排序列的排序顺序,通过该方法的返回值只能显示主排序列的箭头。所以需要重写DefaultTableCellHeaderRenderer
对象的相关方法实现让多个列显示上下箭头,再通过JTable
的getTableHeader().setDefaultRenderer(TableCellRenderer defaultRenderer)
方法指定我们的表头Renderer
)
实现 通过上面的分析,我们通过编写自己的sorter和renderer实现多列排序。 代码与相关注释(在JDK8下测试运行):TableSortDemo.java
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 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 ); MultiColumnTableRowSorter<TableModel> tableRowSorter = new MultiColumnTableRowSorter <>(table.getModel()); table.setRowSorter(tableRowSorter); 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 ); 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
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 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 ) { 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.DefaultTableCellRenderer
的getColumnSortOrder
方法是静态方法不能被覆盖,所以直接复制DefaultTableCellRenderer
类的代码修改getColumnSortOrder
方法):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 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) { 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; } } }
运行效果:
图中以第0列为主排序列,第1列为次排序列进行排序。(若需要实现通过显示1、2、3表明主排序列和次排序列,重写Renderer
的getTableCellRendererComponent
方法)