I am trying to follow the Java best practices by not doing long tasks on the main thread (EDT). So I am planning to use a swingWorker with Modal Dialog. This way the modal dialog blocks the user for doing anything until that task is done and I can update status on the dialog while the process is taking place.
Now the problem is that with the modal dialog, it not only blocks the user but also nothing after setVisible gets called
So if I do
dialog.setVisible(true);
new SwingWorkerTask().execute(); //This does not get called
and if I do
new SwingWorkerTask().execute();
dialog.setVisible(true); // Well what the point of setting visible after the fact.
So How do I block user action and show a dialog while task is taking place?
Thank you
It is only a chicken/egg if you make it such. You can construct all Swing objects on EDT and then let your SwingWorker
(or any other thread) govern all updates by instructing EDT to execute them via SwingUtilities.invokeLater(Runnable)
.
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JProgressBar;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
public class RudeProgressBar extends JFrame {
private JButton button;
public RudeProgressBar() {
setTitle("Rude Progress Bar");
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLayout(new BorderLayout());
button = new JButton("Do teh work");
add(button, BorderLayout.SOUTH);
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
JDialog dialog = new JDialog(RudeProgressBar.this, true);
dialog.setTitle("Doing teh work");
dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
final JProgressBar progressBar = new JProgressBar(0, 100);
dialog.setLayout(new BorderLayout());
dialog.add(progressBar);
dialog.setSize(100, 100);
dialog.setLocationRelativeTo(RudeProgressBar.this);
MyTask task = new MyTask(dialog);
task.addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if ("progress".equals(evt.getPropertyName())) {
progressBar.setValue((Integer)evt.getNewValue());
}
}
});
task.execute();
}
});
setSize(200, 200);
setLocationRelativeTo(null);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
new RudeProgressBar().setVisible(true);
}
});
}
private class MyTask extends SwingWorker<Void, Void> {
private final JDialog dialog;
public MyTask(JDialog dialog) {
this.dialog = dialog;
}
@Override
protected Void doInBackground() throws Exception {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
dialog.setVisible(true);
}
});
int progress = 0;
for (int i = 0; i < 5; i++) {
Thread.sleep(1000);
setProgress(progress += 20);
}
return null;
}
@Override
protected void done() {
dialog.setVisible(false);
dialog.dispose();
}
}
}
If you are worried that the invokeLater
implementation (inside SwingWorker.doInBackground
) might get executed after SwingWorker.done
, simply put the code in done
into another invokeLater
. By doing this, you queue your Runnable
implementations for EDT to execute them in certain order. The queuing will happen even if this method is called from EDT itself.
Note that if you take a look at SwingWorker
implementation, you'll see that it relies on javax.swing.Timer
to execute done()
and the Timer
itself calls invokeLater
, so calling it inside done
again amounts to doing nothing. Nothing will be wrong if you do it, however.