LambdaLayout
Dale Anson, January, 2001
Introduction
LambdaLayout grew out of KappaLayout. Some users of KappaLayout expected
a somewhat different behaviour, LambdaLayout provides that behavior.
It is similar enough that usage is identical, that is, it is possible tosubstitute
LambdaLayout for KappaLayout in code without modification. This document
is intended to be the complete instruction manual for LambdaLayout,and does
not assume any familiarity with KappaLayout.
Motivation
Having worked with Java and user interfaces for a number of years, I have
found that interface construction is a much more difficult task than with
other languages. Like many programmers, I came from a Windows programming
background and was used to the GUI builders provided by Borland and Microsoft
as part of their IDE's. Constructing a user interface was fairly simple,
just drag and drop components onto a form and arrange them however you liked.
In the Windows world, layout managers were unknown, components were placed
on the screen at a given pixel location with a given width and height.
Java promised to run on a variety of platforms and as component sets on the
various platforms varied wildly in size and style, could not use the explicit
pixel layout with good effect. Additionally, Java wanted to ease internationalization,
which means that components must be able to dynamically resize based on the
contents of the words it contains.
Java 1.0 provided several layout managers to assist in placing components
on screen and meet these requirements. The original layout managers
are still with us, and even now at Java version 1.3, there hasn't been any
significant change in the way user interface layout is done. Programmers
use a combination of containers and layout managers in an attempt to create
the user interfaces that they need. GUI builders are available nowfor
Java, providing that same drag and drop capability of the Windows IDE's,but
they also rely on the standard layout managers. User interfacecode
in Java tends to be cluttered with a variety of extra panels and layoutmanagers
to do the layout correctly.
LambdaLayout should be considered a 'second generation' layout manager.
The Java language has matured, its uses have been clarified. However,
Sun has not stepped up with a decent layout manager, nor do the GUI builders
provide any better way than nesting panels and producing hard to follow and
hard to maintain code.
I tend to learn well by following examples, so I'll present one here.
I've used a progress dialog for a couple of years. It looks like this:

