表格(排序)
JTable的单元格和表头写完之后,关于JTable就剩下排序和过滤了,对于过滤来说,简单的直接使用Sun的RowSorter就可以了,复杂的可以集成Lucence等排序工具,对于它来说,性能比实现更重要,有时间就开个专题写,没时间就不写了,这里主要写排序.
排序从界面表现看其实就是对JTableHeader的鼠标操作,引发TableModel的数值比较,最后表现到画面的过程,知道这个过程很重要,它可以帮助我们完全实现自己的排序过程.
对于Java的JTable排序来说,主要分为两个阶段:Java1.6以前和Java1.6版本:Java1.6之前想要实现JTable的话,必须自己实现TableSorter,然后对JTableHeader去注册,很麻烦,但是可以完全自己控制;Java1.6为了方便,Sun增加了排序和过滤的类:
javax.swing.table. TableRowSorter<M extends TableModel> -à
javax.swing. DefaultRowSorter<M, I> -à
javax.swing. RowSorter<M>
我们可以直接使用了.
实现很容易,看Sun官方的例子:
RowSorter<TableModel> rowSorter = new TableRowSorter<TableModel>(table.getModel());
table.setRowSorter(rowSorter);
这是JTable就自动为你实现了过滤,它的好处是不污染任何的JTable数据和渲染,自然进行排序,结果如图:
但是对于默认的JTable来说, TableRowSorter的默认排序都是把数据当做Object来处理的,这时我们看表格的数据行:
对于年份来说,这个排序是不对的,这个时候我们必须要复写TableModel的getColumnClass方法,告诉TableRowSorter我们现在需要排序的数据类型, TableRowSorter就会根据类型给我们的列设置合适的比较器Comparator:
Collator.getInstance()
@Override
public Class<? extends Object> getColumnClass(int c) {
return getValueAt(0, c).getClass();
}
这是对JTable的排序就完成了:
但有时候还有别的情况,比如我们单元格放置的是一个特殊组件, TableRowSorter根本没有合适的比较器给我们提供,或者是基本类型但是我们不希望使用默认的比较器Comparator,这个时候我们就需要自己实现一个Comparator,然后通过TableRowSorter的setComparator设置某一列一个新的比较器.比如我们希望我们的第二列按字符串的反序排序:
TableRowSorter<TableModel> rowSorter = new TableRowSorter<TableModel>(table.getModel());
rowSorter.setComparator(1, new Comparator<String>() {
@Override
publicint compare(String str1, String str2) {
return -str1.compareTo(str2);
}
});
table.setRowSorter(rowSorter);
效果如图:
到此为止,Java1.6排序的基本使用就完成了,通过Sun的方法很简单就实现了.
Java1.6以前的JTable排序功能必须自己实现,Sun官方也提供了例子,当然在1.6里你也可以自己实现,但是自己实现必须考虑排序Model,排序状态,JTableHeader的状态和监听,还要自己写Header的Rnederer,所以不是不能用1.6的话,最好不要自己实现.先看看是如何实现的,因为是Sun官方给的例子,就大概写下,算是学习和回忆以前使用1.5自己写Sorter的日子了.
效果和1.6类似:
首先看TableSorter类,它继承AbstractTableModel,这样它也就相当于自己也是一种底层数据模型,可以记录数据和排序数据:
publicclass TableSorter extends AbstractTableModel {
然后看它的属性:
private JTableHeader tableHeader;
这个是需要排序的JTable的Header,这里需要对它进行鼠标监听和状态管理.
private MouseListener mouseListener;
private TableModelListener tableModelListener;
这两个是鼠标监听器和TableModel监听器,主要是当鼠标排序和JTabel数据变化时排序也随之变化.
private Map<Class<?>, Comparator<?>> columnComparators
= new HashMap<Class<?>, Comparator<?>>();
private List<Directive> sortingColumns
= new ArrayList<Directive>();
这连个主要是记录状态的,一个是记录列的排序比较器,一个是记录列的排序状态.
publicstaticfinal Comparator<Object> LEXICAL_COMPARATOR = new Comparator<Object>() {
publicint compare(Object o1, Object o2) {
return o1.toString().compareTo(o2.toString());
}
};
另外还提供了几种基本的排序比较器,这是String的比较器.
然后是是构造函数,
public TableSorter(TableModel tableModel) {
传入我们需要排序的JTable的TableModel,并对JTable增加事件监听和TableModel数据变化监听:
this.tableHeader.addMouseListener(mouseListener);
this.tableHeader.setDefaultRenderer(new SortableHeaderRenderer(
this.tableHeader.getDefaultRenderer()));
this.tableModel.addTableModelListener(tableModelListener);
然后处理这些监听:
privateclass MouseHandler extends MouseAdapter {
@Override
publicvoid mouseClicked(MouseEvent e) {
首先取得排序列和当前排序状态:
JTableHeader h = (JTableHeader) e.getSource();
TableColumnModel columnModel = h.getColumnModel();
int viewColumn = h.columnAtPoint(e.getPoint());
int column = columnModel.getColumn(viewColumn).getModelIndex();
int status = getSortingStatus(column);
然后判断后调用排序方法:
setSortingStatus(column, status);
TableModel数据变化监听也类似:
privateclass TableModelHandler implements TableModelListener {
@Override
publicvoid tableChanged(TableModelEvent e) {
非排序状态直接插入:
if (!isSorting()) {
clearSortingState();
fireTableChanged(e);
return;
}
排序状态比较插入:
int column = e.getColumn();
if (e.getFirstRow() == e.getLastRow()
&& column != TableModelEvent.ALL_COLUMNS
&& getSortingStatus(column) == NOT_SORTED
&& modelToView != null) {
int viewIndex = getModelToView()[e.getFirstRow()];
fireTableChanged(new TableModelEvent(TableSorter.this,
viewIndex, viewIndex, column, e.getType()));
return;
}
然后是一个行的比较器:
// Helper classes
privateclass Row implements Comparable<Object> {
它取得比较的行:
@Override
publicint compareTo(Object o) {
int row1 = modelIndex;
int row2 = ((Row) o).modelIndex;
再去的比较的值:
Object o1 = tableModel.getValueAt(row1, column);
Object o2 = tableModel.getValueAt(row2, column);
根据我们提供的构造器比较:
comparison = getComparator(column).compare(o1, o2);
其它的还有一些设置比较器,设置JTableHeader的Renderer,绘制排序的图标的方法,基本都是以前JTable会学习到的,就不写了,很多但是不难.
然后是使用,先创建我们自己构造的TableSorter:
TableSorter sorter = new TableSorter(new MyTableModel());
当做JTable的TableModel放入JTable中:
JTable table = new JTable(sorter);
还要设置一下Header,目的是使用我们自己做成的HeaderRenderer,可以出现图标
sorter.setTableHeader(table.getTableHeader());
以后就和正常JTable一样了.
最后,我们看一个我们自己实现排序比较和Renderer的例子:
工程目录如下:
其中和类是实现了Icon接口的绘制JTableHeader排序时的图片的类,
publicclass MyBlankIcon implements Icon {
publicclass MySortIcon implements Icon {
主要过程就是实现paintIcon方法绘制图片,前面写过这种三角形形式的图片绘制方法了,这里省略.
void paintIcon(Component c, Graphics g, int x, int y);
然后就是比较重要的MyTableSorter类,它继承TableRowSorter,扩充了我们自己的排序实现:
publicclass MyTableSorter extends TableRowSorter<TableModel> {
提供了三个方法,正序、逆序和无序:
/**
* Enumeration value indicating the items are sorted in increasing
*/
protectedvoid setASCSort(int colIdx) {
ArrayList<SortKey> key = new ArrayList<SortKey>();
key.add(new RowSorter.SortKey(colIdx, SortOrder.ASCENDING));
setSortKeys(key);
}
/**
* Enumeration value indicating the items are sorted in decreasing * order.
*/
protectedvoid setDESCSort(int colIdx) {
ArrayList<SortKey> key = new ArrayList<SortKey>();
key.add(new RowSorter.SortKey(colIdx, SortOrder.DESCENDING));
setSortKeys(key);
}
/**
* Enumeration value indicating the items are unordered.
*/
protectedvoid setUNSort(int colIdx) {
ArrayList<SortKey> key = new ArrayList<SortKey>();
key.add(new RowSorter.SortKey(colIdx, SortOrder.UNSORTED));
setSortKeys(key);
}
提供设置当前排序状态的方法:
protectedvoid setSorterStatus(int column, boolean CtrlDown) {
List<SortKey> SortStatus = new ArrayList<SortKey>(getSortKeys());
SortKey sortKey = null;
int sortIndex = -1;
for (sortIndex = SortStatus.size() - 1; sortIndex >= 0; sortIndex--) {
if (SortStatus.get(sortIndex).getColumn() == column) {
break;
}
}
if (sortIndex == -1) {
// Key doesn't exist
if (CtrlDown) {
sortKey = new SortKey(column, SortOrder.DESCENDING);
} else {
sortKey = new SortKey(column, SortOrder.ASCENDING);
}
SortStatus.add(0, sortKey);
} elseif (sortIndex == 0) {
// It's the primary sorting key, toggle it
SortStatus.set(0, toggle(SortStatus.get(0), CtrlDown));
} else {
// It's not the first, but was sorted on, remove old
// entry, insert as first with ascending.
SortStatus.remove(sortIndex);
if (CtrlDown) {
sortKey = new SortKey(column, SortOrder.DESCENDING);
} else {
sortKey = new SortKey(column, SortOrder.ASCENDING);
}
SortStatus.add(0, sortKey);
}
if (SortStatus.size() > getMaxSortKeys()) {
SortStatus = SortStatus.subList(0, getMaxSortKeys());
}
setSortable(column, true);
setSortKeys(SortStatus);
setSortable(column, false);
}
提供取得下一个状态的方法:
protected SortKey toggle(SortKey key, boolean CtrlDown) {
if (key.getSortOrder() == SortOrder.ASCENDING) {
returnnew SortKey(key.getColumn(), SortOrder.DESCENDING);
} elseif (key.getSortOrder() == SortOrder.DESCENDING) {
returnnew SortKey(key.getColumn(), SortOrder.UNSORTED);
} else {
returnnew SortKey(key.getColumn(), SortOrder.ASCENDING);
}
}
然后就是Renderer方法了,它继承TableCellRenderer,设置排序状态和Header状态:
publicclass MyHeaderButtonRenderer extends JButton implements
TableCellRenderer, MouseListener {
它提供一个属性存储当前列的排序状态:
/** save the header state. */
private Hashtable<Integer, Integer> state = null;
在它的鼠标事件里,设置排序和排序状态:
/**
* Invoked when the mouse button has been clicked (pressed and
* released) on a component.
*/
@Override
publicvoid mouseClicked(MouseEvent e) {
先取得鼠标事件的列,设置表现:
int col = header.columnAtPoint(e.getPoint());
int sortCol = header.getTable().convertColumnIndexToModel(col);
renderer.setPressedColumn(col);
renderer.setSelectedColumn(col);
header.repaint();
再设置排序和状态:
if (header.getTable().isEditing()) {
header.getTable().getCellEditor().stopCellEditing();
}
if (!(DOWN == renderer.getState(col))
&& !(UP == renderer.getState(col))) {
sorter.setUNSort(sortCol);
}
实现TableCellRenderer的方法设置列的Header的表现:
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
if (header != null && renderer != null) {
boolean isPressed = (column == pushedColumn);
button.getModel().setPressed(isPressed);
button.getModel().setArmed(isPressed);
}
使用也不是很麻烦:
先创建Sorter:
TableSorter tableSorter = new MyTableSorter(table);
设置某一列的比较器,和TableSorter一样:
tableSorter.setComparator(2, new MyComparator<String>());
table.setRowSorter(tableSorter);
然后是创建Renderer:
MyHeaderButtonRenderer renderer = new MyHeaderButtonRenderer();
然后设置JTable的Header的Renderer:
table.getColumnModel().getColumn(i).setHeaderRenderer(renderer);
这样就完成了.