Java Tip 129: SGLayout—a layout manager for the rest of us

SGLayout, a GridLayout rewrite, improves your layout control

A good layout manager proves essential for creating a good graphical user interface (GUI), but many beginner developers find the standard Java layout managers difficult to master. Of these, BorderLayout and FlowLayout generally prove useful, but developers often start running into difficulties when coding more complex layouts. GridBagLayout, in particular, is very powerful, but many developers find it difficult and nonintuitive. Personally, I follow the "least surprise" principle when I look at layout managers: If you are surprised at a layout manager's behavior, it probably was designed poorly. On this basis, GridBagLayout gets particularly poor marks. With that in mind, a better way to get similar results must exist.

In "Java Tip 121: Flex Your Grid Layout" (JavaWorld, December 2001), Bogdan Dorohonceanu describes a GridLayout subclass that avoids the requirement that a grid's elements possess the same dimensions. In this Java Tip, I describe a layout manager, SGLayout, with similar flexibility and direct control over margins, gaps between rows and columns, and fine control over how to locate a component within the grids.

In one example, a simple password panel, Dorohonceanu incorporates a series of labeled text fields into a grid and shows how to improve the panel's appearance by decreasing the label column's width relative to that of the text field column.

Thinking I could improve upon GridLayout, I wrote SGLayout as a complete replacement. In so doing, I incorporated much of GridBagLayout's alignment power.

Note: You can download this article's complete source code from Resources.

SGLayout and its friends

I commence discussion with a rather silly GUI frame, seen in Figure 1, that illustrates SGLayout's possibilities. (I implemented this demo as SGDemo.java.) A JPanel instance, to which an SGLayout manager has been set, replaces the JFrame's contentPane:

  JPanel contentPanel = new JPanel();
  SGLayout contentLayout = new SGLayout(3, 2, , SGLayout.FILL,
     SGLayout.FILL, 15, 5);
  contentPanel.setLayout(contentLayout);
Figure 1. Grid cells and alignments using SGLayout

The layout manager in this case defines management of three rows and two columns. The component added to each grid position will fill the position in both the X- and Y-directions, and there will be a 15-pixel gap between the columns and 5-pixel gaps between the rows. It's complicated but straightforward. (Note that the conventional schizophrenia applies: rows before columns, but X-axis before Y-axis.)

A series of statements then destroys that arrangement's simple order:

  layout.setRowScale(1, 2.0);
  layout.setColumnScale(0, 0.65);

These statements make row 1 wider (by a factor of 2.0 relative to rows 0 and 2, which have a 1.0 default scale-value) and column 0 narrower than column 1 (by a factor of 0.65). The scale values are all relative and are maintained on resizing of the container:

    layout.setColumnAlignment(0, SGLayout.RIGHT, SGLayout.TOP);

This statement changes the alignment of all grid cells in the 0th column. Next, you change the individual cells' alignment (on a row, column basis):

  layout.setAlignment(0, 1, SGLayout.LEFT, SGLayout.CENTER);
  layout.setAlignment(1, 0, SGLayout.RIGHT, SGLayout.FILL);
  layout.setAlignment(2, 0, SGLayout.FILL, SGLayout.BOTTOM);
  layout.setAlignment(2, 1, SGLayout.FILL, SGLayout.FILL);

Make sure that none of the statements cancel an earlier statement (you should work on a row or column basis first, then finish on the individual cells).

Finally, several labels, panels, and a text field are added to the contentPanel. The texts in Figure 1 describe the alignments for most of the grid cells. The button in cell 1, 1 and the label in cell 2, 1 are added to panels that are in turn added to contentPanel. Note that the layout manager for each of these panels is a PointLayout instance (see below).

This example seems silly, considering that no sensible GUI would have such a mixture of alignments. The example does, however, demonstrate how SGLayout makes implementing complex layouts possible.

SGLayout's friends

SGLayout serves as the base class for a family of layout managers. Derived classes include:

  • SRLayout (short for ScaledRowLayout): Manages a 1 x n grid (1 row, n columns)
  • SCLayout (short for ScaledColumnLayout): Manages an n x 1 grid
  • PointLayout: Manages a single cell (a 1 x 1 grid)

