G$D/Groovy: Updating a SwingBuilder UI

by Marc Hedlund

(For an overview of the G$D/Groovy series, see <http://www.oreillynet.com/pub/wlg/5789>.)

Updating a running SwingBuilder UI and the secret widget() method



Once you have a SwingBuilder UI running, how does it ever change? Does it just sit there? Can it be changed programmatically? Good questions. SwingBuilder makes it easy to create Swing interfaces very quickly, but in the process you lose access to the component references you'd normally use to update your running UI.



Groovy adds two main ways to update a Swing UI (above the normal Java methods, which are of course still available to you if you don't use SwingBuilder): action closures and widget references. We'll talk about widget references here, and action closures in another post.



Take a look at this code from the basic SwingBuilder lesson:




import groovy.swing.SwingBuilder;
import java.awt.FlowLayout;

swing = new SwingBuilder();
gui = swing.frame(title:'Test 2', size:[400,200]) {
panel(layout:new FlowLayout()) {
panel(layout:new FlowLayout()) {
for (name in ["Tom", "Dick", "Harry", "Bill"]) {
checkBox(text:name);
}
}

panel(layout:new FlowLayout()) {
comboBox(items:["Red", "Green", "Blue", "Orange"],
selectedIndex:2);
}
}
}

gui.show();


Let's say you wanted to add an item to the JComboBox sometime after the gui.show() call. How would you do that? Since SwingBuilder created the JComboBox for you, you don't have a reference to it, and can't call its addItem() method.



SwingBuilder has a secret method called widget() to help in this situation. I call it a "secret" method because it isn't documented anywhere I could find -- since it was added as a patch to fix two Groovy bugs (GROOVY-333 and GROOVY-501), only the bugnotes for those bugs provide any guidance. The purpose of the widget() method is to allow you to add a component to SwingBuilder that was created outside of the current SwingBuilder call. One reason to do this would be to add a custom component to a UI, one that SwingBuilder doesn't support itself. In this case, though, the widget() method will let us keep a reference to the JComboBox, so we can add an item to it later. Here's the updated code:




import groovy.swing.SwingBuilder;
import java.awt.FlowLayout;

swing = new SwingBuilder();

// first, create the combo box
comboBox = swing.comboBox(items:["Red", "Green", "Blue", "Orange"],
selectedIndex:2);

// second, create the rest of the gui
gui = swing.frame(title:'Test 2', size:[400,200]) {
panel(layout:new FlowLayout()) {
panel(layout:new FlowLayout()) {
for (name in ["Tom", "Dick", "Harry", "Bill"]) {
checkBox(text:name);
}
}

panel(layout:new FlowLayout()) {
// add in the combo box we made earlier
widget(comboBox);
}
}
}

gui.show();

// ... more activity here ...

// finally, update the combo box
comboBox.addItem("Yellow");


As you can see, we use SwingBuilder to make two components -- first the comboBox and then the main frame. Inside the frame call, we use the widget() method to insert the combo box where it belongs. Later on, we can call any method on the combo box we need by using the reference we kept earlier. Here's the new UI, showing the added list item:



image



This is the approach that works, but there are plenty of approaches that don't work! Assigning to a variable inside the SwingBuilder block? Nope. Putting a bare reference to an earlier-created component inside a SwingBuilder call? Nope. Using a closure to generate the component? Nope. You can beat your head against this problem for a long time (trust me!). Fortunately, the widget() call allows you to keep references to components or insert existing components into a SwingBuilder construct.