I\'ve figured out how to get a JTable
to be sorted properly, but I can\'t figure out how to get it to automatically update the sort order when a table cell is c
There are several things you have to do here.
Hope this helps
Its a long-standing bug on JTable, reported in 2007 (astonished that it isn't fixed, not even in jdk7)
Firing a update on all rows is a reasonable quick fix if it doesn't degrade performance too much (due to triggering frequent complete resorts). For the fearless, here's a partial fix on JTable - partial, because not yet all possible scenarios are captured. Which is the reason it never made it into JXTable (or maybe I had other priorities then :-)
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;
}
}
This took a two-step solution:
First I had the TableSorter sort on data change, by using this rather than autoCreateRowSorter
:
sorter = new TableRowSorter<MyTableModel>(m);
table.setRowSorter(sorter);
sorter.setSortsOnUpdates(true);
Then, I had to change the update method to update the entire table. The fireTableCellUpdated
and the fireTableRowsUpdated
would only redraw the specific rows that were updated, not the entire table (meaning you'd get a duplicate-appearing entry that changed as soon as it was redrawn later. So, I changed
fireTableCellUpdated(row, col);
to
fireTableRowsUpdated(0, data.size() - 1);
and now it sorts properly, even upon data changes, and selection is preserved.
probably the easiest way to get it sorted would be to call fireTableDataChanged() instead of fireTableCellUpdated().