【问题标题】:Refreshing JTable in Swing Gives Exception在 Swing 中刷新 JTable 会出现异常
【发布时间】:2011-08-21 10:05:57
【问题描述】:

所以我基本上是在每次击键时或当观察者有更新时尝试刷新我的 JTable。有时我得到的是一个冻结的应用程序和这个堆栈跟踪......

线程“AWT-EventQueue-0”中的异常 java.lang.ArrayIndexOutOfBoundsException: 5 >= 5 在 java.util.Vector.elementAt(未知来源) 在 javax.swing.table.DefaultTableColumnModel.getColumn(未知来源) 在 javax.swing.plaf.synth.SynthTableUI.paintCells(未知来源) 在 javax.swing.plaf.synth.SynthTableUI.paint(未知来源) 在 javax.swing.plaf.synth.SynthTableUI.update(未知来源) 在 javax.swing.JComponent.paintComponent(未知来源) 在 javax.swing.JComponent.paint(未知来源) 在 javax.swing.JComponent.paintToOffscreen(未知来源) 在 javax.swing.RepaintManager$PaintManager.paintDoubleBuffered(未知来源) 在 javax.swing.RepaintManager$PaintManager.paint(未知来源) 在 javax.swing.RepaintManager.paint(未知来源) 在 javax.swing.JComponent._paintImmediately(未知来源) 在 javax.swing.JComponent.paintImmediately(未知来源) 在 javax.swing.RepaintManager.paintDirtyRegions(未知来源) 在 javax.swing.RepaintManager.paintDirtyRegions(未知来源) 在 javax.swing.RepaintManager.seqPaintDirtyRegions(未知来源) 在 javax.swing.SystemEventQueueUtilities$ComponentWorkRequest.run(未知来源) 在 java.awt.event.InvocationEvent.dispatch(未知来源) 在 java.awt.EventQueue.dispatchEvent(未知来源) 在 java.awt.EventDispatchThread.pumpOneEventForFilters(未知来源) 在 java.awt.EventDispatchThread.pumpEventsForFilter(未知来源) 在 java.awt.EventDispatchThread.pumpEventsForHierarchy(未知来源) 在 java.awt.EventDispatchThread.pumpEvents(未知来源) 在 java.awt.EventDispatchThread.pumpEvents(未知来源) 在 java.awt.EventDispatchThread.run(未知来源) 线程“AWT-EventQueue-0”中的异常 java.lang.ArrayIndexOutOfBoundsException: 4 >= 4 在 java.util.Vector.elementAt(未知来源) 在 javax.swing.table.DefaultTableColumnModel.getColumn(未知来源) 在 sun.swing.SwingUtilities2.convertColumnIndexToModel(未知来源) 在 javax.swing.JTable.convertColumnIndexToModel(未知来源) 在 javax.swing.JTable.getColumnClass(未知来源) 在 javax.swing.plaf.synth.SynthTableUI$SynthTableCellRenderer.getTableCellRendererComponent(未知来源) 在 javax.swing.JTable.prepareRenderer(未知来源) 在 javax.swing.plaf.synth.SynthTableUI.paintCell(未知来源) 在 javax.swing.plaf.synth.SynthTableUI.paintCells(未知来源) 在 javax.swing.plaf.synth.SynthTableUI.paint(未知来源) 在 javax.swing.plaf.synth.SynthTableUI.update(未知来源) 在 javax.swing.JComponent.paintComponent(未知来源) 在 javax.swing.JComponent.paint(未知来源) 在 javax.swing.JComponent.paintToOffscreen(未知来源) 在 javax.swing.RepaintManager$PaintManager.paintDoubleBuffered(未知来源) 在 javax.swing.RepaintManager$PaintManager.paint(未知来源) 在 javax.swing.RepaintManager.paint(未知来源) 在 javax.swing.JComponent._paintImmediately(未知来源) 在 javax.swing.JComponent.paintImmediately(未知来源) 在 javax.swing.RepaintManager.paintDirtyRegions(未知来源) 在 javax.swing.RepaintManager.paintDirtyRegions(未知来源) 在 javax.swing.RepaintManager.seqPaintDirtyRegions(未知来源) 在 javax.swing.SystemEventQueueUtilities$ComponentWorkRequest.run(未知来源) 在 java.awt.event.InvocationEvent.dispatch(未知来源) 在 java.awt.EventQueue.dispatchEvent(未知来源) 在 java.awt.EventDispatchThread.pumpOneEventForFilters(未知来源) 在 java.awt.EventDispatchThread.pumpEventsForFilter(未知来源) 在 java.awt.EventDispatchThread.pumpEventsForHierarchy(未知来源) 在 java.awt.EventDispatchThread.pumpEvents(未知来源) 在 java.awt.EventDispatchThread.pumpEvents(未知来源) 在 java.awt.EventDispatchThread.run(未知来源)

如果有帮助,这是我刷新表格的代码:

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);
}

为了记录,我已经尝试将它包装在 try/catch 中,但没有帮助。我也尝试过更改默认异常处理程序,但也没有成功。

