Thursday, November 03, 2005

Bag that! Eclipse way off base with Visual Editor.

The AWT GridBagLayout has been around for a long time. It takes some getting used to but once mastered is the mother of all layout managers, enabling just about any configuration of components
... and that's the problem...
all too often what you get is not quite what you want.

The quirks of the GridBag are well summarized by Totally GridBag a must-see animated blog by a developer who probably discovered a better way! (...more about that at the end.)

For anyone who's tried, a few helper classes usually are in order to prevent the types of problems that guy was having, as is well described in the 1000-page book "Master the GridBagLayout in just 99 days", which has 10 chapters on the idiosyncrasies of the 101 parameters of the GridBagConstraints class. (The book doesn't exist - I'm still writing it. Or rather, Eclipse is. They have probably added about another 5 chapters with their new Visual Editor.)

So with the GridBag being such a veteran of the Java world, you'd think that they might have actually have got somewhere with Visual GUI Designers by now. So after seeing the new features of the Visual Editor, keenly I download the Eclipse plug-in with high expectations. And at first it's not bad! I manage to develop a pretty neat looking GUI which allows me to add components here and there. My previous knowledge of GridBag helps me out, I'm sure - so I'm not sure how a newbie would fare, but all-in-all, Good stuff. Much better than writing all that tedious UI code, even with helper classes.

UI looks good, time to save & go home.

Next day - Katrina strikes. I open up the Visual Editor once more to add a few buttons and some new fields. So I start plopping a few components onto the canvas. All looks good. Go to save my work and "Oh no!" dialog pops up. "The class you are trying to save has compile errors - Proceed?"

What the heck kind of code has this thing been writing behind my back? So I go into to see my code in tatters. Not only has it hashed things up, it has done so royally. I find references to contraint objects that weren't created, and much, much worse, generated code inserted in the middle of other code, even inserted into comments. So I back out the changes, one undo at a time to see the UI in the visual view disappear right before my eyes. Now that's toally messed. Good job I didn't save the thing half-way through!

So a little more evaluation leads me to discover that:

  1. it's almost impossible to make changes to my UI in its current state (every time I try the code is inserted at the wrong place),
  2. the code it generated even before the compile errors is so messy and convaluted it's not even worth trying to maintain by hand and,
  3. the grid references in the GridBagLayout have gaps.

(I also found issues with how the vanilla GridLayout is handled too, but this blog is too long already.)

Time to find a better way.

Enter the TableLayout manager. It's been around for a long time, almost as long as the infamous GridBagLayout. Maybe everyone else knows this already and I'm slow on the uptake...

A little planning up front is always a good idea. For this layout, you want to have a good idea of how many rows and columns in your grid up front.
Add your row and column dimensions as arrays of doubles. For each dimension, specify absoutes as positive integers, percentages as a decimal between 0 and 1, or get it to respect PREFERRED sizes or FILL available space.

Components are added using a formatted string convention in the constraint argument:
"1,3" - add component to col 1, row 3
"1,3,4,6" - add component to col 1, row 3; spanning cells to col 4 row 6
"1,3,R,T" - add component to col 1, row 3; horizontal align right, valign top
"1,3,4,6,R,T" - all of the above

Here's a snippet:

TableLayout layout = new TableLayout();
layout.setColumn(new double[]{TableLayout.PREFERRED, TableLayout.PREFERRED});
layout.setRow(new double[]{TableLayout.FILL});
layout.setHGap(4);
modulePanel = new JPanel(layout);
modulePanel.add(new JLabel("Action:"), "0,0");
modulePanel.add(new JButton("Click Me"), "1,0");

or if you prefer:

...
double[][] colsRows =
{
{TableLayout.PREFERRED, TableLayout.PREFERRED},
{TableLayout.FILL}
};
TableLayout tableLayout = new TableLayout(colsRows);
modulePanel = new JPanel(tableLayout);
modulePanel.add(new JLabel("Action:"),
new TableLayoutConstraints (
0,0,0,0,TableLayoutConstants.RIGHT, TableLayoutConstants.TOP
)
);
modulePanel.add(new JButton("Click Me"), new TableLayoutConstraints(1,0));
...

This definition string thing feels a little strange. But in practice it's a nice, welcome shorthand.
However, if you want a more conventional, type-safe API then the TableLayoutConstraints object can be utilized instead.

This grid doesn't care if there are vacant cells. It doesn't even care if there is more than one resident in the same cell - it will render them all!
Add some padding to the layout with HGap and VGap, and either add a standard border or create a margin by adding blank rows and columns around the edge.
Did I say 'adding rows and columns'? I certainly did! this puppy allows you to create your component grid and then mess with it dynamically after the fact in code or at runtime! They even have an example of how to create animation in the grid by messing with the settings in real time.

Some observations:

The documentation thinks it's a good idea to add blank columns and rows as borders. I don't have an issue with having the option to do it, but Borders are also a viable option that shouldn't be overlooked.

The TableLayout guys present the layout manager as a one size fits all solution, and see it as an advantage to not have to use nested layout managers. I think that nesting layout managers is a wise technique for complex forms, whatever manager you use. The good news here is that TableLayout is often a better (or at least equivalent) alternative to the more simple layouts like FlowLayout, BoxLayout and GridLayout and even BorderLayout. So my gut feel is that nesting TableLayouts sensibly is good advice. (For example, when you find yourself adding a column for one field and you find that every thing else has to be adjusted to span it.)

This is almost an aside, or maybe a note-to-self: Managing layouts during development is almost always a pain when you want to add a field in the middle of a bunch of other fields. A technique to add the components from a dynamic array or collection would not be a waste of effort in conjunction with this layout manager, IMO.

Baffles me why this hasn't been adopted by Sun so everyone can benefit?

No comments: