KappaLayout
LambdaLayout
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 to substitute 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. The LambdaLayout example will still look 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.
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 (the w stands for width), 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"The constraint string follows this format: "x, y, w, h, a, s, p". In detail
"1,5,2,1,,wh,3"
- x - this is the starting column for the component. The first scolumn is on the left and is column number 0, with column numbers getting larger going to the right. If omitted from the constraint string, the default value 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 going down. If omitted from the constraint string, the default value of 0 will be 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 clockwise 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 wH or 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, but it is also nice for use in Swing since you don't have to explicitly create empty borders for padding.
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 per component:
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 usingmakeColumnsSameWidth(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 widthColumns 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 open space 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.