【问题讨论】:

  • 只有在您快速输入时才会出现这种情况吗?也就是说,如果您一次小心地按一个键,它的行为是否符合您的预期?还是不管输入速度如何都会出现问题?
  • 当我快速输入时确实发生了这种情况,并且当实例接收到“更新”调用时确实发生了。我倾向于同意这是一种竞争条件,但这只是一个奇怪的条件。

标签: java multithreading swing exception jtable


【解决方案1】:

同步?我担心你会在 EDT 上做出 Swing 跟注。你有注意不要违反 Swing 单线程规则吗?

【讨论】:

  • 我一直在尝试 - 本质上,如果订单状态在后台更新,那么这个类(它是一个观察者)应该尝试刷新表。我认为同步会停止竞争条件。如果我错了,请原谅我。我是一个相当新手的开发人员。
  • @user744526,在方法级别同步类似于在包含该方法的对象的 Class 上获取锁。它阻止对象具有相同类类型的其他同步方法并行运行。它不会阻止代码在其他对象类型上并行运行,包括 JTable 或 DefaultTableModel 对象。即使在同一个对象上,它也不会停止非同步方法的并行处理。除非您真的确定您知道要避免什么情况,否则您不应该真正使用它。否则只会让你伤心。 :)
  • 但我同意@Hovercraft Full Of Eels,同样的错误你可以......通过使用来自DefautTableModel的AbstractTabelModel,这与建议的EDT有关,
【解决方案2】:

我能看到的最大问题之一是您没有将表格绑定到您的数据,然后在单元格更改时刷新它们,您每次按键时都会从表格下方更改整个数据模型。

我冒险猜测您正在经历“种族”状况。您按下一个键,就开始了重建表及其模型的过程。在重建的过程中(请记住,Swing 使用线程,因此表更新仍在发生您键入,)您将触发另一个重建,这会从第一次刷新下更改模型仍在运行。当然,如果没有看到你所有的代码,我无法确定这一点,但我可以提出一些建议,让事情变得更简洁,更不容易出现并发问题。

您可以创建自己的表格模型类,并实现它以直接查看您的模型,而不是编写代码来获取您的模型数据并将其转换为一个简单的表格模型。这样您就不会遇到在模型和数组之间不断重新传输数据的问题。

要制作自己的表格模型,您可以从 AbstractTableModel (http://download.oracle.com/javase/6/docs/api/javax/swing/table/AbstractTableModel.html) 扩展。如果您不确定如何操作,原始 Java 教程 (http://download.oracle.com/javase/tutorial/uiswing/components/table.html#data) 中有一个很好的示例。对您来说最重要的是“getValueAt”方法,它接受一行和一列并返回该单元格应包含的值。这就是您要放置代码的地方,该代码查看您的“订单”对象并产生您想要的输出。

一旦您有了自定义 TableModel 对象,只需在首次初始化时将它的新实例传递给表的“setModel”方法。

完成后,您将需要处理明智的模型刷新。要刷新表中的所有数据,可以触发“fireTableDataChanged”事件。或者,您可以只刷新表格中实际需要更改的部分,而不是每次都刷新整个内容。我不会在这里详细介绍细节,而是将您指向出色的 Java 教程页面,以实现这一点:http://download.oracle.com/javase/tutorial/uiswing/components/table.html#fire

【讨论】:

  • 哇,这看起来很有帮助 - 但是,我不完全明白为什么这会改善并发问题。仅仅是单个单元格更改比整个刷新更“危险”吗?我想我可以顺其自然。我一定会在明天早上之前让你知道这是否有效,因为我累得要死。但我非常感谢您的回复,我会给您适当的反馈。 =)
  • 它应该可以解决您描述的并发问题,因为表格始终可以告诉所有单元格包含的内容。在您的原始代码中,在某些时候,表格可能无法访问模型中的单个单元格,因为该模型已从其下方拉出,这会导致“超出范围”错误。相反,如果您使用自定义模型和模型刷新事件(即 fireTableDataChanged),该表可以处理该表数据的刷新并将其排队。这就是表格的使用方式,因此更安全。
  • @Erica 请将 AbstactTableModel 的链接从 Java 1.4 更改为当前的 Java 1.6,一些重要更改...
  • @mKorbel,完成,感谢您的提醒。 (习惯的力量,我在日常工作中仍在使用 1.4。):)
【解决方案3】:

正如其他答案已经强调的那样:冻结是您从不是 EDT 的线程调用 refreshTable 的典型指示 - 一般规则是对 Swing 协作者的所有访问,无论是视图还是模型 必须 em> 发生在美国东部时间。

实现这一目标的最简单方法(只是让您开始)是调用对 refreshTable 的每个调用

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

  }

实际上,您可能不想刷新 table,您想要的是刷新它的 model,同时保持表的配置不变 - 无需重新-配置列的宽度。 (也已经提到)您的代码 sn-p 看起来您可以从自定义 tableModel 实现中获利:它是一个订单列表。要走的路是用 TableModel 装饰该列表,例如:

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);

注意:

  • 模型包含 real 对象和对其 real 属性的引用:格式化是 cellRenderers 的任务
  • 模型可以有尽可能多的有用的领域相关方法:在这里它可以从 Customer 实例或其 orderHistory 重新填充自己

【讨论】:

    猜你喜欢
    • 2015-09-20
    • 1970-01-01
    • 1970-01-01
    • 2014-06-03
    • 2015-04-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多