5

我的“问题”可以用以下方式描述。假设我们有一个密集的进程,我们希望在后台运行并让它更新一个 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);
    }

}

现在假设我有各种需要很长时间的方法。例如,我们有一个从服务器下载文件的方法。或者另一个上传到服务器的。或者真的什么。将发布方法委托给这些方法以便它们可以适当地更新 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();
            }
        }
    }

}

它有效,但我知道它缺少一些东西。有什么想法吗?

4

3 回答 3

10

(我正在更新我的答案以使其更加清晰和概括)

尽管您已经成功地解耦了逻辑和表示,但它并没有以适合代码重用的方式完成。Java 的PropertyChangeSupport通过实现绑定属性可以轻松地将逻辑与表示分离,并获得一些实质性的重用。这个想法是使用事件处理程序而不是操作对象。

首先,将抽象概念化。后台工作需要间歇性地向GUI“喊”(发布),GUI需要监听。两个通用类将编码这个想法:

/**
 * 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 细节的任何事情,并且后台逻辑仍然可以控制发布(间接地,通过“喊”)。这段代码更简洁、可读和可重用。

我意识到这个问题现在已经很老了,但希望它对某人有所帮助!

于 2011-07-26T18:35:00.513 回答
0

我遇到了类似的问题。这是我发现的,也许缺少真正正确的答案,但让我们试一试:

  • 如果我们有很多迭代,我们可以在doInBackGround()方法中更新进度条。JProgressBar 是我们的 SwingWorker 的构造函数参数,它扩展了 SwingWorker(所以是的,我们使用自定义)
  • 如果我们没有迭代并且我们不能中断需要大量时间才能完成的方法,我们可以把整个事情搞砸并像大多数人一样去做(所以我们的进度条不是线性过程,但它只是在部分后刷新它的值工作完成)。坏消息是,如果我们的方法是唯一的工作人员(fe 在后台发送电子邮件)进度条会突然变满。虽然不是很好,让我们看看第三个选项
  • 这可能非常疯狂并且性能变慢,这都是因为我们的应用程序必须是花哨的。那么让我们来看看需要很长时间才能完成的方法的源代码。我们覆盖它并粘贴完全相同的代码,但我们又添加了一个参数——猜猜看,是的 JProgressBar。在方法内部,我们创建 Thread 它将一直运行,直到某个布尔参数(标志指示方法最终完成)设置为 true。线程将在一些合理的时间间隔内不断更新 JProgressBar 。最大的问题是假设,合理的间隔是多少。我们应该进行一些测试并估计间隔的值。

在第三点中,我描述了如何从完成某些非迭代任务(至少在我们的 Java 代码中不是)且不能被中断的方法中执行 Thread。线程更新作为方法参数给出的 JProgressBar。但是,这绝对比纯方法调用慢

于 2010-05-25T21:31:33.967 回答
0

也许为每个长方法做一个 SwingWorker。每个 SwingWorker 都有自己的进度级别。

每个 SwingWorker 在 doInBackground 方法期间更新它自己的进度级别,然后调用发布。在 process 方法内部,因此在 EDT 内部,每个 SwingWorker 读取它的进度级别,并更新模型和视觉通用进度条。

于 2010-05-25T21:14:55.083 回答