Here's the original layout code using multiple panels and layout managers
(credit goes to Claude Dugway for this):
JPanel panel = new JPanel();
panel.setLayout(new BorderLayout());
setSize(375, 191);
JPanel promptPanel = new JPanel();
promptPanel.setPreferredSize(new Dimension(70, 80));
promptPanel.setLayout(new GridLayout(4, 1));
promptPanel.add(new JLabel("Source:"));
if ( targetPath != null )
promptPanel.add(new JLabel("Target:"));
promptPanel.add(new JLabel("Status:"));
promptPanel.add(new JLabel("Time Left:"));
JPanel infoPanel = new JPanel();
infoPanel.setLayout(new GridLayout(4, 1));
infoPanel.add(source = new JLabel());
if ( targetPath != null )
infoPanel.add(target = new JLabel());
infoPanel.add(status = new JLabel());
infoPanel.add(timing = new JLabel());
JPanel messagePanel = new JPanel();
messagePanel.setLayout(new BorderLayout());
messagePanel.setBorder(new EmptyBorder(5, 10, 5, 10));
messagePanel.add("West", promptPanel);
messagePanel.add("Center", infoPanel);
panel.add("North", messagePanel);
progress = new JProgressBar();
progress.setValue(50);
progress.setPreferredSize(new Dimension(300, 20));
JPanel progressPanel = new JPanel();
progressPanel.setLayout(new BorderLayout());
progressPanel.setBorder( new EmptyBorder(3, 10, 3, 10));
progressPanel.add("Center", progress);
percent = new JLabel("");
percent.setPreferredSize(new Dimension(45, 23));
progressPanel.add("East", percent);
panel.add("Center", progressPanel);
button = new JButton("Cancel");
button.addActionListener(this);
JPanel buttonPanel = new JPanel();
buttonPanel.setBorder(new EmptyBorder(5, 0, 7, 0));
buttonPanel.add(button);
panel.add("South", buttonPanel);
getContentPane().add(panel);
show();
setSource(sourcePath);
if ( targetPath != null )
setTarget(targetPath);
setSize(size);
Notice this used 6 panels, 5 layout managers, and sets a number of explicit
preferred sizes to get the layout to look right. Here's the same panel,
but using LambdaLayout:
JPanel panel = new JPanel();
panel.setLayout(new LambdaLayout());
panel.setBorder(new javax.swing.border.EtchedBorder());
panel.add("0,0, , ,7,,2", new JLabel("Source: "));
if ( targetPath != null )
panel.add("0, 1,,,7,,2", new JLabel("Target: "));
panel.add("0,2,,,7,,2", new JLabel("Status: "));
panel.add("0,3,,,7,,2", new JLabel("Time Left: "));
panel.add("1,0,3,1,,w,2", source = new JLabel("") );
if ( targetPath != null )
panel.add("1,1,3,1,,w,2", target = new JLabel(""));
panel.add("1,2,3,1,,w,2", status = new JLabel(""));
panel.add("1,3,3,1,,w,2", timing = new JLabel(""));
progress = new JProgressBar();
progress.setStringPainted(true);
progress.setValue(0);
panel.add("0,4,4,1,,w,10", progress);
button = new JButton("Cancel");
button.addActionListener(this);
panel.add("1,5,2,1,,,3", button);
panel.add("0,6,4,1,,w", LambdaLayout.createHorizontalStrut(350));
setContentPane(panel);
pack();
center(parent, this);
show();
Now the progress dialog uses just 1 panel and 1 layout manager, and doesn't
use explicit sizes for any components. This is better than the previous
code in several ways:
- All components start at their actual preferred size, which means the
text they contain will always display correctly.
- If the dialog is resized, the careful layout of the first example fails.
This example still looks nice.
- Should the strings in this code need to be translated to another language,
it will still look good.
- It is much shorter, cleaner, and therefore easier to maintain.
LambdaLayout uses a grid system to layout components. The first cell
in the grid is at (0, 0) and cells are numbered right and down. Again, an
example:
panel.add("1,5,2,1,,,3", button);
The first 4 numbers in the string "1,5,2,1,,,3" tell LambdaLayout where to
put the button. This means put the button in column 1, row 5, make
it span 2 columns, and make it 1 row tall. The '3' means to add 3 pixels
of blank space around the button. The extra commas mean that some optional
layout parameters are omitted.
Here's another example:
panel.add("0,4,4,1,,w,10", progress);
This means put the progress bar in column 0, row 4, make it span 4 columns,
make it 1 row tall, allow it to stretch horizonally, and add 10 pixels of
blank space on all sides. The omitted layout parameter is alignment.
Since it was omitted, the default action is to center the progress bar across
the cells containing it.
The Constraint String
The constraint string controls the layout of a component. There are
7 parameters, all of which are optional. Whitespace and case within
the constraint string is ignored, so these are the same:
"1,5, 2, 1, , WH, 3"
"1,5,2,1,,wh,3"
The constraint string follows this format: "x, y, w, h, a, s, p". In
detail
- x - this is the starting column for the component. The firstcolumn
is on the left and is column number 0, with column numbers gettinglarger
going to the right. If omitted from the constraint string, the defaultvalue
of 0 will be used.
- y - this is the starting row for the component. The first row
is on the top and is row number 0, with row numbers getting larger goingdown.
If omitted from the constraint string, the default value of 0 willbe used.
- w - this is the number of columns that the component spans. If omitted
from the constraint string, the default value of 1 will be used.
- h - this is the number of rows that the component spans. If omitted
from the contraint string, the default value of 1 will be used.
- a - this is the alignment of the component within the cells containing
it. The alignment is expressed as a number from 0 through 8 and follows
this pattern:
+-----+
|8 1 2|
|7 0 3|
|6 5 4|
+-----+
So '0' is centered, '1' means center at the top of the cell, '4'
means align to the right and bottom of the cell, and so on. If omitted from
the constraint string, the default value of 0 will be used.
Alternatively, compass directions can be used:
+-------+
|NW N NE|
| W 0 E |
|SW S SE|
+-------+
N = North
NE = NorthEast
E = East
SE = SouthEast
S = South
SW = SouthWest
W = West
NW = NorthWest
Again, when used in the constraint string, case is ignored, so 'N' and 'n' are
the same.
- s - this allows the component to stretch in either width, height, or
both. Allowed values are 0, w, h, wh, and hw. '0' means don't
allow stretching in either direction and is the default if this parameter
is omitted from the constraint string. 'w' means allow stretching in width,
'h' means allow stretching in height, and 'wh' and 'hw' mean allow stretching
in both directions. This is case-insensitive, so Wh means the same as wHor
WH or wh or even Hw. There is no difference between wh and hw.
- p - this is the amount of padding to put around the component.
This padding will be added on all sides. This parameter is provided
for AWT-only layouts where the Swing border classes aren't available.
LambdaLayout uses a fairly complex algorithm to determine component layout.
Initially, each component is displayed at its preferred size, and if the's'
constraint is omitted, it will always be displayed at its preferred size.
The width of a column and the height of a row is calculated by using thepreferred
sizes of the components in that column or row and will initiallybe set to
the largest preferred size of the components in that column orrow.
For example, if column 1 contains 3 buttons with preferred widthsof 50, 75,
and 100, the column will initially be 100 pixels wide. Asthe container
holding the components is resized, any excess space is apportionedequally
across all columns and rows. For example, suppose we have apanel containing
4 buttons in a 2 x 2 arrangement and all 4 buttons havea preferred size of
50 pixels tall and 100 pixels wide, which makes the panel100 pixels tall
and 200 pixels wide. Now the user resizes the panelto 200 pixels tall
and 300 pixels wide. The columns are now each 150pixels wide and the
rows are now 100 pixels tall, and all the buttons arestill their original
sizes. Suppose, however, that one button was addedlike this:
panel.add("1,1,1,1,,w", button);
Then this button will initially be 50 x 100 (its preferred size), but when
the panel is resized to 200 x 300, the button will stretch in width to 150
pixels.
The Constraint Object
LambdaLayout also has a constraint object similar to GridBagLayout having
a GridBagConstraints. You get a Constraint object by calling LambdaLayout.createConstraint()
like this:
Panel p = new Panel();
LambdaLayout layout = new LambdaLayout();
p.setLayout(layout);
LambdaLayout.Constraint constraint = layout.createConstraint();
constraint.x = 1;
constraint.y = 2;
constraint.s = "wh";
p.add(new Button("Ok"), constraint);
The Constraint object can be reused, so you don't need to create one percomponent:
Panel p = new Panel();
LambdaLayout layout = new LambdaLayout();
p.setLayout(layout);
LambdaLayout.Constraint constraint = layout.createConstraint();
constraint.x = 1;
constraint.y = 2;
constraint.s = "wh";
p.add(new Button("Ok"), constraint);
constraint.x = 2;
p.add(new Button("Cancel", constraint);
For the alignment parameter, use the numbers as described above. To use the
alternate compass directions, use one of:
LambdaLayout.N
LambdaLayout.NE
LambdaLayout.E
LambdaLayout.SE
LambdaLayout.S
LambdaLayout.SW
LambdaLayout.W
LambdaLayout.NW
The center of the cell is always 0.
Here's an example of when using the Constraint object may be easier than using
the constraint string:
ResultSet rs = stmt.executeQuery(query);
Panel p = new Panel();
LambdaLayout layout = new LambdaLayout();
p.setLayout(layout);
LambdaLayout.Constraint constraint = layout.createConstraint();
int i = 0;
while(rs.next()) {
constraint.x = i++;
p.add(new Label(rs.getString(0)), constraint);
}
Extras
LambdaLayout provides some nice extras that can be very useful for certain
layouts. Columns and/or rows can be made the same width or height by using
makeColumnsSameWidth(int col1, int col2)
makeColumnsSameWidth(int[] cols)
makeRowsSameHeight(int row1, int row2)
makeRowsSameHeight(int[] rows)
Here's an example:
LambdaLayout ll = new LambdaLayout();
Panel p = new Panel();
p.setLayout(ll);
Label label = new Label("Pick something from this list:");
p.add("0,0,,1,7,w,2", label);
p.add("2,0,,1,7,w,2", new Label("Selected items:"));
p.add("0,1,,6,,wh,5", new List(10)); // stretch in both directions
p.add("2,1,,6,,wh,5", new List(10)); // "
Button right_btn = new Button("->");
p.add("1,3,,,5", right_btn); // move to bottom of cell
p.add("1,4,,,1", new Button("<-")); // move to top of cell
ll.makeColumnsSameWidth(0,2); // make list columns same width
Columns and/or rows can be set to a fixed width using these methods:
setColumnWidth(int col, int width)
setRowHeight(int row, int height)
Care should be used with these methods as components whose preferred
sizes are larger will be truncated. Continuing the above example, the
following code shows the right way to use these methods. Note that KappaLayout
is used for the button panel as Kappa's properties are better for this use
than LambdaLayout:
KappaLayout kl = new KappaLayout();
Panel button_panel = new Panel();
button_panel.setLayout(kl);
button_panel.add("0,1,,,,w", new Button("OK"));
button_panel.add("1,1,,,,w", new Button("Cancel"));
kl.makeColumnsSameWidth(0,1);
p.add("0,7,3,1", button_panel);
f.add(p);
// call pack() once to be able to get preferred sizes of some components,
// this is the best way to use setRowHeight and setColumnWidth, that is,
// use the preferred width or height of a component. Call layoutContainer(f) afterwards
// to cause the layout manager to layout the components again.
f.pack();
ll.setRowHeight(0, label.getPreferredSize().height); // make labels and lists maintain close contact
ll.setRowHeight(7, button_panel.getPreferredSize().height); // don't let excess height get added to button panel
ll.setColumnWidth(1, right_btn.getPreferredSize().width); // don't let excess width get added to button column
ll.layoutContainer(f);
LambdaLayout also provides struts, which can be useful for creating openspace
between components. Struts are invisible components, and are obtained
using these methods:
Component createHorizontalStrut(int width)
Component createHorizontalStrut(int width, boolean rigid)
Component createVerticalStrut(int height)
Component createVerticalStrut(int height, boolean rigid)
Component createStrut(int width, int height)
Component createStrut(int width, int height, boolean rigid)
These are static methods, and are used like this:
panel.add("0,6,4,1,,w", LambdaLayout.createHorizontalStrut(350));
This creates an invisible component 350 pixels wide, which essentially sets
the combined minimum width of columns 0 through 3 to 350 pixels.
Struts can be rigid or non-rigid. Non-rigid struts are useful for setting
a minimum amount of white space between components. Rigid struts are
useful for setting a specific distance between components. The rigid effect
can also be obtained by adding a non-rigid strut then setting the column
or row to the same width or height. For example, these are the same:
panel.add(LambdaLayout.createHorizontalStrut(10, true),
"2, 4");
and
panel.add(LambdaLayout.createHorizontalStrut(10), "2, 4");
ll.setColumnWidth(2, 10);
This means care must be used when adding struts as other components in the
row or column could be truncated.
Conclusion
LambdaLayout is an extremely versatile layout manager. It does not
do everything, for instance, see the examples above where KappaLayout was
used for button panel layout. What LambdaLayout provides is a very
easy and maintainable way to create complex layouts. Since I started
using these layouts in the fall of 2000, I have almost completely given up
BorderLayout, GridLayout, and GridBagLayout as LambdaLayout and KappaLayout
do a much better job with a much more readable coding style.