Skip to main content

Getting to know GroupLayout, part 1

Posted by tpavek on February 22, 2006 at 8:58 AM PST

Note: Since Java 6 release, an updated version of this text, including the code samples, is available as part of the Swing trail of Java Tutorial:
How to Use GroupLayout, GroupLayout example


GroupLayout is a new layout manager that was developed as a
Swing Labs project in conjunction
with Matisse, the new GUI builder in NetBeans 5.0. There is a chance that
GroupLayout will become a part of JDK in the future. Though the layout manager was
originally designed to suit the GUI builder needs, it is also quite handy for manual coding. This
article will help you get up to speed with how GroupLayout works and shows you
how you can start building GUIs using GroupLayout, whether you choose to
use Matisse or write your own code.

In this article, I'm going to cover some of the theory behind GroupLayout. If you prefer to start with
examples, you can skip this part and wait for the next post which will bring
a complete example with detailed explanation.

Design principle: independent dimensions

The first thing you need to know about GroupLayout is that it works with

horizontal and vertical layout separately. This is not that uncommon, but
unlike other layout managers GroupLayout does not use a single constraints
object or method to completely specify a component's layout; the layout is defined for
each dimension independently. This might seem a bit unusual at first
sight, but
it actually makes things easier because the definition is simpler. When defining the horizontal
layout, you don't need to worry about the vertical
dimension, and vice versa. The layout along the horizontal axis is quite independent
of the layout along the vertical axis. By focusing just on one dimension at a
time you only have to solve half the problem,
the other dimension can be solved later. The downside of this approach is that each component needs to be defined twice in the layout.
You'll soon find out if you forgot to do this, because GroupLayout will generate
an exception ;-)

This dimension independence is quite a powerful concept, similar to SpringLayout,
because it provides flexibility other
layouts can't offer. We'll get back to this topic later; but first let's see what makes GroupLayout different from SpringLayout
and other layout managers.

Layout organization: hierarchical groups

GroupLayout uses two types of arrangements -- sequential and parallel, combined with
hierarchical composition. These
principles are quite basic and well known from Swing.

(1) With sequential arrangement, the components are simply placed
one after another. Just like BoxLayout or FlowLayout would do along one
axis. The position of each component is defined as being relative to the
preceding component. This is important for platform independent layout.

(2) The second way places the components in parallel, on top of each other in the same space,
and aligned
along a common reference point. For example, the components can be right-aligned along
the horizontal
axis, or baseline-aligned along the vertical axis, etc.

Usually, components placed in parallel in one dimension are in a sequence in
the other, so they don't overlap. See the examples below.

What makes these two principles powerful is that they can be combined (nested)
hierarchically. For this purpose GroupLayout defines layout
groups
.
A group is either sequential or parallel and may contain components, gaps and other groups.
The size of a sequential group is the sum of the sizes of the contained elements,
and the size of a parallel group corresponds to the size of the largest element.
Defining a
layout means defining how the components should be grouped by combining the
sequential and parallel arrangements. This resembles nested panels with
BoxLayout, but the groups are quite lightweight compared to panels. There is also a difference in the independent dimensions as
described above. Panels do nesting in both dimensions at once, while groups can
be nested as needed, for each dimension separately.

That's enough theory for now, let's take a look at how it works in practice
with a simple example.

Example

Let's start with something really simple, just three components in a row:

We would like to express this layout using groups. Starting with the horizontal
axis it's easy to see there is a sequential
group
of 3 components arranged from left to right. Along the vertical axis there is a parallel group
of the same 3 components (at the same coordinate); let's say they are aligned
along a baseline. See this picture:

In pseudo code, the layout specification might look like this (the real code is further below):

horizontal layout = sequential group { c1, c2, c3 }
vertical layout = parallel group (BASELINE) { c1, c2, c3 }

Note this illustrates a principle I mentioned earlier; components grouped sequentially in one
dimension usually form a parallel group in the orthogonal dimension.

Now let's add one more component (C4):

Along the horizontal axis the new component forms a parallel group with C3 (because it occupies the same horizontal space as C3),
let's say we want the components left aligned.
Along the vertical axis C4 forms a sequential group with the
original parallel group of the three components.

In pseudo code, the layout specification now looks like this:

