Java Swing表格多列排序

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

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

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

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

分析

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

1
2
3
4
5
6
7
8
9
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

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

// 设置自己的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

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) {
// 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方法):

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) {
// 若配置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方法)