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:
[sourcecode language=”java”]
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();
}
}
[/sourcecode]
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
[sourcecode language=”java”]
Toolkit tk = Toolkit.getDefaultToolkit();
Dimension screenSize = tk.getScreenSize();
[/sourcecode]
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
[sourcecode language=”java”]
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
void run() {
\\ … instantiate the GUI …
}
);
}
[/sourcecode]
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):
[sourcecode language=”java”]
public SwingBuilder edt(Closure c) {
if (SwingUtilities.isEventDispatchThread()) {
c.call(this)
} else {
// …
SwingUtilities.invokeAndWait(c)
// …
}
}
[/sourcecode]
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
:
[sourcecode language=”java”]
public SwingBuilder doLater(Closure c) {
// …
SwingUtilities.invokeLater(c)
// …
}
[/sourcecode]
and for doOutside
:
[sourcecode language=”java”]
public SwingBuilder doOutside(Closure c) {
// …
Thread.start(c)
// …
}
[/sourcecode]
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?
[sourcecode language=”java”]
public static SwingBuilder build(Closure c) {
SwingBuilder builder = new SwingBuilder()
return builder.edt(c)
}
[/sourcecode]
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
[sourcecode language=”java”]
swing.actions() {
action(name:’myMethod’) {
doOutside {
model.prop = textField.text
// … other model property updates …
def ok = someLongRunningService(model)
doLater {
model.ok = ok
} } } }
[/sourcecode]
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.
Leave a Reply