Making Swing Groovy, Part III: EDT - No Fluff Just Stuff

Making Swing Groovy, Part III: EDT

Posted by: Kenneth Kousen on October 13, 2008


In this entry in my “Making Swing Groovy” series, I want to talk about threading issues. Specifically, how to work with the Event Dispatch Thread. As a step along the way, let me first respond to a comment made about my first post in this series.

Kirill Grouchnikov collects interesting Swing-related links every week. He was kind enough to link to my first post, and rightly pointed out that in my Java example, I’d make some errors. As he said,

“… the Swing example is not the best one, violating the EDT rules, working with the content pane and not centering the frame in the monitor, and the author admits some of these points.”

Let me say up front that I’m grateful for his comments. That’s how I learn. Part of the reason I post here (other than just the joy of sharing what I’ve learned) is to find out what I’ve been doing wrong, or, if I’m okay, what I can do better. One of the principles I live by is, “I’m often wrong, but I don’t stay wrong.” So keep those cards and letters coming. :)

Here’s the code that Kirill addressed:


import java.awt.Container;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTextField;
import javax.swing.WindowConstants;

public class EchoGUI extends JFrame {
	private JTextField input = new JTextField(20);
	private JLabel prompt = new JLabel("Input text: ");
	private JLabel echo = new JLabel("Echo: ");
	private JLabel output = new JLabel();

	private Container cp = this.getContentPane();

	public EchoGUI() {
		super("Echo GUI");
		cp.setLayout(new GridLayout(0,2));  // 2 cols, as many rows as necessary

		cp.add(prompt);
		cp.add(input);
		cp.add(echo);
		cp.add(output);

		input.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				output.setText(input.getText());
			}
		});

		this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
		setSize(300,100);
		setVisible(true);
	}

	public static void main(String[] args) {
		new EchoGUI();
	}
}

First, Kirill was right about my working with the content pane rather than adding my components directly to the JFrame instance. I remember way back in the Java 1.0 days (holy AWT, Batman), you added components directly to the instance of java.awt.Frame. Then, when Swing came along, you weren’t supposed to do that any more. Instead, you added to the content pane in front of the JFrame. That held true for Java 1.2, 1.3, and 1.4. Finally, in Java 1.5, you could go directly to the JFrame again, because now adding to the frame redirects to adding to the content pane. I need to retrain myself to do that.

Here is a link to a good post that explains why everything changed and then changed back.

Another of Kirill’s comments was that I didn’t center the frame on the monitor. I used to do that via code like


Toolkit tk = Toolkit.getDefaultToolkit();
Dimension screenSize = tk.getScreenSize();

From that I can get the width and height of the screen, and then using the width and height of the frame I can center everything. I can do that, but here it seems a bit like overkill.

Finally, we come to the real issue, which is also the subject of this post. As he said, I violated the EDT rules, and that’s bad.

Arguably Rule #1 of Swing GUI’s is “Only update GUI Elements in the Event Dispatch Thread (EDT).” From what I gather from frequent posts about that issue across the web, this is an error as common to Swing developers as forgetting to close database connections is when writing JDBC code. And again, this is one of those situations in Swing where the rules have changed multiple times, although maybe it’s more accurate to say that the rule hasn’t changed, but the way to implement it has.

First, some quick background. Every Swing program involves multiple threads. First, there are the initializing threads, which start up the GUI. In a Java application, that’s the main method. Then there is the Event Dispatch Thread, which maintains an Event queue that handles all the GUI updates, and finally there are any background threads that are used to manage long-running processes. If you make sure that non-GUI processes are off the EDT, then the GUI will remain responsive while the application is doing other work.

As of JDK 1.6, the standard library now has the class javax.swing.SwingUtilities, which contains the methods invokeLater and invokeAndWait. According to the concurrency lesson in the Swing tutorial, the proper way to create a GUI is


