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:

lambdalayout1.gif

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:
  1. All components start at their actual preferred size, which means the text they contain will always display correctly.
  2. If the dialog is resized, the careful layout of the first example fails.  The LambdaLayout example will still look nice.
  3. Should the strings in this code need to be translated to another language, it will still look good.
  4. 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 (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"

    "1,5,2,1,,wh,3"
The constraint string follows this format: "x, y, w, h, a, s, p".  In detail
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 the preferred sizes of the components in that column or row and will initially be set to the largest preferred size of the components in that column orrow.  For example, if column 1 contains 3 buttons with preferred widths of 50, 75, and 100, the column will initially be 100 pixels wide.  As the container holding the components is resized, any excess space is apportioned equally across all columns and rows.  For example, suppose we have a panel containing 4 buttons in a 2 x 2 arrangement and all 4 buttons have a preferred size of 50 pixels tall and 100 pixels wide, which makes the panel 100 pixels tall and 200 pixels wide.  Now the user resizes the panel to 200 pixels tall and 300 pixels wide.  The columns are now each 150 pixels wide and the rows are now 100 pixels tall, and all the buttons are still their original sizes.  Suppose, however, that one button was added like 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 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 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 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.

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.

SourceForge Logo