java - 从 SwingWorker 实时更新 GUI

标签 java swing swingworker event-dispatch-thread invokelater

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

考虑到在 SwingWorker#doInBackground() 内部调用 SwingUtilities#invokeAndWait() 可能是可以的,我想更进一步并问:在后台线程内调用 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() 更重要。

进一步:严格使用 process()publish() 来完成 SSCCE 所说明的内容会更困难,因为您将如何传递 i< 的值process() 方法?

最佳答案

另一个解决方案是使用 SwingWorker 固有的 SwingPropertyChangeSupport 并通知所有监听器相关绑定(bind)属性的更改。您可以使用 SwingWorkers 已有的进度绑定(bind)属性,但只有当您的属性从 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 生成的代码。它只会分散您对问题核心代码的注意力。

关于java - 从 SwingWorker 实时更新 GUI,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20586373/

相关文章:

java - 从一个 JFrame 获取信息并将其放入另一个 JFrame 中

java - 在 Java 中追加到数组

java - JCS 过期/删除通知

java - 是否允许在非 EDT 线程中加载 Swing 类?

Java - 如何使用 SwingWorker 创建多线程游戏

java - Swagger 2.0 (OpenApi 3.0) 中的 BeanConfig(或类似的?)

java - 将监听器定义的字符串变量导入到另一个包中

java - IDE 的垂直文档兴趣点突出显示组件。它叫什么?

java - 在循环(线程)中制作 JProgressBar 更新值时出现问题

java - 安全地打开和关闭模态 JDialog(使用 SwingWorker)