Code : Matrix Layout Manager
A Table Oriented Layout Manager
A long time ago, in a land far, far away... (from New Zealand) I was writing a GUI in Java. After about 7 levels of nesting I began to think that I was missing something critical - that there had to be an easier way. Don't get me wrong, I love Java's concept of resizeable flowing layouts (I cannot count the number of times I have had to deal with a Windows O/S dialog on my 1920x1200 screen utilizing about 10% of my available real estate with a little teeny tiny list box in the middle showing 9 of the 2700 files I am sifting through). But there just has to be a better way!
One of those annoying little non-resizeable windows:

So it's not that the basic idea behind layout-driven GUIs is flawed, it was just all the darned nesting - it resulted in code that had absolutely no visual resemblance to the end GUI, and resulted in building things inside out, instead of outside in. I wanted a layout that handled most or all of the requirements for a particular window or panel in one level, expressed in a way that made it easy to visualize the end result.
My first foray into writing a layout manager was largely a bust. It specified size and positioning relative to the sides of the window and/or other components. It actually worked quite well, but it's big problem was calculating preferred size - it couldn't; it was designed to position and size the components within a known panel area. It couldn't figure things out in reverse - things just got too complicated too fast.
Then, drawing on experience from the early days of writing HTML, before CSS, I realized the potential of tables to easily define how different elements of a form should be positioned with respect to one another. Research into existing solutions revealed a few candidates, but they were either commercially licensed (I couldn't use them for my hobbies), restricted from commercial redistribution (I couldn't use them for work), or were part of larger frameworks the entirety of which were too bulky for my needs.
But the biggest detractor was that they used constraint objects, but I needed a string-based constraint specification (for reasons not relevant to this discussion). One of these other layout managers was already named TableLayout, so I chose to call mine MatrixLayout (but the name TableLayout was my first choice).
The layout manager itself works for either AWT or Swing layouts; but to compile without the reference to the javax.swing package a number of the preformed layout helper methods at the end need to be commented out or redefined with AWT components.
How It Works
Essentially, the layout works by subdividing the panel into a grid of some number of rows and columns. The resulting cells form the containers in which components can be placed. Spanning, coupled with the ability to specify as many rows or columns desired, allows virtually any non-overlapping form design to be created. Each cell has insets to create space around the component placed in it, avoiding the need to have empty rows and columns to separate components.
Each layout is created with a set of row and column constraints that govern all cells in that row or column. In addition each component is added with constraints which apply to that specific cell. A fundamental design decision was made to have only a single component per cell - this avoids a lot of complications. Putting multiple components in a single cell is done by nesting another panel with it's own layout (perhaps a MatrixLayout), which turns the problems into a nesting/recursion consideration which is already nicely solved by Java GUI layout.
| Attribute | Function | Default | Values | Notes | ||||
|---|---|---|---|---|---|---|---|---|
| Size | Row Height | "Preferred" | "Preferred", "Pref", n!, n.nn% | |||||
| CellSpan | Default row spanning for cell contents | Defaults | 1-number of rows, or '*' for all remaining | |||||
| CellAlign | Default vertical alignment for cell contents | Defaults | "Top", "Center" or "Middle", "Bottom", "Fill" | |||||
| CellInsets | Default vertical insets for cell contents | Defaults | top,bottom (each can be Default, Def, or Dft) | #1 | ||||
| CellGroup | Default vertical group for cell contents | Defaults | A label to link cells for common heights |
| Attribute | Function | Default | Values | Notes | ||||
|---|---|---|---|---|---|---|---|---|
| Size | Column width | "Preferred" | "Preferred", "Pref", n!, n.nn% | |||||
| CellSpan | Default column spanning for cell contents | Defaults | 1-number of columns, or '*' for all remaining | |||||
| CellAlign | Default horizontal alignment for cell contents | Defaults | "Left", "Center" or "Middle", "Right", "Fill" | |||||
| CellInsets | Default horizontal insets for cell contents | Defaults | left,right (each can be Default, Def, or Dft) | #1 | ||||
| CellGroup | Default horizontal group for cell contents | Defaults | A label to link cells for common widths |
| Attribute | Function | Default | Values | Notes | ||||
|---|---|---|---|---|---|---|---|---|
| Row | Row number | "Current" | "Next", "Current", 1 - Number of rows | #2 | ||||
| Col | Column number | "Next" | "Next", "Current", 1 - Number of columns | #2 | ||||
| hSpan | Number of columns to span | "1" | 1 - Number of Columns, or '*' for all remaining | #3 | ||||
| vSpan | Number of rows to span | "1" | 1 - Number of Rows, or '*' for all remaining | #3 | ||||
| hAlign | Horizontal alignment of component in cell | "Left" | "Left", "Center" or "Middle", "Right", "Fill" | |||||
| vAlign | Vertical alignment of component in cell | "Middle" | "Top", "Center" or "Middle", "Bottom", "Fill" | |||||
| hGroup | Horizontal cell-group name | None | A label to link cells for common widths | |||||
| vGroup | Vertical cell-group name | None | A label to link cells for common heights | |||||
| Insets | Insets for cell | "3,3:3,3" | top,left:bottom,right (each can be "Default,Def, or Dft") | #4 |
| #1 Insets for the outer edges of cells which are on the outer edges of the container are ignored. |
| #2 Both row and column may not be "Current" since only 1 component is allowed in each cell. |
| #3 The number of cells to span cannot extend beyond to the last cell in the row or column. |
| #4 Insets for the outer edges of cells which are on the outer edges of the container are ignored. |
The layout manager is constructed with row and column specifications, which may be specified as either two strings for the simple cases or, to allow greater control, two arrays of strings, one element for each row/column. Then, as each component is added to the layout, specific constraints for the cell are supplied. To make life simpler, the constructor may also be given a set of default constraints applied initially to each cell. Perhaps the best way to get a feel for it is to see some actual code, here is the code that defines the main window for TimeKeeper .
This Window:

Is defined by this code:
rows=new String[] {
"size=Preferred",
"size=100% cellAlign=Top",
"size=Preferred",
"size=Preferred",
"size=Preferred",
};
cols=new String[] {
"size=Preferred cellAlign=Right",
"size=Preferred ",
"size=100% cellAlign=Right",
};
cp=new EPanel(new MatrixLayout(rows,cols));
cp.setBorder(BorderFactory.createEmptyBorder(10,10,10,10));
cp.setName("TimeKeeper");
// ADD PANEL CONTENTS
cp.add(formLabel(" Project :"),"row=Next col=1"); cp.add(projCode); cp.add(bb1,"hSpan=*");
cp.add(formLabel("Activity :"),"row=Next col=1"); cp.add(formScrollPane("ActivityScrollPane",activity),"hAlign=Fill vAlign=Fill hSpan=*");
cp.add(formLabel(" Hours :"),"row=Next col=1"); cp.add(MatrixLayout.singleRowBar(1,5,new Component[]{curHours,formLabel(" (n.nn or HH:MM)"),formLabel(" Total : "),totalHours,MatrixLayout.NULL,reminderEnabled}),"hAlign=Fill hSpan=*");
cp.add(bb2 ,"row=Next col=1 hSpan=*");
Breaking it down...
The rows are all set to get the (largest) preferred size of all the components within them, except row 2, which is defined to consume 100% of the space remaining after fixed sizes have been allocated - row 2 contains the text entry box. The columns get their preferred size, except column 3 which expands and contracts like row 2. Columns 1 and 3 are right aligned, which is why the field labels stick to their components. The default value for row is "Cur" and for col it's "Next", so we just need row and col specified for the first cell on each line and the other components are put into the next column on the same line.
Next we give the entire panel a border to create space around the outside edge, between the window border and the panel contents. This is necessary, because MatrixLayout ignores cell insets for it's outer edges, which makes nesting panels within panels much easier. In AWT where we don't have the convenience of borders, we would create 0 width "gap" rows and columns at the outside edges to create space. We also name the panel.
Next we populate the window's cells with our components. Note the use of "hSpan=*" to cause the component to span the remaining cells in the row. This is because we created 3 columns so that we could place the activity buttons over on the right. Notice the scroll pane which is spanned into cells R2,C2 and R2,C3 with horizontal and vertical alignments of Fill; it grows with it's row/column in both dimensions - and since, in turn, R2,C3 grows in both dimensions with the size of the window, the text box fills the available space as we resize the window. How about that, resizeable windows are so simple really!!
The button bar in row 4 is put into cell 1, so it is right aligned, but it spans the entire row, so it shoots across all the way to the right edge of the panel. Cool!
The only other thing to mention is the nested panel to create the Hours strip (rather a long line of code... oh well, I prefer that to breaking the line in this specific case). It uses a utility method to create a component bar, with a little trick to push the reminder checkbox to the right of the window; cell 5 is specified to be the expanding cell, but it is skipped over using the special NULL component putting the reminder checkbox in cell 6. Since cell 5 expands, it fills the extra space from sizing the window.
Get The Source
The package statement has been stripped from the source for convenience. I do not advocate unpackaged
classes - you should add a package according to your own requirements.
The source compiles to Java 5, but it should be able to compile against Java 2 or later.
Download MatrixLayout.java (Total downloads: 71)