Threading with Swing

来源:互联网 发布:降低mac svn版本号 编辑:程序博客网 时间:2024/05/22 20:00

Threading with Swing (ctd):
SwingUtilities.invokeLater()

In our introduction to threading with Swing,we said that any updates to the user interface must happen on theevent dispatch thread.So from any other thread— in practice, that means code thatisn'tcalled directly from an event handler— we must specifically arrange for our GUI updatecode, and generallyonly 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 userinterface:

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 baror some other component showing the current progress to the user. How can we dothis if we're no longer in the event dispatch thread? Well, theSwingUtilitiesclass, which provides various useful little calls, includes a method calledinvokeLater(). This method allows us to post a "job" to Swing, whichit will then run on the event dispatch thread at its next convenience. Sohere is how to useSwingUtilities.invokeLater() from out runQueriesmethod:

// Called from non-UI threadprivate 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 mustmake sure that the code to update it is inside a call toinvokeLater().

There's a bit of awkward syntax that we've glossed over, but which it's importantto get used to for Swing programming generally. Essentially, we use ananonumous inner class to define our "job"— morespecifically, an implementation of theRunnable interface. Anonymousinner classes are a bit of syntactic shortcut. We could also have writtensomething like:

// Called from non-UI threadprivate 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);    }  });}

But usually, it's a bit tedious to have to write a separate class definitionfor every pattern of update job. (Note that either way, they stillcompileto a different class.)

Application startup code

There's one place where it's very easy to forget that we needSwingUtilities.invokeLater(), and that's on application startup. Ourapplicationsmain() method will always be called by a special "main" thread that the VM starts up for us. And this main thread isnot theevent dispatch thread! So:

The code that initialises our GUImust also take place in aninvokeLater().

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  }}