Обновление графического интерфейса в реальном времени от SwingWorker

Хорошо, это следующий вопрос к моему вчерашнему вопросу: " Обработка ошибок в 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() метод?

2 ответа

Другим решением является использование врожденного SwingWperer's 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.
  • Попробуйте извлечь ненужный код, сгенерированный NetBeans, из своего SSCCE. Это только отвлекает от кода, который является основой вашего вопроса.

Это правильно, но это не приносит пользы от всей работы, выполняемой SwingWorker для пакетного обновления, и позволяет избежать использования этих низкоуровневых методов SwingUtilities. Он также сочетает в себе фоновый процесс и обновления пользовательского интерфейса вместо элегантного их разделения.

Как пройти i к методу публикации: создайте класс, содержащий этот счетчик и строку:

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);
    }
    ...
}
Другие вопросы по тегам