简体   繁体   English

从SwingWorker实时更新GUI

[英]Updating the GUI in real time from SwingWorker

Ok, this is a follow-up question to my question from yesterday, " Error handling in SwingWorker ." 好的,这是我昨天的问题“ SwingWorker中的错误处理 ”的后续问题。

In consideration of the fact that it might be ok to call SwingUtilities#invokeAndWait() inside of SwingWorker#doInBackground() , I want to push it a step further and ask: might it also be ok to call SwingUtilities#invokeLater() inside the background thread? 考虑到这样的事实,它可能是好的调用SwingUtilities#invokeAndWait()SwingWorker#doInBackground()我想更进一步推,然后问: 也许它也确定调用SwingUtilities#invokeLater()里面的后台线程?

Another SSCCE to illustrate: 另一个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;
}

In this SSCCE, we are updating the DeterminateLoadingFrame to update the user about what is happening, while publishing the results through publish() and process() . 在此SSCCE中,我们将更新DeterminateLoadingFrame以向用户更新正在发生的事情,同时通过publish()process() publish()结果。

This seems as unorthodox as the original question asked yesterday. 这似乎与昨天的原始问题一样是非常规的。 Yet I am unable to come up with any reason it would be not a good idea. 但是我无法提出任何理由,这不是一个好主意。 We are adhering to proper threading policies. 我们坚持适当的线程策略。 And updating the GUI in this way would seem (to me) to be incredibly useful and versatile. 对我而言,以这种方式更新GUI似乎非常有用且用途广泛。 Much moreso, in fact, than process() and publish() . 实际上,比process()publish()还要多。

Further: accomplishing what the SSCCE is illustrating would be harder strictly using process() and publish() because how would you pass the value of i to the process() method? 此外:严格地使用process()publish()来完成SSCCE所说明的内容将更加困难,因为您将如何将i的值传递给process()方法?

Another solution is to use SwingWorker's innate SwingPropertyChangeSupport and notify all listeners of changes of pertinent bound properties. 另一种解决方案是使用SwingWorker的固有SwingPropertyChangeSupport并将相关绑定属性的更改通知所有侦听器。 You could use the progress bound property that SwingWorkers already have, but you can only use this if your property will go from 0 to 100. If you have one going to a different value (here 1000), you'll have to use your own. 您可以使用SwingWorkers已经拥有的进度绑定属性,但是只有当您的属性从0变为100时,才可以使用此属性。如果您将一个属性设置为其他值(此处为1000),则必须使用自己的。 Since the PropertyChangeSupport object is a SwingPropertyChangeSupport, then all notifications will be done on the Swing event thread. 由于PropertyChangeSupport对象是SwingPropertyChangeSupport,因此所有通知都将在Swing事件线程上完成。 I like the uncoupling that this provides -- the SwingWorker need know nothing about who is listening to what, or what is done to the information. 我喜欢这样提供的解耦功能-SwingWorker不需要知道谁在听什么,或者对信息做了什么。

eg, 例如,

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;

  }

  // .....

and

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
}

As an aside: 作为旁白:

  • You should use a dialog window such as a JDialog for any secondary windows, not another JFrame. 您应该将对话框窗口(例如JDialog)用于任何辅助窗口,而不是另一个JFrame。
  • Try to get unnecessary NetBeans-generated code out of your SSCCE. 尝试从SSCCE中删除不必要的NetBeans生成的代码。 It only serves to distract from the code that is the meat of your question. 它只会分散您所关注的代码的注意力。

It's correct, but it doesn't benefit from all the work done by the SwingWorker to batch updates and let you avoid using these low-level SwingUtilities methods. 没错,但是它不能从SwingWorker进行的所有工作中受益,因为它们可以批量更新,并且可以避免使用这些底层SwingUtilities方法。 It also couples the background process and the UI updates instead of decoupling them elegantly. 它还将后台进程和UI更新耦合在一起,而不是优雅地将它们分离。

How to pass i to the publish method: create a class containing this counter and the row: 如何将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);
    }
    ...
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM