JTable的实时排序

我已经弄清楚如何正确地对JTable进行排序,但我无法弄清楚如何在更改表格单元格时自动更新排序顺序。 现在,我有这个(不可否认的很长)代码,主要基于Java Tutorial的How to Use Tables中的代码 。 我已经突出了我使用// ADDED所做的更改。 在这种情况下,新添加的值可以正确排序,但是当我进入编辑值时,即使我调用fireTableCellUpdated ,它似乎也没有fireTableCellUpdated

简而言之,当模型中的数据值发生变化时,如何让表重新排序?

 /* * Copyright (c) 1995 - 2008 Sun Microsystems, Inc. All rights reserved. * See the standard BSD license. */ package components; /* * TableSortDemo.java requires no other files. */ import java.awt.Dimension; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.ArrayList; import javax.swing.BoxLayout; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.table.AbstractTableModel; public class TableSortDemo extends JPanel { private boolean DEBUG = false; public TableSortDemo() { super(); setLayout(new BoxLayout(TableSortDemo.this, BoxLayout.PAGE_AXIS)); final MyTableModel m = new MyTableModel(); JTable table = new JTable(m); table.setPreferredScrollableViewportSize(new Dimension(500, 70)); table.setFillsViewportHeight(true); table.setAutoCreateRowSorter(true); //Create the scroll pane and add the table to it. JScrollPane scrollPane = new JScrollPane(table); //Add the scroll pane to this panel. add(scrollPane); // ADDED: button to add a value JButton addButton = new JButton("Add a new value"); addButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { m.addValue( JOptionPane.showInputDialog( TableSortDemo.this, "Value?")); } }); // ADDED button to change a value JButton setButton = new JButton("Change a value"); setButton.addActionListener(new ActionListener() { /* (non-Javadoc) * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent) */ public void actionPerformed(ActionEvent e) { m.setValueAt( JOptionPane.showInputDialog( TableSortDemo.this, "Value?"), Integer.parseInt( JOptionPane.showInputDialog( TableSortDemo.this, "Which?")), 0); } }); add(addButton); add(setButton); } class MyTableModel extends AbstractTableModel { private static final long serialVersionUID = -7053335255134714625L; private String[] columnNames = {"Column"}; // ADDED data as mutable ArrayList private ArrayList data = new ArrayList(); public MyTableModel() { data.add("Anders"); data.add("Lars"); data.add("Betty"); data.add("Anna"); data.add("Jon"); data.add("Zach"); } // ADDED public void addValue(Object v) { data.add(v.toString()); int row = data.size() - 1; fireTableRowsInserted(row, row); } public int getColumnCount() { return columnNames.length; } public int getRowCount() { return data.size(); } public String getColumnName(int col) { return columnNames[col]; } public Object getValueAt(int row, int col) { return data.get(row) + " " + row; } /* * JTable uses this method to determine the default renderer/ * editor for each cell. If we didn't implement this method, * then the last column would contain text ("true"/"false"), * rather than a check box. */ public Class getColumnClass(int c) { return String.class; } /* * Don't need to implement this method unless your table's * editable. */ public boolean isCellEditable(int row, int col) { //Note that the data/cell address is constant, //no matter where the cell appears onscreen. if (col < 2) { return false; } else { return true; } } /* * Don't need to implement this method unless your table's * data can change. */ public void setValueAt(Object value, int row, int col) { if (DEBUG) { System.out.println("Setting value at " + row + "," + col + " to " + value + " (an instance of " + value.getClass() + ")"); } data.set(row, value.toString()); // ADDED: uncommented this line, despite warnings to the contrary fireTableCellUpdated(row, col); if (DEBUG) { System.out.println("New value of data:"); printDebugData(); } } private void printDebugData() { int numRows = getRowCount(); int numCols = getColumnCount(); for (int i=0; i < numRows; i++) { System.out.print(" row " + i + ":"); for (int j=0; j < numCols; j++) { System.out.print(" " + data.get(i)); } System.out.println(); } System.out.println("--------------------------"); } } /** * Create the GUI and show it. For thread safety, * this method should be invoked from the * event-dispatching thread. */ private static void createAndShowGUI() { //Create and set up the window. JFrame frame = new JFrame("TableSortDemo"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //Create and set up the content pane. TableSortDemo newContentPane = new TableSortDemo(); newContentPane.setOpaque(true); //content panes must be opaque frame.setContentPane(newContentPane); //Display the window. frame.pack(); frame.setVisible(true); } public static void main(String[] args) { //Schedule a job for the event-dispatching thread: //creating and showing this application's GUI. javax.swing.SwingUtilities.invokeLater(new Runnable() { public void run() { createAndShowGUI(); } }); } } 

这需要两个步骤:

首先,我通过使用this而不是autoCreateRowSorter对数据更改进行了TableSorter排序:

 sorter = new TableRowSorter(m); table.setRowSorter(sorter); sorter.setSortsOnUpdates(true); 

然后,我不得不更改更新方法以更新整个表。 fireTableCellUpdatedfireTableRowsUpdated只会重绘已更新的特定行,而不是整个表(意味着你会看到一个重复出现的条目,它会在以后重新绘制后立即更改。所以,我改变了

 fireTableCellUpdated(row, col); 

 fireTableRowsUpdated(0, data.size() - 1); 

现在它正确排序,即使数据发生变化,也会保留选择。

它是JTable的一个长期存在的错误 ,在2007年报道(惊讶于它没有修复,甚至没有在jdk7中)

如果不会过多地降低性能(由于触发频繁的完整度假村),则触发所有行的更新是合理的快速修复。 对于无所畏惧的人来说,这是对JTable的局部修复 – 部分修复,因为尚未捕获所有可能的场景。 这是它从未进入JXTable的原因(或者我可能还有其他优先事项:-)

 public static class JTableRepaintOnUpdate extends JTable { private UpdateHandler beforeSort; @Override public void sorterChanged(RowSorterEvent e) { super.sorterChanged(e); maybeRepaintOnSorterChanged(e); } private void beforeUpdate(TableModelEvent e) { if (!isSorted()) return; beforeSort = new UpdateHandler(e); } private void afterUpdate() { beforeSort = null; } private void maybeRepaintOnSorterChanged(RowSorterEvent e) { if (beforeSort == null) return; if ((e == null) || (e.getType() != RowSorterEvent.Type.SORTED)) return; UpdateHandler afterSort = new UpdateHandler(beforeSort); if (afterSort.allHidden(beforeSort)) { return; } else if (afterSort.complex(beforeSort)) { repaint(); return; } int firstRow = afterSort.getFirstCombined(beforeSort); int lastRow = afterSort.getLastCombined(beforeSort); Rectangle first = getCellRect(firstRow, 0, false); first.width = getWidth(); Rectangle last = getCellRect(lastRow, 0, false); repaint(first.union(last)); } private class UpdateHandler { private int firstModelRow; private int lastModelRow; private int viewRow; private boolean allHidden; public UpdateHandler(TableModelEvent e) { firstModelRow = e.getFirstRow(); lastModelRow = e.getLastRow(); convert(); } public UpdateHandler(UpdateHandler e) { firstModelRow = e.firstModelRow; lastModelRow = e.lastModelRow; convert(); } public boolean allHidden(UpdateHandler e) { return this.allHidden && e.allHidden; } public boolean complex(UpdateHandler e) { return (firstModelRow != lastModelRow); } public int getFirstCombined(UpdateHandler e) { if (allHidden) return e.viewRow; if (e.allHidden) return viewRow; return Math.min(viewRow, e.viewRow); } public int getLastCombined(UpdateHandler e) { if (allHidden || e.allHidden) return getRowCount() - 1; return Math.max(viewRow, e.viewRow); } private void convert() { // multiple updates if (firstModelRow != lastModelRow) { // don't bother too much - calculation not guaranteed to do anything good // just check if the all changed indices are hidden allHidden = true; for (int i = firstModelRow; i <= lastModelRow; i++) { if (convertRowIndexToView(i) >= 0) { allHidden = false; break; } } viewRow = -1; return; } // single update viewRow = convertRowIndexToView(firstModelRow); allHidden = viewRow < 0; } } private boolean isSorted() { // JW: not good enough - need a way to decide if there are any sortkeys which // constitute a sort or any effective filters return getRowSorter() != null; } @Override public void tableChanged(TableModelEvent e) { if (isUpdate(e)) { beforeUpdate(e); } try { super.tableChanged(e); } finally { afterUpdate(); } } /** * Convenience method to detect dataChanged table event type. * * @param e the event to examine. * @return true if the event is of type dataChanged, false else. */ protected boolean isDataChanged(TableModelEvent e) { if (e == null) return false; return e.getType() == TableModelEvent.UPDATE && e.getFirstRow() == 0 && e.getLastRow() == Integer.MAX_VALUE; } /** * Convenience method to detect update table event type. * * @param e the event to examine. * @return true if the event is of type update and not dataChanged, false else. */ protected boolean isUpdate(TableModelEvent e) { if (isStructureChanged(e)) return false; return e.getType() == TableModelEvent.UPDATE && e.getLastRow() < Integer.MAX_VALUE; } /** * Convenience method to detect a structureChanged table event type. * @param e the event to examine. * @return true if the event is of type structureChanged or null, false else. */ protected boolean isStructureChanged(TableModelEvent e) { return e == null || e.getFirstRow() == TableModelEvent.HEADER_ROW; } } 

可能最简单的方法是调用fireTableDataChanged()而不是fireTableCellUpdated()。

你有几件事需要做。

  1. 由于表模型包装了您的集合,因此它必须是可排序的。 这意味着您的对象(行)必须实现Comparable接口,因此可以正确地对集合进行排序。
  2. 在setValueAt方法中,您必须使用Collections.sort更新相应的属性并使用该集合。 然后,显然,你必须调用fireTableDataChanged让table知道它需要重绘。
  3. 假设在添加数据时发生同样的事情。
  4. 删除数据后,您不必求助,但仍需要fireTableDataChanged
  5. 如果你的collections很大,你可以考虑最初将数据添加到适当的地方而不是诉诸。

希望这可以帮助