从SwingWorker实时更新GUI

时间:2013-12-14 17:48:04

标签: java swing swingworker event-dispatch-thread invokelater

好的,这是我昨天提出的问题的后续问题,“Error handling in SwingWorker。”

考虑到可能可以在SwingUtilities#invokeAndWait()内拨打SwingWorker#doInBackground()这一事实,我想进一步向前推进并询问:可能也可以在后台线程中调用SwingUtilities#invokeLater()吗?

另一个SSCCE来说明:

public class NewClass extends javax.swing.JFrame {

    public NewClass() {
        jScrollPane1 = new javax.swing.JScrollPane();
        atable = new javax.swing.JTable();
        jButton1 = new javax.swing.JButton();

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);

        atable.setModel(new javax.swing.table.DefaultTableModel(
            new Object[][]{
                {null, null},
                {null, null},
                {null, null},
                {null, null}
            },
            new String[]{
                "Title 1", "Title 2"
            }
        ));
        jScrollPane1.setViewportView(atable);

        jButton1.setText("Go");
        jButton1.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jButton1ActionPerformed(evt);
            }
        });

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 380, Short.MAX_VALUE)
                    .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                        .addGap(0, 0, Short.MAX_VALUE)
                        .addComponent(jButton1)))
                .addContainerGap())
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 228, Short.MAX_VALUE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jButton1)
                .addContainerGap())
        );

        pack();
        setLocationByPlatform(true);
        setVisible(true);
    }

    private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
        new Task();
    }

    class Task extends javax.swing.SwingWorker<Void, Object[]> {

        DeterminateLoadingFrame dlf;

        public Task() {
            dlf = new DeterminateLoadingFrame();
            atable.setModel(new javax.swing.table.DefaultTableModel(
                new Object[][]{},
                new String[]{
                    "Title 1", "Title 2"
                }
            ));
            dlf.setMaximum(1000);
            execute();
        }

        @Override
        protected void process(java.util.List<Object[]> chunks) {
            for (Object[] row : chunks) {
                ((javax.swing.table.DefaultTableModel) atable.getModel()).addRow(row);
            }
        }

        @Override
        protected Void doInBackground() throws Exception {
            for (int i = 0; i < 1000; i++) {
                final int j = i;
                Object[] row = new Object[2];
                row[0] = "row " + i;
                row[1] = Math.round(Math.random() * 100);
                publish(row);
                javax.swing.SwingUtilities.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        dlf.changeText("Processing " + j + " of " + 1000);
                        dlf.setValue(j);
                    }
                });
                Thread.sleep(100);

            }
            return null;

        }

        @Override
        protected void done() {
            if (!isCancelled()) {
                try {
                    get();
                } catch (java.util.concurrent.ExecutionException | InterruptedException e) {
                    e.printStackTrace();
                }
            }
            dlf.dispose();
        }

    }

    class DeterminateLoadingFrame extends javax.swing.JFrame {

        javax.swing.JLabel jLabel1;
        javax.swing.JProgressBar jProgressBar1;

        public DeterminateLoadingFrame() {

            jProgressBar1 = new javax.swing.JProgressBar();
            jLabel1 = new javax.swing.JLabel();

            setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
            setTitle("Loading");

            jLabel1.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
            jLabel1.setText("Loading ...");

            javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
            getContentPane().setLayout(layout);
            layout.setHorizontalGroup(
                layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                    .addContainerGap()
                    .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
                        .addComponent(jLabel1, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, 182, Short.MAX_VALUE)
                        .addComponent(jProgressBar1, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, 182, Short.MAX_VALUE))
                    .addContainerGap())
            );
            layout.setVerticalGroup(
                layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                .addGroup(layout.createSequentialGroup()
                    .addContainerGap()
                    .addComponent(jLabel1)
                    .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                    .addComponent(jProgressBar1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
            );

            pack();
            setAlwaysOnTop(true);
            setLocationRelativeTo(null);
            setVisible(true);
            setAlwaysOnTop(false);
        }

        public void changeText(String message) {
            this.jLabel1.setText(message);
            System.out.println(message);
        }

        public void setMaximum(int max) {
            jProgressBar1.setMaximum(max);
        }

        public void setValue(int n) {
            jProgressBar1.setValue(n);
        }

    }

    public static void main(String args[]) {
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new NewClass();
            }
        });
    }

    private javax.swing.JTable atable;
    private javax.swing.JButton jButton1;
    private javax.swing.JScrollPane jScrollPane1;
}

