user744526
user744526

Reputation: 33

Refreshing JTable in Swing Gives Exception

So I'm essentially trying to refresh my JTable every time a keystroke is hit, or when there's an update on the observer's part. Occasionally what I get is a frozen application and this stack trace...

Exception in thread "AWT-EventQueue-0" java.lang.ArrayIndexOutOfBoundsException: 5 >= 5 at java.util.Vector.elementAt(Unknown Source) at javax.swing.table.DefaultTableColumnModel.getColumn(Unknown Source) at javax.swing.plaf.synth.SynthTableUI.paintCells(Unknown Source) at javax.swing.plaf.synth.SynthTableUI.paint(Unknown Source) at javax.swing.plaf.synth.SynthTableUI.update(Unknown Source) at javax.swing.JComponent.paintComponent(Unknown Source) at javax.swing.JComponent.paint(Unknown Source) at javax.swing.JComponent.paintToOffscreen(Unknown Source) at javax.swing.RepaintManager$PaintManager.paintDoubleBuffered(Unknown Source) at javax.swing.RepaintManager$PaintManager.paint(Unknown Source) at javax.swing.RepaintManager.paint(Unknown Source) at javax.swing.JComponent._paintImmediately(Unknown Source) at javax.swing.JComponent.paintImmediately(Unknown Source) at javax.swing.RepaintManager.paintDirtyRegions(Unknown Source) at javax.swing.RepaintManager.paintDirtyRegions(Unknown Source) at javax.swing.RepaintManager.seqPaintDirtyRegions(Unknown Source) at javax.swing.SystemEventQueueUtilities$ComponentWorkRequest.run(Unknown Source) at java.awt.event.InvocationEvent.dispatch(Unknown Source) at java.awt.EventQueue.dispatchEvent(Unknown Source) at java.awt.EventDispatchThread.pumpOneEventForFilters(Unknown Source) at java.awt.EventDispatchThread.pumpEventsForFilter(Unknown Source) at java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source) at java.awt.EventDispatchThread.pumpEvents(Unknown Source) at java.awt.EventDispatchThread.pumpEvents(Unknown Source) at java.awt.EventDispatchThread.run(Unknown Source) Exception in thread "AWT-EventQueue-0" java.lang.ArrayIndexOutOfBoundsException: 4 >= 4 at java.util.Vector.elementAt(Unknown Source) at javax.swing.table.DefaultTableColumnModel.getColumn(Unknown Source) at sun.swing.SwingUtilities2.convertColumnIndexToModel(Unknown Source) at javax.swing.JTable.convertColumnIndexToModel(Unknown Source) at javax.swing.JTable.getColumnClass(Unknown Source) at javax.swing.plaf.synth.SynthTableUI$SynthTableCellRenderer.getTableCellRendererComponent(Unknown Source) at javax.swing.JTable.prepareRenderer(Unknown Source) at javax.swing.plaf.synth.SynthTableUI.paintCell(Unknown Source) at javax.swing.plaf.synth.SynthTableUI.paintCells(Unknown Source) at javax.swing.plaf.synth.SynthTableUI.paint(Unknown Source) at javax.swing.plaf.synth.SynthTableUI.update(Unknown Source) at javax.swing.JComponent.paintComponent(Unknown Source) at javax.swing.JComponent.paint(Unknown Source) at javax.swing.JComponent.paintToOffscreen(Unknown Source) at javax.swing.RepaintManager$PaintManager.paintDoubleBuffered(Unknown Source) at javax.swing.RepaintManager$PaintManager.paint(Unknown Source) at javax.swing.RepaintManager.paint(Unknown Source) at javax.swing.JComponent._paintImmediately(Unknown Source) at javax.swing.JComponent.paintImmediately(Unknown Source) at javax.swing.RepaintManager.paintDirtyRegions(Unknown Source) at javax.swing.RepaintManager.paintDirtyRegions(Unknown Source) at javax.swing.RepaintManager.seqPaintDirtyRegions(Unknown Source) at javax.swing.SystemEventQueueUtilities$ComponentWorkRequest.run(Unknown Source) at java.awt.event.InvocationEvent.dispatch(Unknown Source) at java.awt.EventQueue.dispatchEvent(Unknown Source) at java.awt.EventDispatchThread.pumpOneEventForFilters(Unknown Source) at java.awt.EventDispatchThread.pumpEventsForFilter(Unknown Source) at java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source) at java.awt.EventDispatchThread.pumpEvents(Unknown Source) at java.awt.EventDispatchThread.pumpEvents(Unknown Source) at java.awt.EventDispatchThread.run(Unknown Source)

