Как делегировать публикацию SwingWorker другим методам - PullRequest
5 голосов
/ 26 мая 2010

Моя «проблема» может быть описана следующим образом. Предположим, у нас интенсивный процесс, который мы хотим запустить в фоновом режиме, и он должен обновлять панель Swing JProgress. Решение простое:

import java.util.List;

import javax.swing.JOptionPane;
import javax.swing.JProgressBar;
import javax.swing.SwingWorker;


/**
 * @author Savvas Dalkitsis
 */
public class Test {

    public static void main(String[] args) {
        final JProgressBar progressBar = new JProgressBar(0,99);
        SwingWorker<Void, Integer> w = new SwingWorker<Void, Integer>(){

            @Override
            protected void process(List<Integer> chunks) {
                progressBar.setValue(chunks.get(chunks.size()-1));
            }

            @Override
            protected Void doInBackground() throws Exception {

                for (int i=0;i<100;i++) {
                    publish(i);
                    Thread.sleep(300);
                }

                return null;
            }

        };
        w.execute();
        JOptionPane.showOptionDialog(null,
                new Object[] { "Process", progressBar }, "Process",
                JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE,
                null, null, null);
    }

}

Теперь предположим, что у меня есть различные методы, которые занимают много времени. Например, у нас есть метод, который загружает файл с сервера. Или другой, который загружает на сервер. Или что-нибудь действительно. Как правильно делегировать метод publish этим методам, чтобы они могли соответствующим образом обновить GUI?

На данный момент я обнаружил следующее (предположим, что метод "aMethod" находится, например, в каком-то другом пакете):

import java.awt.event.ActionEvent;
import java.util.List;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JOptionPane;
import javax.swing.JProgressBar;
import javax.swing.SwingWorker;


/**
 * @author Savvas Dalkitsis
 */
public class Test {

    public static void main(String[] args) {
        final JProgressBar progressBar = new JProgressBar(0,99);
        SwingWorker<Void, Integer> w = new SwingWorker<Void, Integer>(){

            @Override
            protected void process(List<Integer> chunks) {
                progressBar.setValue(chunks.get(chunks.size()-1));
            }

            @SuppressWarnings("serial")
            @Override
            protected Void doInBackground() throws Exception {

                aMethod(new AbstractAction() {

                    @Override
                    public void actionPerformed(ActionEvent e) {
                        publish((Integer)getValue("progress"));
                    }
                });

                return null;
            }

        };
        w.execute();
        JOptionPane.showOptionDialog(null,
                new Object[] { "Process", progressBar }, "Process",
                JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE,
                null, null, null);
    }

    public static void aMethod (Action action) {
        for (int i=0;i<100;i++) {
            action.putValue("progress", i);
            action.actionPerformed(null);
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

Это работает, но я знаю, что ему чего-то не хватает. Есть мысли?

Ответы [ 3 ]

10 голосов
/ 26 июля 2011

(я обновляю свой ответ, чтобы сделать его более понятным и обобщенным)

Несмотря на то, что вы успешно развязали свою логику и представление, это не сделано так, чтобы можно было повторно использовать код. Java PropertyChangeSupport позволяет легко отделить логику от презентации, реализуя связанные свойства и получить существенное повторное использование. Идея состоит в том, чтобы использовать обработчики событий вместо объектов действий.

Во-первых, концептуализировать абстракцию. Фоновая работа должна периодически «выкрикивать» (публиковать) графический интерфейс, и графический интерфейс должен его прослушивать. Два общих класса будут кодифицировать эту идею:

/**
 * Wrapper for the background logic.
 *
 * <T> return type
 * <S> intermediary type (the "shout out")
 */
public static abstract class LoudCall<T, S> implements Callable<T> {

    private PropertyChangeSupport pcs;
    private S shout;

    public LoudCall() {
        pcs = new PropertyChangeSupport(this);
    }

    public void shoutOut(S s) {
        pcs.firePropertyChange("shoutOut", this.shout, 
                this.shout = s);
    }

    public void addListener(PropertyChangeListener listener) {
        pcs.addPropertyChangeListener(listener);
    }

    public void removeListener(PropertyChangeListener listener) {
        pcs.removePropertyChangeListener(listener);
    }

    @Override
    public abstract T call() throws Exception;
}

/**
 * Wrapper for the GUI listener.
 *
 * <T> return type
 * <S> intermediary type (the "shout out" to listen for)
 */
public static abstract class ListenerTask<T, S> extends SwingWorker<T, S> 
        implements PropertyChangeListener {

    private LoudCall<T, S> aMethod;

    public ListenerTask(LoudCall<T, S> aMethod) {
        this.aMethod = aMethod;
    }

    @Override
    protected T doInBackground() throws Exception {
        aMethod.addListener(this);
        return aMethod.call();
    }

    @Override
    public void propertyChange(PropertyChangeEvent evt) {
        if ("shoutOut".equals(evt.getPropertyName())) {
            publish((S)evt.getNewValue());
        }
    }

    @Override
    protected abstract void process(List<S> chunks);
}

Эти классы могут использоваться для всех ваших виджетов Swing. Для ProgressBar «кричать» будет Integer, а тип возвращаемого значения - Void:

public class ProgressExample {  
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
    @Override
    public void run() {

        // 1. setup the progress bar
        final JProgressBar progressBar = new JProgressBar(0, 99);

        // 2. Wrap the logic in a "Loud Call"
        LoudCall<Void, Integer> aMethod = new LoudCall<Void, Integer>() {
            @Override
            public Void call() throws Exception {
                for (int i = 0; i < 100; i++) {
                    // "i have an update for the GUI!"
                    shoutOut(i);
                    Thread.sleep(100);
                }
                return null;
            }
        };

        // 3. Run it with a "Listener Task"
        (new ListenerTask<Void, Integer>(aMethod) {
            @Override
            protected void process(List<Integer> chunks) {
                progressBar.setValue(chunks.get(chunks.size() - 1));
            }
        }).execute();

        // 4. show it off!
        JOptionPane.showOptionDialog(null,
            new Object[] { "Process", progressBar }, "Process",
            JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE,
            null, null, null
        );
    }
        });
    }
}

