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
对象作为表头Renderer
,Renderer
的getTableCellRendererComponent
方法里设置上下箭头图标并返回用于显示表头单元格的组件,而该方法里调用的getColumnSortOrder
方法只会返回主排序列的排序顺序,通过该方法的返回值只能显示主排序列的箭头。所以需要重写DefaultTableCellHeaderRenderer
对象的相关方法实现让多个列显示上下箭头,再通过JTable
的getTableHeader().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.DefaultTableCellRenderer
的getColumnSortOrder
方法是静态方法不能被覆盖,所以直接复制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;
}
}
}
运行效果:
图中以第0列为主排序列,第1列为次排序列进行排序。(若需要实现通过显示1、2、3表明主排序列和次排序列,重写Renderer
的getTableCellRendererComponent
方法)