Programming isn't easy, and adding a user interface around functionality can really make life difficult. Especially since not all UI frameworks are thread safe (including Swing). So how do we efficiently handle the UI, run the worker code, and communicate data between the two, all while keeping the UI responsive?
Luckily for users of Swing, there are a few options, both of which can make programming GUIs much simpler. Here are two of those options.
Invoke Later
SwingUtilities.invokeLater()
is great for updating the UI from another thread. Maybe you have a long-running task, and you'd like to update a progress bar to provide feedback for the user. You could try something like this:
startButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
progressBar.setValue(0);
progressBar.setStringPainted(true);
// Runs outside of the Swing UI thread
new Thread(new Runnable() {
public void run() {
for (i = 0; i <= 100; i++) {
// Runs inside of the Swing UI thread
SwingUtilities.invokeLater(new Runnable() {
public void run() {
progressBar.setValue(i);
}
});
try {
java.lang.Thread.sleep(100);
}
catch(Exception e) { }
}
}
}).start();
}
});
Hopefully you can see from this example how you might use SwingUtilities.invokeLater()
in order to communicate between UI and worker threads. You can think of invokeLater
as a simple callback to the UI for sending whatever updates you need.
Swing Worker
SwingWorker<T,V>
can be used similarly to invokeLater
, but each has its strong points. Personally, I prefer to use SwingWorker
for long-running tasks that I don't need to update the UI for (like loading a large document), while invokeLater
might be used more for long-running tasks that do need to update the UI. SwingWorker
can be used in this way with:
private Document doc;
JButton button = new JButton("Open XML");
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// All code inside SwingWorker runs on a seperate thread
SwingWorker<Document, Void> worker = new SwingWorker<Document, Void>() {
@Override
public Document doInBackground() {
Document intDoc = loadXML();
return intDoc;
}
@Override
public void done() {
try {
doc = get();
} catch (InterruptedException ex) {
ex.printStackTrace();
} catch (ExecutionException ex) {
ex.printStackTrace();
}
}
};
// Call the SwingWorker from within the Swing thread
worker.execute();
}
});
This class breaks out worker events in to methods that can be implemented depending on your needs. For more advanced usage, check out the publish(V... chunks)
and process(List<V> chunks)
methods.