I provide these classes simply for convenience; you can use a 1 x 1 grid in SGLayout, for example, but PointLayout clarifies your intentions.

The alignment options

Before I demonstrate a more sensible example, let's first examine the alignment options in detail:

  • Horizontal alignment: LEFT, RIGHT, CENTER, FILL
  • Vertical alignment: TOP, BOTTOM, CENTER, FILL

Given that you must specify both horizontal and vertical alignments for each cell, row, column, or layout, you can choose from 16 possible alignment combinations (not all of them particularly useful). Figure 1 illustrates some of these combinations. Typical examples, as used above, are:

  layout.setAlignment(0, 1, SGLayout.LEFT, SGLayout.CENTER);
  layout.setAlignment(1, 0, SGLayout.RIGHT, SGLayout.FILL);

Margins and gaps

Set the JPanel's inset margins on a subclass basis by overriding the getInsets() method; you cannot modify them using setInsets(). The getInsets() method, as used by a layout manager such as GridLayout, defines a no-go zone for the layout. On the other hand, SGLayout and its friends ignore the insets and permit you to set margins directly with setMargins(). (Zero is the default for topMargin, leftMargin, bottomMargin, and rightMargin; all values are in numbers of pixels.) So, for Figure 1:

  SGLayout contentLayout = new SGLayout(3, 2, , SGLayout.FILL,
                                  SGLayout.FILL, 15, 5);
  contentLayout.setMargins(5, 10, 15, 20);

The margins in this case skew the grid's positioning within the frame. In general, the combination of margins, gaps, and alignments provides good control over placement. Figure 1's position illustrates that point: PointLayout positions an JButton instance in a panel:

  JPanel buttonPanel = new JPanel();
  PointLayout buttonLayout =
      new PointLayout(PointLayout.RIGHT, PointLayout.CENTER);
  buttonLayout.setMargins(25, 10, 5, 40);  // Try commenting this line out

Note that the precise dimensions assigned to buttonPanel account for the margins and preferred size of the child component within it. If margins remain unspecified, buttonPanel's height is exactly that of its child button, and the result simply looks wrong.

A better dialog

Figure 2 illustrates how SGDialog and friends create a dialog with three labeled fields and a button panel containing three buttons. I used three different layout managers in three JPanel instances. First, a top-level contentPanel is created with a SCLayout (2), on which position 1 is given a scale value of 1.5. To this panel are then added: displayPanel with SGLayout (3 x 2), with column 0 given a scale value of 0.4, and buttonPanel with SRLayout (3).

Figure 2. A dialog created with SGLayout and friends

The addContent() method incorporates the action:

  private void addContent() {
    JTextField nameField = new JTextField(12);    // Try changing these widths
    JTextField addressField = new JTextField(20);
    JTextField zipField = new JTextField(2);
    JButton helpButton = new JButton("Help");
    JButton exitButton = new JButton("Exit");
    JButton saveButton = new JButton("Save");
    JPanel contentPanel = new JPanel();
    SCLayout contentLayout = new SCLayout(2, SCLayout.FILL, SCLayout.FILL, 3);
    contentLayout.setScale(0, 1.5);
    contentPanel.setLayout(contentLayout);
    JPanel displayPanel = new JPanel();
    SGLayout displayLayout =
      new SGLayout(3, 2, SGLayout.LEFT, SGLayout.CENTER, 5, 0);
    displayLayout.setColumnScale(0, 0.4);
    displayLayout.setColumnAlignment(0, SGLayout.RIGHT, SGLayout.CENTER);
    displayPanel.setLayout(displayLayout);
    displayPanel.add(new JLabel("Name"));
    displayPanel.add(nameField);
    displayPanel.add(new JLabel("Address"));
    displayPanel.add(addressField);
    displayPanel.add(new JLabel("Zip"));
    displayPanel.add(zipField);
    JPanel buttonPanel = new JPanel();
    buttonPanel.setBorder(BorderFactory.createEtchedBorder());
    SRLayout buttonLayout = new SRLayout(3, SRLayout.FILL, SRLayout.CENTER, 10);
    buttonLayout.setMargins(5, 15, 5, 15);
    buttonPanel.setLayout(buttonLayout);
    buttonPanel.add(helpButton);
    buttonPanel.add(exitButton);
    buttonPanel.add(saveButton);
    contentPanel.add(displayPanel);
    contentPanel.add(buttonPanel);
    setContentPane(contentPanel);
    pack();
  }