If it helps, here is my code for refreshing the table:

public synchronized void refreshTable()
{
    Customer cust = custManager.getCustomer(phoneNumber.getText());

    if (cust == null)
    {
        table.setModel(new DefaultTableModel(new Object[][] {}, tableHeader) {
                @SuppressWarnings("rawtypes")
                Class[] columnTypes = new Class[] {Integer.class, String.class,
                                                   Object.class, Object.class, 
                                                   Object.class, Object.class};
                @SuppressWarnings({ "unchecked", "rawtypes" })
                public Class getColumnClass(int columnIndex) {
                    return columnTypes[columnIndex];
                }

                public boolean isCellEditable(int row, int column) {
                    return false;
                }
        });
        table.getColumnModel().getColumn(0).setPreferredWidth(40);
        table.getColumnModel().getColumn(1).setPreferredWidth(120);
        return;
    }

    Object[][] grid = new Object[cust.getOrderHistory().size()][6];

    SimpleDateFormat sdf = new SimpleDateFormat("MMM/dd/yyyy HH:mm");
    NumberFormat currencyFormat = NumberFormat.getCurrencyInstance();

    int i = 0;
    for (Entry<GregorianCalendar, Order> orderEntry : cust.getOrderHistory())
    {
        Order order = orderEntry.getValue();

        grid[i][0] = order.getOrderID();
        grid[i][1] = sdf.format((order.getProcessedTimestamp().getTime()));
        grid[i][2] = currencyFormat.format(order.getSubTotal()/100.00);
        grid[i][3] = currencyFormat.format(order.getTaxedAmount()/100.00);
        grid[i][4] = currencyFormat.format(order.getTotal()/100.00);
        grid[i][5] = order.getOrderStatus();

        i++;
    }

    DefaultTableModel dft = new DefaultTableModel(grid, tableHeader) {
            @SuppressWarnings("rawtypes")
            Class[] columnTypes = new Class[] {Integer.class, String.class, 
                                               Object.class, Object.class, 
                                               Object.class, Object.class};
            @SuppressWarnings({ "unchecked", "rawtypes" })
            public Class getColumnClass(int columnIndex) {
                return columnTypes[columnIndex];
            }

            public boolean isCellEditable(int row, int column) {
                return false;
            }
        };

    table.setModel(dft);
    table.getColumnModel().getColumn(0).setPreferredWidth(40);
    table.getColumnModel().getColumn(1).setPreferredWidth(120);
}

For the record, I have tried wrapping it around in a try/catch, and no that will not help. I also have tried changing the default exception handler, and that has been unsuccessful as well.

Upvotes: 3

Views: 8918

Answers (3)

kleopatra
kleopatra

Reputation: 51536

As the other answers already emphasized: freezing is a typical indication that you call the refreshTable from a thread that's not the EDT - the general rule is that all access to Swing collaborators, be it view or model must happen on the EDT.

The simplest way (just to get you started) to achieve that is to invoke each call to refreshTable

  public void invokeRefreshTable(final OrderTableModel model) {
    SwingUtilities.invokeLater(new Runnable() {
        public void run() {
            refreshTable(model)
        }
    }); 

  }

Actually, you probably don't want to refresh the table, what you want is to refresh its model while keeping the table's configuration as is - no need to re-config the column's width. (Also already mentioned) your code snippet looks like you could profit from a custom tableModel implementation: it's a list of Orders. The way to go is to decorate that list with a TableModel, something like:

