我的“问题”可以用以下描述。假设我们有一个密集的过程,我们希望在后台运行并让它更新一个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();
}
}
}
}
它有效,但我知道它缺少一些东西。有什么想法吗?
答案 0 :(得分:10)
(我正在更新我的答案,以使其更清晰和更全面)
虽然您已经成功地解耦了逻辑和表示,但它并没有以适合代码重用的方式完成。 Java PropertyChangeSupport通过实现bound properties可以轻松地将逻辑与表示分离,并获得一些实质性的重用。我们的想法是使用事件处理程序而不是操作对象。
首先,概念化抽象。后台工作需要间歇性地“喊出”(发布)到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,“shout out”将是一个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细节的任何信息,而后台逻辑仍然可以控制发布(间接地,通过“喊叫”)。此代码更简洁,可读且可重用。
我意识到这个问题现在已经很老了,但希望对某些人有帮助!
答案 1 :(得分:0)
也许为每个长方法做一个SwingWorker。每个SwingWorker都有自己的进度级别。
每个SwingWorker在doInBackground方法期间更新它自己的进度级别,然后调用publish。在流程方法内部,所以在EDT内部,每个SwingWorker都会读取它的进度级别,并更新模型和愿景的一般进度条。
答案 2 :(得分:0)
我遇到了类似的问题。这是我发现的,也许真正正确的答案缺乏,但让我们试一试:
doInBackGround()
方法中更新进度条。 JProgressBar是SwingWorker的构造函数参数,它扩展了SwingWorker(所以是的,我们使用自定义)在第三点中,我描述了如何从方法执行Thread,该方法完成了一些非迭代的任务(至少在我们的Java代码中没有)并且不能被中断。线程更新作为方法参数给出的JProgressBar。然而,这是 definitelly slow 作为纯方法调用