horizontal layout = sequential group { c1, c2, parallel group (LEFT) { c3, c4 } }
vertical layout = sequential group { parallel group (BASELINE) { c1, c2, c3 }, c4 }

Now that you understand the principle of groups, you know the most important
thing about designing layouts with GroupLayout! There are just a few more details
to explain: how to add gaps, how to define
resize behavior, how to write real code, etc.

Gaps

A gap can be thought of as an invisible component of certain size. Gaps of
arbitrary size can be added to groups just like components or other
groups. Using gaps you can precisely control the distance
between components or from the container border.

GroupLayout also defines a symbolic default gap that corresponds to a
preferred
distance
between neighboring components (or between a component and container border).
The size of such a gap is not defined by an explicit number, but computed dynamically based on the look and feel the
application is using (the LayoutStyle class is used for this). There
are two advantages to using preferred gaps: you don't have to specify the
pixel sizes of the gaps, and they automatically adjust to the environment the UI runs in,
reflecting the actual platform guidelines.

GroupLayout distinguishes between (a) the preferred gap between two components and
(b) the preferred gap between a component and the
container border. There are corresponding methods in the GroupLayout API for adding these
gaps (addPreferredGap and addContainerGap). There are
three types of component gaps: related, unrelated and indented.
The LayoutStyle
class defines corresponding constants (to be used as the first parameter of the addPreferredGap
method): RELATED, UNRELATED and INDENT. The difference between
related and unrelated gap is just in size (the distance between unrelated components is a bit
bigger). Indented represents a preferred horizontal distance of two
components where one of them is positioned underneath the second with an indent.

To make things easier, GroupLayout can insert gaps
automatically
. If you don't add your
own gaps explicitly, it adds the related preferred gaps for you. This is not the
default behavior, you have to turn this feature on by invoking setAutocreateGaps(true) and setAutocreateContainerGaps(true) on the layout.
Then you'll get correct spacing almost for free!

How to write code

Now, let's take a look at the actual code to create the layout described
above.

Let's assume we have a container named panel and four
components (c1, c2, c3, c4)
which are already set up. First, we create a new GroupLayout object and
associate it with the panel:

    GroupLayout layout = new GroupLayout(panel);
    panel.setLayout(layout);

We specify automatic gap insertion:

    layout.setAutocreateGaps(true);
    layout.setAutocreateContainerGaps(true);

Finally, we define groups and add the components. We establish a root group
for each dimension using
setHorizontalGroup and setVerticalGroup methods.
Groups are created via createSequentialGroup and createParallelGroup
methods. Components are added to groups by using a variant of the add method.

    layout.setHorizontalGroup(
        layout.createSequentialGroup()
            .add(c1)
            .add(c2)
            .add(layout.createParallelGroup(GroupLayout.LEADING)
                .add(c3)
                .add(c4))
    );
    layout.setVerticalGroup(
        layout.createSequentialGroup()
            .add(layout.createParallelGroup(GroupLayout.BASELINE)
                .add(c1)
                .add(c2)
                .add(c3))
            .add(c4)
    );

Note that default alignment must be specified for parallel groups. It can be
one of the following constants defined in the GroupLayout class: LEADING,
TRAILING and CENTER. These constants are used for both dimensions; in the
horizontal dimension LEADING means "left", while in the vertical dimension it
means "top". Similarly TRAILING maps to "right" or "bottom". The BASELINE
alignment is valid only in the vertical dimension.

Some notes about the code:

  • Components are not added to the container directly, they
    are added to groups.
    GroupLayout adds the components to the container automatically
    for you.

  • Note the chained calls of the add methods used to fill the groups.
    The add method always returns the group on which it is called.
    Thanks to this you don't need to use local variables to hold the groups.

  • It is a good idea to indent the code so it is easy to see the hierarchical structure of the groups. Give
    each component a new line, add one
    level of indent for each new group in the hierarchy. A decent source editor will
    help you with pairing the parenthesis to close the createXXXGroup methods.
    By following these simple
    rules it is easier to add a new component, or remove an existing one.

Size of components and resizability

The size of each component in a GroupLayout is constrained by three values;
minimum size, preferred size and maximum size. These sizes control how the
component resizes within the layout. The GroupLayout.add(...) method allows the size
constraints to be specified. There is no limit on the number of resizable
components in a layout.

