Java:在后台更新包含大量行的JTable

时间:2014-02-27 15:08:59

标签: java multithreading swing jtable mq

我正在编写一个简单的Java Swing实用程序,它将从MQ JMS服务器读取消息并将它们显示在JTable中。

private void getMessages() {
        try {
            if (null != Queue) {
                Queue.close();  //Close previous queue connection if there is one.
            }
            Queue = new MQQueue(QueueManager, tableQueues.getValueAt(tableQueues.getSelectedRow(), 1).toString(), MQConstants.MQOO_INPUT_SHARED | MQConstants.MQOO_BROWSE | MQConstants.MQOO_OUTPUT, queueManager, null, null);
            int count = 0;
            modelMessages.setRowCount(0);
            MQGetMessageOptions getOptions = new MQGetMessageOptions();
            getOptions.options = MQConstants.MQGMO_BROWSE_FIRST;
            ArrayList<Object[]> rows = new ArrayList<Object[]>();
            while(true) {
                if (count > 0) {
                    getOptions.options = MQConstants.MQGMO_BROWSE_NEXT;
                }
                MQMessage message = new MQMessage();
                try {
                    Queue.get(message, getOptions);
                    byte[] b = new byte[message.getMessageLength()];
                    message.readFully(b);
                    rows.add(new Object[]{count + 1, new String(b)});
                    modelMessages.addRow(new Object[]{count + 1, new String(b)});
                    message.clearMessage();
                    count++;
                }
                catch (IOException e) {
                    break;
                }
                catch (MQException e) {
                    if (e.completionCode == 2 && e.reasonCode == MQConstants.MQRC_NO_MSG_AVAILABLE) {
                        break;
                    }
                }
            }
            modelMessages.fireTableDataChanged();
        } catch (MQException e) {
            txtMessage.setText("MQJE001: Completion Code '" + e.completionCode + "', Reason '" + e.reasonCode + "'.");
            modelMessages.setRowCount(0);
            modelMessages.fireTableDataChanged();
        }
    }

这适用于较小的队列但是对于大型队列,填充此表需要一段时间,同时,Swing应用程序被冻结,因此我正在调查在后台填充JTable的方法,同时不仅保留应用程序但是JTable本身在这期间可以使用和滚动。

我对线程并不是很熟悉,我尝试了一些东西,例如在SwingUtilities.invokeLater中包装一些部分并实现doInBackground(),但似乎没有任何工作。有人可以指出我如何处理这个问题的正确方向吗?

修改

根据以下回复,以下是解决方案:

public class GetMessagesWorker extends SwingWorker<DefaultTableModel, Object[]> {

    //http://stackoverflow.com/questions/22072480/java-updating-jtable-with-lots-of-rows-in-the-background#

    private final DefaultTableModel model;

    public GetMessagesWorker(DefaultTableModel model){
        this.model = model;
    }

    @Override
    protected DefaultTableModel doInBackground() throws Exception {
        try {
            if (null != Queue) {
                Queue.close();  //Close previous queue connection if there is one.
            }
            Queue = new MQQueue(QueueManager, tableQueues.getValueAt(tableQueues.getSelectedRow(), 1).toString(), MQConstants.MQOO_INPUT_SHARED | MQConstants.MQOO_BROWSE | MQConstants.MQOO_OUTPUT, queueManager, null, null);
            int count = 0;
            modelMessages.setRowCount(0);
            MQGetMessageOptions getOptions = new MQGetMessageOptions();
            getOptions.options = MQConstants.MQGMO_BROWSE_FIRST;
            while(true) {
                if (count > 0) {
                    getOptions.options = MQConstants.MQGMO_BROWSE_NEXT;
                }
                MQMessage message = new MQMessage();
                try {
                    Queue.get(message, getOptions);
                    byte[] b = new byte[message.getMessageLength()];
                    message.readFully(b);
                    Object[] row = {count + 1, new String(b)};
                    publish(row);
                    message.clearMessage();
                    count++;
                    if (isCancelled()) {
                        modelMessages.setRowCount(0);
                        count = 0;
                        message.clearMessage();
                        return model;
                    }
                }
                catch (IOException e) {
                    break;
                }
                catch (MQException e) {
                    if (e.completionCode == 2 && e.reasonCode == MQConstants.MQRC_NO_MSG_AVAILABLE) {
                        break;
                    }
                }
            }
            //modelMessages.fireTableDataChanged();
        } catch (MQException e) {
            txtMessage.setText("MQJE001: Completion Code '" + e.completionCode + "', Reason '" + e.reasonCode + "'.");
            modelMessages.setRowCount(0);
            modelMessages.fireTableDataChanged();
        }
        return model;
    }

    @Override
    protected void process(List<Object[]> chunks){
        for(Object[] row : chunks){
            model.addRow(row);
        }
    }

}

这是听众:

    tableQueues = new JTable(modelQueues);
    tableQueues.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
        public void valueChanged(ListSelectionEvent e) {
            if (!e.getValueIsAdjusting()) {
                txtMessage.setText("");
                if (tableQueues.getSelectedRow() > -1) {
                    gmw.cancel(false);
                    gmw = new GetMessagesWorker(modelMessages);
                    gmw.execute();
                }
            }
        }
    });

2 个答案:

答案 0 :(得分:5)

在Swing中进行线程处理时,您需要了解必须在后台线程中执行工作,但任何UI更新必须在Swing线程中完成。

这意味着你应该创建一个线程来获取消息。如果您有一个(或更多),则使用SwingUtilities与Swing线程同步并更新表。

在上面的代码段中,这意味着每次调用SwingUtilitiesmodelMessages等方法时都需要使用txtMessage

由于这非常昂贵,您通常会在列表中收集10个新行,然后只需拨打SwingUtilities.invokeLater()一次即可将其全部添加。

答案 1 :(得分:5)

这是由于Swing架构。您必须了解正在执行侦听器的同一线程负责更多事情(例如,刷新UI)。

我会在你的情况下使用SwingWorker。这是一种特定于Swing的后台线程,具有特殊功能,可以在执行其长任务时安全地向UI发送更新。您可以在后台执行长期任务,并为其检索的每个相关结果调用publish()。每次调用publish()时,都会调用process()方法(向引擎盖下的主线程发送更新),您可以更新表格模型。

您可以在此处找到有关SwingWorker的更多信息:

http://docs.oracle.com/javase/tutorial/uiswing/concurrency/worker.html