public static void main(String[] args) {
    SwingUtilities.invokeLater(new Runnable() {
        @Override
        void run() {
            \\  ... instantiate the GUI ...
        }
    );
}

The invokeLater method creates the Runnable object and gives it to the EDT to add to the event queue. The alternative method invokeAndWait does the same thing, but it blocks until the task is completed.

What does Groovy bring to this picture? As usual, Groovy simplifies matters. Groovy adds three helpful methods to groovy.swing.SwingBuilder: edt, doLater, and doOutside.

What do they do? One of the best things about using an open source API is that, well, you have access to the source code. For the edt method, the implementation looks like (paraphrased):


public SwingBuilder edt(Closure c) {
    if (SwingUtilities.isEventDispatchThread()) {
        c.call(this)
    } else {
        // ...
       SwingUtilities.invokeAndWait(c)
       // ...
    }
}

In other words, if we’re in the EDT already, invoke the closure. If not, call invokeAndWait to get to the EDT.

Likewise, for doLater:


public SwingBuilder doLater(Closure c) {
    // ...
   SwingUtilities.invokeLater(c)
   // ...
}

and for doOutside:


public SwingBuilder doOutside(Closure c) {
    // ...
   Thread.start(c)
    // ...
}

So what’s the bottom line?

  • edt: use the EDT through invokeAndWait
  • doLater: use the EDT through invokeLater
  • doOutside: create a worker thread and start it (off the EDT)

Incidentally, SwingBuilder also has a build method specifically designed to construct the GUI. How does it handle threading?


public static SwingBuilder build(Closure c) {
    SwingBuilder builder = new SwingBuilder()
    return builder.edt(c)
}

That’s the whole method — create the buillder and then build the GUI on the EDT, synchronously. That’s why all of our previous examples of SwingBuilder, which only used the build method on the builder, ran correctly. They’re doing the initial construction on the EDT, as they should.

Ultimately, practical examples of Groovy Swing code uses actions that take advantage of these methods, as in


swing.actions() {
    action(name:'myMethod') {

        doOutside {
            model.prop = textField.text
            // ... other model property updates ...
            def ok = someLongRunningService(model)

            doLater {
                model.ok = ok
}  }   }  }

And there you have it. Grab the data and update the model outside the EDT, then run the long running application. When it’s finished, update the GUI in the EDT. (Note that here we’re updating the model in the EDT because the model properties are bound to the GUI elements, so changes in model properties will result in a GUI update. See my previous post in this series for details.)

From here it’s only a short step to Griffon.

      
Kenneth Kousen

About Kenneth Kousen

Ken Kousen is a Java Champion, several time JavaOne Rock Star, and a Grails Rock Star. He is the author of the Pragmatic Library books “Mockito Made Clear” and “Help Your Boss Help You,” the O'Reilly books “Kotlin Cookbook”, “Modern Java Recipes”, and “Gradle Recipes for Android”, and the Manning book “Making Java Groovy”. He also has recorded over a dozen video courses for the O'Reilly Learning Platform, covering topics related to Android, Spring, Java, Groovy, Grails, and Gradle.

His academic background include BS degrees in Mechanical Engineering and Mathematics from M.I.T., an MA and Ph.D. in Aerospace Engineering from Princeton, and an MS in Computer Science from R.P.I. He is currently President of Kousen IT, Inc., based in Connecticut.

Why Attend the NFJS Tour?

  • » Cutting-Edge Technologies
  • » Agile Practices
  • » Peer Exchange

Current Topics:

  • Languages on the JVM: Scala, Groovy, Clojure
  • Enterprise Java
  • Core Java, Java 8
  • Agility
  • Testing: Geb, Spock, Easyb
  • REST
  • NoSQL: MongoDB, Cassandra
  • Hadoop
  • Spring 4
  • Cloud
  • Automation Tools: Gradle, Git, Jenkins, Sonar
  • HTML5, CSS3, AngularJS, jQuery, Usability
  • Mobile Apps - iPhone and Android
  • More...
Learn More »