Swing Actions With Groovy Closures
Groovy doesn't have anonymous or inner classes. The feeling is that if your really understood the features Groovy does have you wouldn't want to write them anyway-- so they are left out. Fair enough. But how do you write toward a Java API that assumes you do have inner classes? Wouldn't it be great if Swing would accept closures instead of those ugly anonymous classes? Well (surprise) we can substitute closures for inner class in a very straight forward and easy manner.
First, lets look at a Java example, then we will refactor it into Groovy. This example comes straight from sun.com.
"To write an Action Listener, follow the steps given below:
- Declare an event handler class and specify that the class either
implements an ActionListener interface or extends a class that
implements an ActionListener interface. For example:
public class MyClass implements ActionListener { - Register an instance of the event handler class as a listener on one
or more components. For example:
someComponent.addActionListener(instanceOfMyClass); - Include code that implements the methods in listener interface. For
example:
public void actionPerformed(ActionEvent e) { /*code here*/ }
End qoute.
Fun fun! You really just want to attach some component to some method or some code. But Java doesn't have function pointers or anonymous blocks of code. You can attach method and code blocks alright, but you have to attach a whole class while your at it, weather you want to or not. The smallest class is an infamous anonymous inner class, like this one:
someComponent.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("don't forget to import all this stuff!/n");
}});
If you're really gaga on Java you can get on a newsgroup and argue about how to indent it! Lets just think about what that is for a second. It's a class with nothing but one method, which in turn has nothing but a single statement.
There's a clean design under that thing somewhere. But our minds are wired to reject this sort of long winded statement.
Wouldn't it be great if Swing simply took closures like this:
b.addActionListener{ println 'hello world' };
That's your block of code -- just like with the anonymous class but without the garbage. Well (of course), it can. The first part is understanding what you're looking at.
b.addActionListener{ println 'hello world' };
is the exact same as:
b.addActionListener( { println 'hello world' } );
which has the same method signature as:
b.addActionListener( new groovy.lang.Closure() );
Now it looks like we are back to classes. The truth is that Groovy Closures are Java Objects too. Well sort of. But the idea is that the integration is as seamless as possible. Instead of the usual Swing method we are calling:
public void addActionListener(Closure)
Which takes our wonderful Closure and creates the expected Object for
Swing. You can simply subclass the Swing component you need to add this
new method which is just a proxy to addActionListener. You
only have to do this once for each type of component and once you do it
you can use it transparently. So here is the proxy class for JButton:
package net.glenp.groovy.gui;
import javax.swing.JButton
class GButton extends JButton {
/*
Construct a new 'anonymous' action
and register it with the button.
*/
public void addActionListener(Closure code)
{
this.addActionListener(new GAction(code));
}
}
Swing wants us to provide it Actions. Which are basically classes that wrap methods-- methods being the closest thing Java has to a code block and classes being the closest thing it has to something like a function pointer. At any rate, we will be using closures but we still have to give Swing its Actions so we use the Actions as a wrapper for our closures:
package net.glenp.groovy.gui;
import javax.swing.AbstractAction;
import java.awt.event.ActionEvent;
import groovy.lang.Closure;
class GAction extends AbstractAction {
Closure code;
GAction(Closure c) { code= c; }
public void actionPerformed(ActionEvent e) { code(); }
}
The closure is a field of our action. Events will cause Swing to call
actionPerformed where we can just execute the closure. And
now we can do this:
package net.glenp.groovy.gui;
import javax.swing.*
class TestUI
{
static void main(args)
{
JFrame f = new JFrame();
JButton b= new GButton();
b.setText("press me");
b.addActionListener{ println 'hello world' } // <-- cool!
f.getContentPane().add(b);
f.pack()
f.show()
f.setVisible(true);
}
}
The event handling code still uses the regular Swing Framework but is written in one line-- a readable one.