Monday, October 12, 2009

Griffon Tip: Silly SwingBuilder Tricks

Here's two quick tips for working with radio buttons in SwingBuilder.  Radio buttons are created using SwingBuilder's radioButton() method.  To ensure only one is selected at a time, you must also associate them with a button group.   Ideally SwingBuilder would allow you to nest your radioButton()s inside a buttonGroup(), e.g.
buttonGroup() {
    // doesn't work
    radioButton(text: 'Option 1')
    radioButton(text: 'Option 2')

Unfortunately this doesn't work as buttonGroup() does not support nesting.  Marc Hedlund stumbled across this same issue and offers up one solution.  I like his solution but it means creating a separate variable to hold the button group.  I've found a bit nicer way to do it using Groovy's with keyword:
buttonGroup().with {
    add radioButton(text: 'Option 1')
    add radioButton(text: 'Option 2')

The advantage of this approach is that it conveys our intention better and we don't have to instance extra variables.

EDIT: the 'with' also passes in the created object so you could take it a step further and replace the add as well:
buttonGroup().with { group ->
    radioButton(text: 'Option 1', buttonGroup: group)
    radioButton(text: 'Option 2', buttonGroup: group)

As a bonus, here's how you can use mutual binding to keep a boolean in your model in sync with the radio buttons.  Let's say your model looks something like so:
class DemoModel {
    @Bindable boolean option1 = true

Using mutual binding, you can keep the model synced up without any explicit management code:
buttonGroup().with {
    add radioButton(text: 'Option 1', selected: bind(source: model, sourceProperty: 'option1', mutual:true))
    add radioButton(text: 'Option 2', selected: bind { !model.option1 })

Obviously this will only work for cases where you have an either-or choice.


M.J. Gribbin said...

Very cool tip, I love the readability of it.

Fabian said...

using the mutual property within bind() always gets me an Missing PropertyException.

"No such property: mutual for class: org.codehaus.groovy.binding.PropertyBinding$ProperyFullBinding"

calling it like:

textField (text:
bind(source: model, sourceProperty: 'itagID',

Any idea why this is happening?


Josh Reed said...

Hi Fabian,

Sorry for the late reply, I've been out of town.

The only thing I can think of with your mutual binding issues is are you using version 0.2 of Griffon? I can't remember when mutual binding was officially added but this example has been tested on 0.2.


Fabian said...

Hey Josh,
thank you for blogging, your stuff helps me a lot and is really appreciated! I thought I had installed the latest Version but in fact I did not. So updating to 0.2 solved this and other problems.