在此SSCCE中,我们正在更新DeterminateLoadingFrame以更新用户有关正在发生的事情,同时通过publish()process()发布结果。

这似乎与昨天提出的原始问题一样不正统。然而,我无法提出任何理由这不是一个好主意。我们坚持正确的线程政策。以这种方式更新GUI似乎(对我来说)非常有用和多才多艺。事实上,还有process()publish()

此外:完成SSCCE所说明的内容将更难严格地使用process()publish(),因为您如何将i的值传递给process()方法?< / p>

2 个答案:

答案 0 :(得分:3)

另一个解决方案是使用SwingWorker的先天SwingPropertyChangeSupport并通知所有侦听器相关绑定属性的更改。您可以使用SwingWorkers已经拥有的进度绑定属性,但是如果您的属性从0到100,则只能使用此属性。如果您有一个不同的值(此处为1000),则必须使用自己的属性。由于PropertyChangeSupport对象是SwingPropertyChangeSupport,因此所有通知都将在Swing事件线程上完成。我喜欢这提供的解耦 - SwingWorker不需要知道谁在听什么,或者对信息做了什么。

例如,

class Task extends javax.swing.SwingWorker<Void, Object[]> {

  public static final String ROW_COUNT = "row count";
  DeterminateLoadingFrame dlf;
  private int rowCount = 0;

  public Task() {
     dlf = new DeterminateLoadingFrame();  // and I would pass this in from the GUI
     atable.setModel(new javax.swing.table.DefaultTableModel(
           new Object[][] {}, new String[] { "Title 1", "Title 2" }));
     dlf.setMaximum(1000);
     // execute();  // I reserve this for the calling code.
  }

  public int getRowCount() {
     return rowCount;
  }

  public void setRowCount(int newValue) {
     int oldValue = this.rowCount;
     this.rowCount = newValue;

     firePropertyChange(ROW_COUNT, oldValue, newValue);
  }

  @Override
  protected void process(java.util.List<Object[]> chunks) {
     for (Object[] row : chunks) {
        ((javax.swing.table.DefaultTableModel) atable.getModel())
              .addRow(row);
     }
  }

  @Override
  protected Void doInBackground() throws Exception {
     for (int i = 0; i < 1000; i++) {
        final int j = i;
        Object[] row = new Object[2];
        row[0] = "row " + i;
        row[1] = Math.round(Math.random() * 100);
        publish(row);
        setRowCount(j);
        Thread.sleep(100);

     }
     return null;

  }

  // .....

private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
  final Task task = new Task();
  task.addPropertyChangeListener(new PropertyChangeListener() {

     @Override
     public void propertyChange(PropertyChangeEvent evt) {
        if (Task.ROW_COUNT.equals(evt.getPropertyName())) {
           int rowCount = task.getRowCount();
           // do what you need with rowCount here
        }
     }
  });
  task.execute(); // again I usually call this here
}

暂且不说:

  • 您应该为任何辅助窗口使用对话窗口,例如JDialog,而不是另一个JFrame。
  • 尝试从SSCCE中获取不必要的NetBeans生成的代码。它只会分散你问题的代码。

答案 1 :(得分:2)

这是正确的,但它并没有从SwingWorker完成批量更新的所有工作中受益,并且让您避免使用这些低级SwingUtilities方法。它还结合了后台进程和UI更新,而不是优雅地将它们分离。

如何将i传递给publish方法:创建一个包含此计数器和行的类:

private static class Update {
    private int counter;
    private Object[] row;

    // constructor, getters omitted for brevity
}

...

class Task extends SwingWorker<Void, Update> {

    @Override
    protected Void doInBackground() throws Exception {
        for (int i = 0; i < 1000; i++) {
            Object[] row = new Object[2];
            row[0] = "row " + i;
            row[1] = Math.round(Math.random() * 100);
            publish(new Update(i, row);
            Thread.sleep(100);

        }
        return null;
    }

    @Override
    protected void process(java.util.List<Update> chunks) {
        for (Update update : chunks) {
            ((DefaultTableModel) atable.getModel()).addRow(update.getRow());   
        }
        int lastCounter = chunks.get(chunks.size() - 1).getCounter();
        dlf.changeText("Processing " + lastCounter + " of " + 1000);
        dlf.setValue(lastCounter);
    }
    ...
}