javaswingtablecellrenderertablerowsorter

Error when sorting rows in Jtable


I'm trying to set a RowSorter on my Jtable, I used the method setAutoCreateRowSorter(Boolean b) to sort the rows

table.setAutoCreateRowSorter(true);

But when I make the table as rawSorted, I get a strange error!

The conflict is visible when I want to delete a line, I used fireTableRowsDeleted().

int raw = table.getSelectedRow();  // the index of raw that i want to delete it
System.out.println(raw);
model.delte_raw(raw); // model is my table model

public void delte_raw(int raw)
         {
             if (!ls.isEmpty()) {
         this.fireTableRowsDeleted(raw+1, raw);
         ls.remove(raw);
         }

I want to show you what result return the code as above in 2 cases:

Case 1:

When I make my table as not rawsorted:

table.setAutoCreateRowSorter(false);

when I delete a line, it all works successfully.

Case 2:

When I make my table as rawsorted:

table.setAutoCreateRowSorter(true);

when I delete a line, I get the error as below:

Exception in thread "AWT-EventQueue-0" java.lang.IndexOutOfBoundsException: Invalid range
    at javax.swing.DefaultRowSorter.checkAgainstModel(DefaultRowSorter.java:921)
    at javax.swing.DefaultRowSorter.rowsDeleted(DefaultRowSorter.java:878)
    at javax.swing.JTable.notifySorter(JTable.java:4277)
    at javax.swing.JTable.sortedTableChanged(JTable.java:4121)
    at javax.swing.JTable.tableChanged(JTable.java:4398)
    at javax.swing.table.AbstractTableModel.fireTableChanged(AbstractTableModel.java:296)
    at javax.swing.table.AbstractTableModel.fireTableRowsDeleted(AbstractTableModel.java:261)

I think that the error is in my defaultRowSorter, so I defined my specific cellRenderer as below:

//    final TableCellRenderer r = table.getTableHeader().getDefaultRenderer();
//TableCellRenderer wrapper = new TableCellRenderer() {
//            private Icon ascendingIcon = new ImageIcon("images/e.png");
//            private Icon descendingIcon = new ImageIcon("images/e.png");
//
//    @Override
//    public Component getTableCellRendererComponent(JTable table,
//            Object value, boolean isSelected, boolean hasFocus,
//            int row, int column) 
//    {
//        Component comp = r.getTableCellRendererComponent(table, value, isSelected, 
//            hasFocus, row, column);
//        if (comp instanceof JLabel) {
//            JLabel label = (JLabel) comp;
//            label.setIcon(getSortIcon(table, column));
//        }
//        return comp;
//    }
//
//    /**
//     * Implements the logic to choose the appropriate icon.
//     */
//    private Icon getSortIcon(JTable table, int column) {
//        SortOrder sortOrder = getColumnSortOrder(table, column);
//        if (SortOrder.UNSORTED == sortOrder) {
//            return null;
//        }
//        return SortOrder.ASCENDING == sortOrder ? ascendingIcon : descendingIcon;
//    }
//
//    private SortOrder getColumnSortOrder(JTable table, int column) {
//        if (table == null || table.getRowSorter() == null) {
//            return SortOrder.UNSORTED;
//        }
//        List<? extends RowSorter.SortKey> keys = table.getRowSorter().getSortKeys();
//        if (keys.size() > 0) {
//                    RowSorter.SortKey key = keys.get(0);
//            if (key.getColumn() == table.convertColumnIndexToModel(column)) {
//                return key.getSortOrder();
//            }
//        }
//        return SortOrder.UNSORTED;
//    }
//
//};
//table.getTableHeader().setDefaultRenderer(wrapper);

But again, the same error!

Why do I get this error? I googled it a lot, but either I used the wrong keywords or there are no simple solutions on the internet.


Solution

  • In your table model:

    public void delte_raw(int raw) {
        if (!ls.isEmpty()) {
            this.fireTableRowsDeleted(raw+1, raw); // why raw+1 ???
            ls.remove(raw);
        }
    }
    

    As your table model extends from AbstractTableModel and looking at fireTableRowsDeleted(int firstRow, int lastRow) javadoc:

    Notifies all listeners that rows in the range [firstRow, lastRow], inclusive, have been deleted.

    So it should be:

    public void delte_raw(int raw) {
        if (!ls.isEmpty()) {            
            ls.remove(raw); // remove the row index from the List and then fire the event
            fireTableRowsDeleted(raw, raw);
        }
    }
    

    Knowing the exception source: looking at DefaultRowSorter.checkAgainstModel(int firstRow, int endRow) implementation:

    private void checkAgainstModel(int firstRow, int endRow) {
        if (firstRow > endRow || firstRow < 0 || endRow < 0 ||
                firstRow > modelRowCount) {
            throw new IndexOutOfBoundsException("Invalid range");
        }
    }
    

    As you can see, calling this method with [raw+1,raw] range causes an IndexOutOfBoundsException.

    Edit

    As @mKorbel masterfully points out, I've totally overlooked this:

    int raw = table.getSelectedRow();  // this is the index in the view
    model.delte_raw(raw); // convert raw in the right model index is needed
    

    You need to convert raw in the right model index. Otherwise it can cause side effects since in a sorted table is most likely the selected index in the view be different than its related model's index:

    int raw = table.getSelectedRow();  // this is the index in the view
    model.delte_raw(table.convertRowIndexToModel(raw)); // perfect
    

    See JTable.convertRowIndexToModel(int viewRowIndex)