JTable Direct Sort

I figured out how to sort JTable , but I can't figure out how to get it to automatically update the sort order when the table cell changes. Right now I have this (albeit long) code, mainly based on what in the Java tutorial How to use tables . I highlighted the changes I made using // ADDED . In this case, the newly added values ​​are sorted properly, but when I enter to edit the value, it does not seem to come fireTableCellUpdated , although I call fireTableCellUpdated ?

In short, how can I get a table for re-sorting when the data value in the model changes?

 /* * 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<String> data = new ArrayList<String>(); 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<String> 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 GUI. javax.swing.SwingUtilities.invokeLater(new Runnable() { public void run() { createAndShowGUI(); } }); } } 
+6
java sorting model-view-controller swing jtable
source share
4 answers

This required a two-stage solution:

First, I had the TableSorter sort when changing data, using this instead of autoCreateRowSorter :

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

Then I had to change the update method to update the whole table. fireTableCellUpdated and fireTableRowsUpdated will only redraw certain rows that have been updated, not the entire table (this means that you will get a duplicate record that changed as soon as it was redrawn. So, I changed

 fireTableCellUpdated(row, col); 

to

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

and now it is sorted correctly even when the data changes, and the selection is saved.

+15
source share

Its a long-standing JTable bug reported in 2007 (it struck that it wasn’t fixed, even in jdk7)

Dismissing an update for all rows is a smart quick fix if it doesn't degrade performance too much (due to the launch of frequent full resorts). For fearless, here the partial fix for JTable is partial, because not all possible scenarios are captured yet. For what reason it never got into JXTable (or maybe I had other priorities :-)

 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; } } 
+4
source share

Probably the easiest way to get it sorted is to call fireTableDataChanged () instead of fireTableCellUpdated ().

+1
source share

Here you need to do a few things.

  • Since the table model wraps your collection, it must be sortable. This means that your object (string) must implement the Comparable interface, so the collection can be sorted correctly.
  • In your setValueAt method, you need to update the corresponding attribute and start the collection using Collections.sort. Then, obviously, you need to call fireTableDataChanged to tell the table to redraw it.
  • The same thing happens with the addition of data.
  • When the data is deleted, you do not need to resort, but you still have to run TableDataChanged
  • If your collection will be large, you might consider adding data to the appropriate place first, rather than resorting to help.

Hope this helps

-one
source share

All Articles