I really enjoyed the talk by Kyle Cordes at St. Louis Code Camp on his Swing Flying Boxes code. If you go to his page, you can run the demo directly from there via Web Start and see what it is.
Kyle asked me during the presentation what my estimate was for how long it would take to implement. I jokingly said “three beers”, but when pushed said 5000 lines. As Kyle’s talk showed, it was really only a small amount of code to actually implement the effect. But I stand by my initial “3 beer” estimate – I’d say one to write the basic frame and helper classes, one to implement the first version of the effect, and one to redo it once you figure out how to better leverage Swing to do most of it for you.
Anyhow, during the talk, I took his code and retrofit parts of it into the Swing Application Framework (aka JSR 296) which I’ve been playing with for my Java 7 presentation a bit. The SAF gives you some nice facilities to structure your application, standard ways to define and automatically inject resources, mange actions and background tasks, and handle session state and local storage. I thought some of my changes would be easy to show here.
Kyle’s main class looked like this (crunched a bit to save space):
[source:java]
public class OrderingMain {
private JFrame frame = new JFrame(“Demo”);
private JPanel panel = new JPanel(new BorderLayout());
private OrderingWidget orderingWidget = new OrderingWidget();
private JLabel topLabel = new JLabel(“Drag and drop the work orders.”);
public OrderingMain() {
topLabel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
panel.add(topLabel, BorderLayout.NORTH);
panel.add(orderingWidget.getComponent(), BorderLayout.CENTER);
}
private void createAndShowGUI() {
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
panel.setOpaque(true); // content panes must be opaque
frame.setContentPane(panel);
frame.pack();
frame.setVisible(true);
populateRandomSampleData();
}
private void populateRandomSampleData() {
// data set up – OMITTED
}
public static void main(String[] args) throws Exception {
UIManager.setLookAndFeel(“com.jgoodies.looks.plastic.PlasticXPLookAndFeel”);
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
new OrderingMain().createAndShowGUI();
}
});
}
}
[/source]
I made the following changes to his code to move this into SAF:
- The Swing Application Framework provides an Application class that you should subclass to take advantage of the framework’s functionality. They also will provide a set of subclasses of the Application class that take common Swing application setups into account. Right now, they only have SingleFrameApplication but they had some other proposed templates for MDI, multi-window, etc. So, I changed OrderingMain to extend SingleFrameApplication.
- I updated his main() method to instead call the standard Application launch method.
- Because we are extending the SingleFrameApplication, some of the standard JFrame definition was no longer needed as it’s done in the framework class. So, that removed some code.
- I implemented the application lifecycle method startup() to create and show the main JPanel.
- I leveraged the SAF’s resource injection by moving some resources like the main frame title, the look and feel, and the main label’s text into a resource file. Since I showed the panel through the framework, those resources were automatically injected into the Swing component for me.
In the end, my class looked like this:
[source:java]
public class OrderingMain extends SingleFrameApplication {
private OrderingWidget orderingWidget = new OrderingWidget();
private void populateRandomSampleData() {
// OMITTED
}
private JComponent createMainPanel() {
JLabel topLabel = new JLabel();
topLabel.setName(“mainLabel”);
JPanel panel = new JPanel(new BorderLayout());
topLabel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
panel.add(topLabel, BorderLayout.NORTH);
panel.add(orderingWidget.getComponent(), BorderLayout.CENTER);
return panel;
}
@Override protected void startup() {
show(createMainPanel());
populateRandomSampleData();
}
public static void main(String[] args) throws Exception {
launch(OrderingMain.class, args);
}
}
[/source]
And of course, I have a new file swingtalk/ordering/resources/OrderingMain.properties (the naming convention here allows the resources to be found automatically based on the full class name):
[source:java]
Application.title = Demo
Application.lookAndFeel = com.jgoodies.looks.plastic.PlasticXPLookAndFeel
mainLabel.text = Drag and drop the work orders.
[/source]
So far, so good. Kyle also had a class called UrgencyPalette that was managing a set of urgency colors for the various components. I killed off that class and just replaced it with a few resources in the WorkOrderPanel.properties resource file, since the WorkOrderPanel class was the only one using those colors. In doing so, I leveraged the built-in resource converters for handling color definitions in the resource file (converters also exist for keystrokes, images, fonts, etc). So, swingtalk/ordering/resources/WorkOrderPanel.properties is: [source:java]
urgencyColor.1=255, 0, 0
urgencyColor.2=#ffd9b3
urgencyColor.3=255, 255, 0
urgencyColor.4=0, 255, 0
[/source]
Here we see two different formats being used to specify colors. Unfortunately, these don’t handle the change in saturation that Kyle was getting in his custom class. I’m not sure if there is some way to take care of that in the resource converter or not. The code to actually get to these urgencies looks like: [source:java]
public Color getUrgencyColor(int urgency) {
ResourceMap resourceMap = ApplicationContext.getInstance().
getResourceMap(WorkOrderPanel.class);
return resourceMap.getColor(“urgencyColor.” + urgency);
}
[/source]
I suspect that it’s probably possible to make some changes in OrderingWidget to leverage the SAF Action annotation framework and possibly even the background Task framework, but I’m not enough of an expert on either Swing or the Swing Application Framework to take this any further.
Many thanks to Kyle for his excellent talk and also to this blog by Dean Iverson on the Swing Application Framework for helping me understand more about the framework.