Только слушатель должен знать что-либо о деталях GUI, а фоновая логика все еще контролирует публикацию (косвенно, «крича»). Этот код более краткий, читаемый и многократно используемый.

Я понимаю, что этот вопрос довольно старый, но, надеюсь, он кому-нибудь поможет!

0 голосов
/ 26 мая 2010

Я столкнулся с подобной проблемой. Вот что я нашел, может быть, в самом правильном ответе не хватает, но давайте попробуем:

  • если у нас много итераций, мы можем обновить индикатор выполнения в методе doInBackGround(). JProgressBar - это параметр конструктора нашего SwingWorker, который расширяет SwingWorker (так что да, мы используем custom)
  • если у нас нет итераций и мы не можем прервать метод, выполнение которого занимает много времени, мы можем либо все испортить, и сделать это как большинство людей (поэтому наш индикатор выполнения не имеет линейного процесса, а просто обновляет его значения после частичной работы). Плохая новость заключается в том, что если наш метод является единственным, который рабочий (например, отправляет электронную почту в фоновом режиме), индикатор выполнения становится внезапно заполненным . Не очень приятно, давайте взглянем на третий вариант
  • это может быть довольно сумасшедшим и замедление производительности, это все потому, что наше приложение должно быть fancy . Итак, давайте перейдем к исходному коду метода, выполнение которого занимает много времени. Мы переопределяем его и вставляем точно такой же код, но добавляем еще один параметр - угадайте, что да, JProgressBar. Внутри метода мы создаем Thread, который будет работать до тех пор, пока некоторый логический параметр (флаг, указывающий на то, что метод окончательно завершен) не будет установлен в true. Поток будет обновлять JProgressBar через некоторые разумные интервалы. Самая большая проблема - предположить, какой разумный интервал. Мы должны пройти некоторые тесты и оценить значение для интервалов.

В третьем пункте я описал, как выполнить Thread из метода, который выполняет некоторую задачу, которая не является итеративной (по крайней мере, в нашем коде Java) и не может быть прервана. Поток обновляет JProgressBar, который был задан как параметр метода. Это, однако, определенно медленнее как чистый метод, вызывающий

0 голосов
/ 26 мая 2010

Возможно, сделайте SwingWorker для каждого длинного метода. Каждый SwingWorker имеет свой собственный уровень прогресса.

Каждый SwingWorker обновляет свой собственный уровень прогресса во время метода doInBackground, а затем вызывает публикацию. Внутри метода процесса, то есть внутри EDT, каждый SwingWorker считывает свой уровень прогресса и обновляет общую диаграмму прогресса модели и видения.

...