If not specified explicitly, the layout asks the component for its default
sizes (by using the component's getMinimumSize(), getPreferredSize()
and getMaximumSize() methods). Thus you don't need to
specify anything for most of the components, e.g. to make JTextField
resizable or JButton fixed, because the components have the desired resizing
behavior as default. On the other hand you can override the default behavior.
For example you can make a
JTextField fixed or JButton resizable.

GroupLayout defines constants that provide precise control over
resize behavior. They can be used as parameters in the add(Component comp, int min, int pref, int max) 
method. Here are two examples:

1) To force a component to be resizable (allow shrinking and growing):

    group.add(component, 0, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) ...

This allows the component to resize between zero size (minimum) to any size (Short.MAX_VALUE
as maximum size means "infinite"). If we wanted the component not to shrink
below its
default minimum size, we'd use GroupLayout.DEFAULT_SIZE instead of 0
in the second parameter.

2) To make a component fixed size (suppress resizing):

    group.add(component, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE,
              GroupLayout.PREFERRED_SIZE) ...

In these examples the initial size of the component is not altered, its
default size is the component's preferred size. If we
wanted a specific size for the component, we would specify it in the second
parameter instead of using GroupLayout.DEFAULT_SIZE.

Resizable gaps

Specifying size and resizability applies to gaps as well, including the preferred ones.
For example, you can specify a preferred gap
between two components that acts like a spring pushing the components away
from each other (to the opposite sides of the container). The preferred distance
of the two components is only used as the minimum size of the gap. See the
following snippet:

    layout.createSequentialGroup()
        .add(c1)
        .addPreferredGap(LayoutStyle.RELATED,
                         GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
        .add(c2);

Justified layout

Resizable elements placed in a parallel group are stretched to fill the
space of the group determined by the largest element in the group, so they end up
aligned with the same size. GroupLayout
also provides control over whether the enclosing parallel group itself should
resize. If group resizing is suppressed, it prevents the contained elements from
growing over the preferred size of the group. This way you can make a block of components
align on both sides, or constrain individual components to have the same size.

Let's try to achieve the same size for two components from our example (c3 and c4 in the horizontal
dimension):

    layout.createParallelGroup(GroupLayout.LEADING, false)
      .add(c3, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
      .add(c4, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE);

The underlying mechanism works as follows:

  1. The size of the parallel group is set to the preferred size of the largest
    element; so to the preferred size of c4 in our example.
  2. Resizable elements are stretched to the size of the group. In our example,
    only c3 is effectively stretched, the size of c4 already corresponds to the size of the
    group.

As a result, c3 and c4 would have the same width.
The components would not resize further because the parallel group itself is not
resizable.

Question for attentive readers: Why do we define both components in the
parallel group as resizable in this example? It seems enough to have just c3
resizable since c4 is not stretched anyway...

(The answer is simple: because of platform and localization independence.
Otherwise we
would have to rely on that c4 component is always bigger than c3. But this
may change when the application runs on different platform or is translated to
another language. By having both components resizing they adjust to each other,
no matter which one is bigger at the moment.)

Same size of components

The previous case is special because the components are in the same parallel
group. But what if we wanted unrelated components to have the same size?
Clearly, the same size can't always be ensured by grouping. The OK and Cancel buttons
in a row at the bottom of a dialog are a good example. For this purpose
GroupLayout provides a linkSize method. This method allows
the size of
arbitrary components to be linked regardless of where they are placed. The resulting size
of the linked components is set
according to the largest component. For example:

    layout.linkSize(new Component[] { c3, c4 }, GroupLayout.HORIZONTAL);

Note that in this example the size is linked selectively for the horizontal dimension.


That's all for today. Now you should know enough about GroupLayout to
start using it!

Note: The easiest way to test GroupLayout is to use it with NetBeans 5.0
where it is bundled (just make sure "Swing Layout Extensions" library
is present in the libraries of the working project).

The layout manager and related extensions are hosted on http://swing-layout.dev.java.net
as one of the Swing Labs
projects (see the earlier Scott Violet's announcement).
You can get the latest version from the download
page
.

Would you like to see more examples of using GroupLayout? In the continuation of this article
I will show how to create a layout for a sample dialog, with detailed
explanation of each step, illustrations, code samples, etc.

Related Topics >>