My "problem" can be described by the following. Assume we have an intensive process that we want to have running in the background and have it update a Swing JProgress bar. The solution is easy:
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);
}
}
Now assume that i have various methods that take a long time. For instance we have a method that downloads a file from a server. Or another that uploads to a server. Or anything really. What is the proper way of delegating the publish method to those methods so that they can update the GUI appropriately?
What i have found so far is this (assume that the method "aMethod" resides in some other package for instance):
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 (InterruptedExc开发者_如何学Ception e) {
e.printStackTrace();
}
}
}
}
It works but i know it lacks something. Any thoughts?
(I'm updating my answer to make it more clear and generalized)
Although you have successfully decoupled your logic and presentation, it's not done in a way that lends itself to code reuse. Java's PropertyChangeSupport makes it easy to decouple the logic from the presentation by implementing bound properties, and get some substantial reuse. The idea is to use event handlers instead of action objects.
First, conceptualize the abstraction. The background work needs to "shout out" (publish) to the GUI intermittently, and the GUI needs to listen for it. Two generic classes will codify this idea:
/**
* 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);
}
These classes can be used for all of your Swing widgets. For a ProgressBar, the "shout out" will be an Integer, and the return type is 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
);
}
});
}
}
Only the listener needs to know anything about the GUI details, and the background logic still has control over publishing (indirectly, by "shouting"). This code is more terse, readable, and reusable.
I realize this question is pretty old now, but hopefully it helps somebody!
Perhaps do a SwingWorker for each long method. Each SwingWorker has it's own progress level.
Each SwingWorker update it's own progress level during doInBackground method, then call publish. Inside the process method, so inside the EDT, each SwingWorker read it's progress level, and update model and vision general progress bar.
I faced similar problem. Here is what I found, maybe the really correct answer lacks, but let's give it a try :
- if we have lot of iterations, we can update progress bar in
doInBackGround()
method. JProgressBar is constructor parameter of our SwingWorker which extends SwingWorker (so yes, we use custom) - if we do not have iterations and we can not interrupt method which takes lot of time to complete, we can either screw the whole thing and do it as most people (so our progress bar has not linear process but it just refreshes it values after partial job is done). Bad news is, if our method is the only which worker does (f.e. sending e-mail on background) progress bar will become suddenly full. Not very nice though, lets take a look on third option
- this might be quite crazy and performance slowing, it's all because our app must be fancy. So let's get to the source code of method which takes long time to complete. We override it and paste exactly same code, but we add one more parameter - guess what, yes JProgressBar. Inside of method we create Thread which will run until some boolean parameter (flag indicating method is finally completed) is set on true. Thread will keep updating JProgressBar in some reasonable intervals. The biggest problem is to assume, what is the reasonable interval. We should take some tests and estimate value for intervals.
In third point I described how to execute Thread from method which completes some task which is not iterative (at least not in our Java code) and can not be interrupted. Thread updates JProgressBar which was given as a method parameter. This is , however, definitelly slower as pure method calling
精彩评论