public static class OrderTableModel extends AbstractTableModel {
    List<Order> orders = new ArrayList<Order>();
    Class<?>[] columnTypes = new Class[] { Integer.class,
            Timestamp.class, BigDecimal.class, BigDecimal.class, BigDecimal.class,
            Object.class };

    public void setOrders(Customer customer) {
        setOrders(customer != null ? customer.getOrderHistory() : null);
    }

    public void setOrders(Map<?, ?> orderHistory) {
        clear();
        if (orderHistory == null) return;
        for (Entry<?, ?> orderEntry: orderHistory) {
            orders.add(orderEntry.getValue());
        }
        fireTableRowsInserted(0, orders.size() - 1);
    }

    public void clear() {
        int rowCount = orders.size();
        orders.clear();
        if (rowCount > 0 ) {
            fireTableRowsDeleted(0, rowCount -1);
        }
    }

    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {
       switch (columnIndex) {
           case 0: return orders.get(rowIndex).getOrderID();
           //....

           default:
               break;
       }

        return null;
    }

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

    @Override
    public int getColumnCount() {
        return columnTypes.length;
    }

    @Override
    public Class<?> getColumnClass(int columnIndex) {
        return columnTypes[columnIndex];
    }

}

// the single model instance to re-use always
OrderTableModel model = new OrderTableModel()
// separate initial configuration of the table
JTable table = new JTable(model);
// register custom renderers
table.setDefaultRenderer(BigDecimal.class, new BigDecimalRenderer());
table.setDefaultRenderer(Timestamp.class, new TimeStampRenderer());
// config the table
table.getColumnModel().getColumn(0).setPreferredSize(...)
....

// the refresh method crumpled down to a single line
public void refreshTable(OrderTableModel model) {
    model.setCustomer(customManager.getCustomer(phoneNumber))
}

// if refreshing is needed
invokeRefreshTable(model);

Note:

  • the model contains the real objects and references to their real properties: formatting is the task of cellRenderers
  • the model can have as many domain-related methods as are helpful: here it can re-fill itself from either a Customer instance or its orderHistory

Upvotes: 2

Erica
Erica

Reputation: 2261

One of the biggest problems I can see is that you are not binding the table to your data then refreshing cells as they change, you are changing the whole data model out from under the table every time you press a key.

I'd hazard a guess that you're experiencing a "race" condition. You press a key, and that starts off the process of rebuilding your table and its model. Part way through that rebuild (remember, Swing uses threads, so the table update is still happening while you type,) you're going and triggering another rebuild, which changes the model out from under the first refresh which is still running. Of course, without seeing all of your code I can't be certain about that, but I can suggest a few things to make things a bit cleaner and less prone to concurrency issues.

Rather than writing code which takes your model data and translates it into a simple table model every key stroke, you can create your own table model class, and implement it to look directly at your model. That way you don't have the problem of constantly re-transferring data between your model and the array.

To make your own table model, you can just extend from the AbstractTableModel ( http://download.oracle.com/javase/6/docs/api/javax/swing/table/AbstractTableModel.html ). There's a great example in the original Java Tutorial ( http://download.oracle.com/javase/tutorial/uiswing/components/table.html#data ) if you're not sure how to go about it. The most important thing for you is the "getValueAt" method, which takes a row and a column and returns what value that cell should contain. That's where you want to put the code which looks at your "Order" object and produces the output you want.

Once you have your custom TableModel object, just pass a new instance of it into the "setModel" method of the table when you first initialize it.

Once that is done, you'll want to handle sensible model refreshes. To refresh all of the data in the table, you can trigger a "fireTableDataChanged" event. Alternatively, you can refresh only the parts of your table which actually need to be changed, not the whole thing every time. Rather than go into the fine grain details here, I'll just point you to the excellent Java Tutorial page on doing exactly that: http://download.oracle.com/javase/tutorial/uiswing/components/table.html#fire

Upvotes: 3

Hovercraft Full Of Eels
Hovercraft Full Of Eels

Reputation: 285470

Synchronized? I fear that you're making Swing calls off of the EDT. Have you taken care to not violate the Swing single threading rule?

Upvotes: 2

Related Questions