Friday, April 16, 2010

Use controller action via listeners in Griffon view (Updated)

I have been asked recently a couple of times about how to use listeners in Griffon view for event handling such as mousePressed or focusLost. Apparently neither Swing builder nor Griffon documented this aspect in details, and it has been a bit confusing for some folks to figure it out since the usage is slightly different from regular actions.

In Griffon view you can supply a predefined action to a component such as JButton to invoke a controller action in a breeze. The following code snippet demonstrate how it is done in Griffon:


actions {
action(id: "quitAction",
name: messageSource.getMessage('menuItem.exit.caption'),
mnemonic: "x",
closure: controller.quit)
}

...

menuBar {
menu(messageSource.getMessage('menu.file.caption')) {
menuItem(quitAction)
}
}

...


From Andres Almiray:

The main difference between all examples is that the first assigns an instance of javax.swing.Action to the node's value. The builder knows that it can configure a button/menuItem/etc out of an Action so it does it.



When it comes to listener and event handling, one would expect a similar pattern applies; for example according to Swing builder documentation mousePressed event exists on JComponent level so one would hope the following code can bind a controller action to an mousePressed event.


actions {
action(id: "nodeSelectionAction",
closure: controller.selectNode)
}

list(id:'nodeList', model: model.serverListModel, eventPressed: nodeSelectionAction)


From Andres Almiray:
The second example does not work because it attempts to assign an instance of javax.swing.Action as the value of the menuPressed property, which has to be a MouseListener. That's why it breaks (although the error text may be misleading).



Unfortunately you will get No such property: mousePressed for class: javax.swing.JList error message. Instead the correct way to do this is by using the closure instead of an Action since mousePressed expects a listener. Groovy closure declared here will be converted to a listener automatically:


list(id:'nodeList', model: model.serverListModel, mousePressed: controller.selectNode)

From Andres Almiray:
The third example works because it assigns a closure as the value of the mousePressed property. Groovy will generate under the covers a proxy of a MouseListener that uses that closure as the implementation of said proxy's mousePressed method.


Hope this post will save some investigation time for someone who is new to Griffon and Swing builder.

Go Griffon!

2 comments:

Andres Almiray said...

Hi Nick, the main difference between all examples is that the first assigns an instance of javax.swing.Action to the node's value. The builder knows that it can configure a button/menuItem/etc out of an Action so it does it.

The second example does not work because it attempts to assign an instance of javax.swing.Action as the value of the menuPressed property, which has to be a MouseListener. That's why it breaks (although the error text may be misleading).

The third example works because it assigns a closure as the value of the mousePressed property. Groovy will generate under the covers a proxy of a MouseListener that uses that closure as the implementation of said proxy's mousePressed method.

Btw the last example can be written using node notation, like this:

list(model: model.serverListModel, mousePressed: controller.selectNode)

Cheers,
Andres

Nick Zhu said...

Hey Andres, thanks for the informative explanation. I will update the post with your comment, and yes the node notation is definitely better than manual assignment.


Cheers,

Nick