Threading with Swing (ctd):
SwingUtilities.invokeLater()
The SwingUtilities.invokeLater() method is an extremely important method to know about if you are
writing a Java application that uses multithreading and your program uses Swing for its user interface.
In our introduction to threading with Swing,
we said that any updates to the user interface must happen on the event dispatch thread.
So from any other thread— in practice, that means code that isn't
called directly from an event handler— we must specifically arrange for our GUI update
code, and generally only that code, to be called on the event dispatch thread.
So, supposing we have a button that launches a series of database queries.
We dutifully start up a new thread so that our queries won't block the user
interface:
JButton b = new JButton("Run query");
b.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
Thread queryThread = new Thread() {
public void run() {
runQueries();
}
};
queryThread.start();
}
});
That was the easy bit. But now, from our query thread, we want to update a progress bar
or some other component showing the current progress to the user. How can we do
this if we're no longer in the event dispatch thread? Well, the SwingUtilities
class, which provides various useful little calls, includes a method called
invokeLater(). This method allows us to post a "job" to Swing, which
it will then run on the event dispatch thread at its next convenience. So
here is how to use SwingUtilities.invokeLater() from out runQueries
method:
// Called from non-UI thread
private void runQueries() {
for (int i = 0; i < noQueries; i++) {
runDatabaseQuery(i);
updateProgress(i);
}
}
private void updateProgress(final int queryNo) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// Here, we can safely update the GUI
// because we'll be called from the
// event dispatch thread
statusLabel.setText("Query: " + queryNo);
}
});
}
Here, statusLabel would be a JLabel or JTextField or something of that ilk—
it doesn't matter terribly much. The point is: whatever GUI component it is, we must
make sure that the code to update it is inside a call to invokeLater().
There's a bit of awkward syntax that we've glossed over, but which it's important
to get used to for Swing programming generally.
Essentially, we use an anonumous inner class to define our "job"— more
specifically, an implementation of the Runnable interface. Anonymous
inner classes are a bit of syntactic shortcut. We could also have written
something like:
class UpdateJob implements Runnable {
private final String progress;
UpdateJob(String progress) {
this.progress = progress;
}
public void run() {
statusLabel.setText(progress);
}
}
...
Runnable task = new UpdateJob("Query: " + i);
SwingUtilities.invokeLater(task);
But usually, it's a bit tedious to have to write a separate class definition
for every pattern of update job. (Note that either way, they still compile
to a different class.)
Application startup code
There's one place where it's very easy to forget that we need
SwingUtilities.invokeLater(), and that's on application startup. Our
applications main() method will always be called by a special "main"
thread that the VM starts up for us. And this main thread is not the
event dispatch thread! So:
The code that initialises our GUI
must also take place in an invokeLater().
So our initial main() method should look something like this:
public class MyApplication extends JFrame {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
MyApplication app = new MyApplication();
app.setVisible(true);
}
});
}
private MyApplication() {
// create UI here: add buttons, actions etc
}
}
If you enjoy this Java programming article, please share with friends and colleagues. Follow the author on Twitter for the latest news and rants.
Editorial page content written by Neil Coffey. Copyright © Javamex UK 2021. All rights reserved.