After adding a TableRowSorter adding values to model cause java.lang.IndexOutOfBoundsException: Invalid range
Asked Answered
C

3

11

After adding a TableRowSorter to a table and its corresponding model any corresponding adds specifically at firetabletablerowsinserted cause exceptions. It is clear from testing that the GetRowCount() is returning a value past the models range. However it does not make sense to me how to continue to add values to the table after a sorter or filter has been added?

As an example, I set the row filter before adding anything to the table then add a value to the table with the following calls in my model:

this.addRow(row, createRow(trans,row));
this.fireTableRowsInserted(this.getRowCount(), this.getRowCount());

The rowcount is of size 1 and the exception is thrown:

java.lang.IndexOutOfBoundsException: Invalid range
at javax.swing.DefaultRowSorter.checkAgainstModel(Unknown Source)
at javax.swing.DefaultRowSorter.rowsInserted(Unknown Source)
at com.gui.model

If I do the same steps without first adding the sorter everything is fine. I assumed that possibly I needed to notify the model that the sorter may have made changes and tried the following but still returns an exception:

this.addRow(row, createRow(trans,row));
this.fireTableStructureChanged()
this.fireTableRowsInserted(this.getRowCount(), this.getRowCount());

I even tried to notify the sorter inside the model that a value has been added to the model before calling fire like below but it fails as well:

 this.addRow(row, createRow(trans,row));
 if(sorter.getRowFilter() != null){
      //if a sorter exists we are in add notify sorter
      sorter.rowsInserted(getRowCount(), getRowCount());
  }
  this.fireTableRowsInserted(this.getRowCount(), this.getRowCount());

Lastly, I hard coded the FireTableRowsInsterted(0,0) and it does not throw any exception. But nothing gets added to table? So, I know it is definitely some type of OutOfBounds issue. I have looked all over and cannot seem to find the answer. If anyone has any idea how this is suppose to work it be very helpful. Here is code that sets the sorter inside jpanel:

    messageTable.setRowSorter(null);
     HttpTransactionTableModel m = getTransactionTableModel();
     final int statusIndex = m.getColIndex("status");
     RowFilter<Object,Object> startsWithAFilter = new RowFilter<Object,Object>() {
           public boolean include(Entry<? extends Object, ? extends Object> entry) {

               for(char responseCode:responseCodes)
               {
                   if (entry.getStringValue(statusIndex).startsWith(Character.toString(responseCode))) {
                         return true;
                       }
               }


             // None of the columns start with "a"; return false so that this
             // entry is not shown
             return false;
           }
         };


        m.sorter.setRowFilter(startsWithAFilter);
        messageTable.setRowSorter(m.sorter);

Here is code inside my model that adds value to model:

