I've had a couple requests from people wanting to use JRadioButtons in their generated UI (by default, Metawidget generates JComboBox components for selection lists).Devil in the Details
There are a few aesthetic issues with using JRadioButtons: should they be arranged horizontally or vertically; should they be horizontal up to a certain number of items, then switch to vertical; should they have a choice at the top for 'null'; should you be able to 'unselect all' after you've made an initial choice; should they only be used for not-nullable fields; etc. These are the kind of things keeping me from putting JRadioButtons in the core SwingWidgetBuilder.
But it's straightforward to add your own WidgetBuilder - where you can decide all the above little UI aesthetics according to your personal taste.
In a Bind
While you're at it, you probably want to add binding support. Again this depends on your personal preference, but let's say you want to use BeansBindingProcessor. It turns out BeansBinding isn't very good at binding to groups of JRadioButtons (a known problem), so one approach is to dummy up a little ButtonGroupPanel with its own getter and setter.
Example Code
Here's an example WidgetBuilder. You'll need to add it as part of a CompositeWidgetBuilder, as shown here in the Reference Documentation.
public class JRadioButtonWidgetBuilder
implements WidgetBuilder<JComponent, SwingMetawidget>, SwingValuePropertyProvider {
public JComponent buildWidget( String elementName, Map<String, String> attributes, SwingMetawidget metawidget ) {
String lookupAttribute = attributes.get( LOOKUP );
if ( lookupAttribute == null ) {
return null;
}
String[] lookups = ArrayUtils.fromString( lookupAttribute );
String[] lookupLabels = ArrayUtils.fromString( attributes.get( LOOKUP_LABELS ) );
ButtonGroupPanel panel = new ButtonGroupPanel();
panel.setLayout( new GridLayout( 1, lookups.length ) );
for ( int loop = 0; loop < lookups.length; loop++ ) {
JRadioButton radioButton = new JRadioButton();
if ( lookupLabels.length == 0 ) {
radioButton.setText( lookups[loop] );
} else {
radioButton.setText( lookupLabels[loop] );
}
radioButton.setActionCommand( lookups[loop] );
panel.add( radioButton );
}
return panel;
}
public String getValueProperty( Component component ) {
if ( component instanceof ButtonGroupPanel ) {
return "selected";
}
return null;
}
public static class ButtonGroupPanel
extends JPanel {
private ButtonGroup mButtonGroup = new ButtonGroup();
public String getSelected() {
ButtonModel buttonModel = mButtonGroup.getSelection();
if ( buttonModel == null ) {
return null;
}
return buttonModel.getActionCommand();
}
public void setSelected( String selected ) {
for ( Enumeration<AbstractButton> e = mButtonGroup.getElements(); e.hasMoreElements(); ) {
AbstractButton button = e.nextElement();
if ( !selected.equals( button.getActionCommand() ) ) {
continue;
}
String oldValue = getSelected();
mButtonGroup.setSelected( button.getModel(), true );
firePropertyChange( "selected", oldValue, selected );
break;
}
}
@Override
protected void addImpl( Component component, Object constraints, int index ) {
super.addImpl( component, constraints, index );
if ( component instanceof AbstractButton ) {
mButtonGroup.add( (AbstractButton) component );
}
}
}
}
implements WidgetBuilder<JComponent, SwingMetawidget>, SwingValuePropertyProvider {
public JComponent buildWidget( String elementName, Map<String, String> attributes, SwingMetawidget metawidget ) {
String lookupAttribute = attributes.get( LOOKUP );
if ( lookupAttribute == null ) {
return null;
}
String[] lookups = ArrayUtils.fromString( lookupAttribute );
String[] lookupLabels = ArrayUtils.fromString( attributes.get( LOOKUP_LABELS ) );
ButtonGroupPanel panel = new ButtonGroupPanel();
panel.setLayout( new GridLayout( 1, lookups.length ) );
for ( int loop = 0; loop < lookups.length; loop++ ) {
JRadioButton radioButton = new JRadioButton();
if ( lookupLabels.length == 0 ) {
radioButton.setText( lookups[loop] );
} else {
radioButton.setText( lookupLabels[loop] );
}
radioButton.setActionCommand( lookups[loop] );
panel.add( radioButton );
}
return panel;
}
public String getValueProperty( Component component ) {
if ( component instanceof ButtonGroupPanel ) {
return "selected";
}
return null;
}
public static class ButtonGroupPanel
extends JPanel {
private ButtonGroup mButtonGroup = new ButtonGroup();
public String getSelected() {
ButtonModel buttonModel = mButtonGroup.getSelection();
if ( buttonModel == null ) {
return null;
}
return buttonModel.getActionCommand();
}
public void setSelected( String selected ) {
for ( Enumeration<AbstractButton> e = mButtonGroup.getElements(); e.hasMoreElements(); ) {
AbstractButton button = e.nextElement();
if ( !selected.equals( button.getActionCommand() ) ) {
continue;
}
String oldValue = getSelected();
mButtonGroup.setSelected( button.getModel(), true );
firePropertyChange( "selected", oldValue, selected );
break;
}
}
@Override
protected void addImpl( Component component, Object constraints, int index ) {
super.addImpl( component, constraints, index );
if ( component instanceof AbstractButton ) {
mButtonGroup.add( (AbstractButton) component );
}
}
}
}
Feedback welcome!



Metawidget v1.0 is now available! Representing the culmination of two and a half years of careful incremental releases, we're proud to announce
Here's
It's a cool component, but for me it lacks an important feature: once you've chosen a color there's no way to unchoose it - to clear the box. This is annoying because typically getColor is a nullable field, so we need a way to null it. One approach would be to extend HtmlColorPicker (or its Renderer) and try to add the extra functionality - but that can be a bit scary.
Now you can click the red 'X' next to each ColorPicker, and it will use JavaScript to clear the box ready for POST back.