Note that column 0's alignment in displayPanel is set to (RIGHT, CENTER) to align the labels correctly—5 pixels before the text fields. Proper JTextField instance management may require special attention. Unless you use FILL for the X-alignment, as in column 1 for displayPanel, you must set a text field's width, either as the number of W characters or as specific text. Otherwise the field will not have a useful preferred size (see below). If you use FILL for the X-alignment, the text field will always occupy the full width of the text position.

How does SGLayout differ from GridLayout?

SGLayout, like GridLayout, implements the LayoutManager interface. Any layout manager sets each child components' bounds, using the setBounds() method. The two layout managers differ, however, in the logic that calculates the parameters for setBounds() (X-origin, Y-origin, width, height).

GridLayout proves rather simple-minded: it looks through the child components for the smallest minimum width and height, and for the smallest preferred width and height, which it uses to shape the grid, with all cells of the same size. Then it steps through the list of children and allocates to each a position and size using setBounds().

SGLayout, in contrast, allocates to each grid cell an amount of space, based on the parent container's and any scalings' sizes, and then positions a child component within that space. If the child proves too large for the space, based on its preferred size, SGLayout crops the child to fit. Otherwise, SGLayout fits the child into the grid in accordance with the alignments. If, for example, enough space exists and the alignments are (CENTER, CENTER), the child component shifts to the right and down by an amount equal to half the difference between the available width (or height) and the preferred width (or height). The child then displays in its preferred size. If the alignment is (FILL, FILL), the available space is given to the child.

SGLayout and friends on the job

The measure of a layout manager stems from how well it works in practice—specifically, from how easily you can control it while coding and how well it responds to resizing. Considering that, let's examine some practical guidelines.

Alignments

SCLayout proves particularly useful for a column of subpanels. Experience suggests that the alignment should be FILL, CENTER, giving the subpanels their natural (preferred) height; if the Y-alignment is FILL, the subpanels expand vertically, often with an ugly result. The problem can worsen on resizing.

Never give a FILL Y-alignment to a JTextField instance. The result can be grotesque, particularly on resizing.

SRLayout often proves useful for laying out JButton instances, as Figure 2 shows. The result seems best when the alignment is FILL, CENTER with a 5-pixel gap and (perhaps) margins of 3, 20, 3, 20. Alternatively, FlowLayout(FlowLayout.CENTER) works well with a row of buttons.

Gaps, margins, and borders

SGLayout has great power, but can produce some wild results, as Figure 1 shows. In general, avoid complex grid layouts and incorporate other containers, such as JTabbedPanes, within the grid to provide sensible access to child components.

Gaps between subpanels should be small (2 or 3 pixels at most) and, in most cases, margins should be left at the default (0, 0, 0, 0). Borders always enhance the final appearance of subpanels.

Great layouts

SGLayout and friends are a valuable addition to the Java GUI armory. Over the past year, I have used them to create all the interfaces I've needed. I occasionally use FlowLayout for subpanels, and BorderLayout at the top level, but no other layout managers.

John Redmond, PhD works as a Java consultant with Pegasus Technology, a development and support company based in Sydney, Australia. He has degrees in pharmacy and organic chemistry and, in a previous life, was a professorial fellow at Macquarie University, Sydney, and Australian National University, Canberra. He has written more than 80 publications in the areas of carbohydrate analysis and biotechnology. John has been writing code for more than twenty years and has written laboratory data-acquisition systems and several Forth implementations using assembler language for 8080, 6809, and 68000. He has also worked with C and C++, but, after more than two years of Java development on distributed systems, he feels that he has reached the programmer's nirvana.
1 2 Page
Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more