public void update(Observable o, Object evt) {
    if (evt instanceof ObservableEvent<?>) {

        ObservableEvent<?> event = (ObservableEvent<?>) evt;

        if (event.getElement() instanceof HttpTransaction) {

            HttpTransaction trans = (HttpTransaction) event.getElement();

            // handle adding of an element
            if (event.getAction() == PUT) {

                if (includeTransaction(trans)) {

                    // handle request elements
                    if (trans.getRequest() != null && idMap.get(trans.getID()) == null) {

                        idMap.put(trans.getID(), count++);
                       // transactionManager.save(trans);
                        int row = idMap.get(trans.getID());
                        this.addRow(row, createRow(trans,row));
                        if(sorter.getRowFilter() != null){
                            sorter.rowsInserted(getRowCount(), getRowCount());
                        }
                        this.fireTableRowsInserted(this.getRowCount(), this.getRowCount());

                    }
Circumnutate answered 28/5, 2011 at 23:43 Comment(2)
this.fireTableRowsInserted(this.getRowCount(), this.getRowCount()); -- off by one, it's zero-based indexFurbelow
+1 @Furbelow - good spotting :-)Klug
C
3

You have an out by 1 error. The correct code for firing the event is:

this.fireTableRowsInserted(this.getRowCount()-1, this.getRowCount()-1);
Catherincatherina answered 3/6, 2011 at 4:7 Comment(2)
consider editing your answer: The error you found in your code is the same as the underlying error in OPs code :-)Klug
@kleopatra, good suggestion, I've edited my answer so it's just the answer.Catherincatherina
T
2

I went back and had a better look at this after seeing kleopatra's comment. I was changing my TableModel after creating a RowSorter, but before attaching the RowSorter to the JTable. Here's an example that shows the problem I was having.

import javax.swing.*;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableRowSorter;
import java.util.ArrayList;
import java.util.List;

public class TestTableMain {
    public static void main(String[] args) {
        new TestTableMain();
    }

    public TestTableMain() {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                buildAndShowMainFrame();
            }
        });
    }

    private void buildAndShowMainFrame() {
        JFrame frame = new JFrame();
        JScrollPane scrollPane = new JScrollPane();

        TestTableModel model = new TestTableModel();
        JTable table = new JTable(model);

        TableRowSorter<TestTableModel> rowSorter = new TableRowSorter<>(model);
        rowSorter.setRowFilter(null);

        model.add("First added item.");
        /* The RowSorter doesn't observe the TableModel directly. Instead,
         * the JTable observes the TableModel and notifies the RowSorter
         * about changes. At this point, the RowSorter(s) internal variable
         * modelRowCount is incorrect.  There are two easy ways to fix this:
         *
         * 1. Don't add data to the model until the RowSorter has been
         * attached to the JTable.
         *
         * 2. Notify the RowSorter about model changes just prior to
         * attaching it to the JTable.
         */

        // Uncomment the next line to notify rowSorter that you've changed
        // the model it's using prior to attaching it to the table.
        //rowSorter.modelStructureChanged();
        table.setRowSorter(rowSorter);

        scrollPane.setViewportView(table);
        frame.setContentPane(scrollPane);

        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.pack();
        frame.setVisible(true);

        model.add("Second added item.");
    }

    private class TestTableModel extends AbstractTableModel {
        private List<String> items = new ArrayList<>();

        public TestTableModel() {
            for(int i=0;i<5;i++) {
                add("Item " + i);
            }
        }

        @Override
        public int getRowCount() {
            return items.size();
        }

        @Override
        public int getColumnCount() {
            return 1;
        }

        @Override
        public Object getValueAt(int rowIndex, int columnIndex) {
            return items.get(rowIndex);
        }

        public void add(String item) {
            items.add(item);
            fireTableRowsInserted(items.size() - 1, items.size() - 1);
        }
    }
}
Technicality answered 16/8, 2012 at 17:45 Comment(6)
simply wrong - never ever try to second guess the normal internal update. Instead, try to find the error in your code instead of randomly adding stuff ;-)Klug
@Klug You're right. I tracked down the actual cause today and I've updated my answer.Technicality
you might consider to remove the original part - wrong stuff tends to stick in minds :-)Klug
I'm still so confused even after this answer. When I run your code, it throws an error. When I uncomment the modelStructureChanged() line, it works. But we agreed (nay, the javadocs specifically say) that it is wrong to call that method yourself. So what's the takeaway here??Industrious
"1. Don't add data to the model until the RowSorter has been attached to the JTable." In my code, the RowSorter is added to the table before the addRow method is called and I still get the error. So I don't think that makes a difference.Industrious
@Industrious I just ran it and attaching the RowSorter before modifying the model works for me. I think that's the best approach if possible. In my example, move table.setRowSorter(rowSorter) above model.add("First added item."). The JavaDocs say You normally do not call this method. I don't think that means it's forbidden, just not commonly done. This question is fairly old and I never accepted an answer because none of them explain the solution extremely well. You might want to start a new question with the code that's causing problems for you.Technicality
C
0

So, for now it looks like if you check in your model if your currently in sorting mode and if that is case only call update on sorting model. Otherwise call normal model fire updates everything seems to work so far. I'm still open for better ways to handle this though:

                         if(sorter.getRowFilter() != null){
                             sorter.modelStructureChanged();
                           }
                           else
                         this.fireTableRowsInserted(this.getRowCount(), this.getRowCount());
Circumnutate answered 29/5, 2011 at 18:5 Comment(1)
simply wrong - see the comment in @Catherincatherina 's comment to his own answer: same error in yours. BTW, never-ever fire a model event from outside of the model. It's the model's exclusive responsibility to notify interested parties ...Klug

© 2022 - 2024 — McMap. All rights reserved.