tpavek

2 posts

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


The first part of this article provided the theory behindGroupLayout. Now it's time to try it on a real example. So let's create a layout for this dialog:

http://weblogs.java.net/blog/tpavek/find.png

A good way to analyze the layout is to use a top-down decomposition. I'll describe the procedure that proved useful to me; step by step to make it clear. Once you get used to "group thinking", you'll be able to proceed much faster.

Horizontal layout

Examining the horizontal dimension from left to right, we can see there are 3 groups in a sequence. The first one is actually not a group, just a component -- the label. The second one is a group containing the text field and the checkboxes (we'll decompose it later). And the third is a group of the two buttons. As illustrated here:

http://weblogs.java.net/blog/tpavek/find_a1.PNG

Let's sketch out the sequential group in code. Note thatGroupLayout.LEADING corresponds to left alignment in the horizontal dimension. Also note we don't specify gaps, assuming the gap auto-insertion feature is turned on.

    layout.setHorizontalGroup(layout.createSequentialGroup()
        .add(label)
        .add(layout.createParallelGroup(GroupLayout.LEADING))
        .add(layout.createParallelGroup(GroupLayout.LEADING))
    );

Now let's decompose the group in the middle. This is the hardest one. There's a text field in parallel with a sequence of two parallel groups each containing two checkboxes. See the following illustration:

http://weblogs.java.net/blog/tpavek/find_a2.PNG

Let's add the corresponding code:

    layout.setHorizontalGroup(layout.createSequentialGroup()
        .add(label)
        .add(layout.createParallelGroup(GroupLayout.LEADING)
            .add(textField)
            .add(layout.createSequentialGroup()
                .add(layout.createParallelGroup(GroupLayout.LEADING)
                    .add(caseCheckBox)
                    .add(wholeCheckBox))
                .add(layout.createParallelGroup(GroupLayout.LEADING)
                    .add(wrapCheckBox)
                    .add(backCheckBox))))
        .add(layout.createParallelGroup(GroupLayout.LEADING))
    );

Note we want the text field to be resizable, but that happens automatically since JTextField returns the right maximum size by default.

The remaining group on the right is trivial: it contains just two buttons. We even don't need the picture ;-) Here's the code:

    layout.setHorizontalGroup(layout.createSequentialGroup()
        .add(label)
        .add(layout.createParallelGroup(GroupLayout.LEADING)
            .add(textField)
            .add(layout.createSequentialGroup()
                .add(layout.createParallelGroup(GroupLayout.LEADING)
                    .add(caseCheckBox)
                    .add(wholeCheckBox))
                .add(layout.createParallelGroup(GroupLayout.LEADING)
                    .add(wrapCheckBox)
                    .add(backCheckBox))))
        .add(layout.createParallelGroup(GroupLayout.LEADING)
            .add(findButton)
            .add(cancelButton))
    );

And finally, we'd like the buttons to have always the same size, so let's link them:

    layout.linkSize(new Component[] { findButton, cancelButton },
                    GroupLayout.HORIZONTAL);

Now we are done with the horizontal dimension. Let's switch to the vertical dimension. From now, we'll only need to think about the y axis.

Vertical layout

In the vertical dimension, we examine the layout from top to bottom. We definitely want all the components on the first line aligned on baseline. So along the vertical axis there is a sequence of the baseline group, followed by a group of the remaining components. See the following picture.

http://weblogs.java.net/blog/tpavek/find_a3.PNG

Let's sketch out the code. First, we need to define two parallel groups. Note that GroupLayout.LEADING corresponds to the top alignment in the vertical dimension.

    layout.setVerticalGroup(layout.createSequentialGroup()
        .add(layout.createParallelGroup(GroupLayout.BASELINE))
        .add(layout.createParallelGroup(GroupLayout.LEADING))
    );

We can fill the baseline group right away:

    layout.setVerticalGroup(layout.createSequentialGroup()
        .add(layout.createParallelGroup(GroupLayout.BASELINE)
            .add(label)
            .add(textField)
            .add(findButton))
        .add(layout.createParallelGroup(GroupLayout.LEADING))
    );

Now let's look at the bottom group. Note the Cancel button is not on a shared baseline with the checkboxes; it is aligned at the top. So the second parallel group comprises the button and a sequential group of two baseline groups with checkboxes:

http://weblogs.java.net/blog/tpavek/find_a4.PNG

The corresponding code looks as follows:

    layout.setVerticalGroup(layout.createSequentialGroup()
        .add(layout.createParallelGroup(GroupLayout.BASELINE)
            .add(label)
            .add(textField)
            .add(findButton))
        .add(layout.createParallelGroup(GroupLayout.LEADING)
            .add(layout.createSequentialGroup()
                .add(layout.createParallelGroup(GroupLayout.BASELINE)
                    .add(caseCheckBox)
                    .add(wrapCheckBox))
                .add(layout.createParallelGroup(GroupLayout.BASELINE)
                    .add(wholeCheckBox)
                    .add(backCheckBox)))
            .add(cancelButton))
    );

So, that's it! We've created a complete layout including resize behavior without specifying a single number in pixels. That's a true cross platform layout! Note that we don't need to specify gaps between components, we get correct spacing automatically and according to the look and feel guidelines. Here's the complete code for the Find dialog's layout:

    GroupLayout layout = new GroupLayout(getContentPane());
    getContentPane().setLayout(layout);
    layout.setAutocreateGaps(true);
    layout.setAutocreateContainerGaps(true);

    layout.setHorizontalGroup(layout.createSequentialGroup()
        .add(label)
        .add(layout.createParallelGroup(GroupLayout.LEADING)
            .add(textField)
            .add(layout.createSequentialGroup()
                .add(layout.createParallelGroup(GroupLayout.LEADING)
                    .add(caseCheckBox)
                    .add(wholeCheckBox))
                .add(layout.createParallelGroup(GroupLayout.LEADING)
                    .add(wrapCheckBox)
                    .add(backCheckBox))))
        .add(layout.createParallelGroup(GroupLayout.LEADING)
            .add(findButton)
            .add(cancelButton))
    );
    layout.linkSize(new Component[] { findButton, cancelButton },
                    GroupLayout.HORIZONTAL);

    layout.setVerticalGroup(layout.createSequentialGroup()
        .add(layout.createParallelGroup(GroupLayout.BASELINE)
            .add(label)
            .add(textField)
            .add(findButton))
        .add(layout.createParallelGroup(GroupLayout.LEADING)
            .add(layout.createSequentialGroup()
                .add(layout.createParallelGroup(GroupLayout.BASELINE)
                    .add(caseCheckBox)
                    .add(wrapCheckBox))
                .add(layout.createParallelGroup(GroupLayout.BASELINE)
                    .add(wholeCheckBox)
                    .add(backCheckBox)))
            .add(cancelButton))
    );

Here is the complete Find.java file. You can compile it and run against the Swing Layout Extensions library. Try resizing the dialog horizontally to see how the layout automatically adjusts to the new size.

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 added in the libraries of the working project). 

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


That's all for now. In future articles, I'll take a look at dynamic changes of the layout, compare GroupLayoutwith other layout managers, describe its limitations, and I'll cover other topics of interest based on your feedback.

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 thatGroupLayout 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 howGroupLayout 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 behindGroupLayout. 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 GroupLayoutis that it works with horizontal and vertical layout separately. This is not that uncommon, but unlike other layout managersGroupLayout does not use a single constraintsobject 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 thehorizontal layout, you don't need to worry about thevertical 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 fromSpringLayout 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 BoxLayoutor 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 inparallel, 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 purposeGroupLayout 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:

http://weblogs.java.net/blog/tpavek/example1a.PNG

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:

http://weblogs.java.net/blog/tpavek/groups1a.PNG

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):

http://weblogs.java.net/blog/tpavek/example1b.PNG

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.

http://weblogs.java.net/blog/tpavek/groups1b.PNG

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 withGroupLayout! 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 LayoutStyleclass defines corresponding constants (to be used as the first parameter of the addPreferredGap method):RELATED, UNRELATED andINDENT. 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.

http://weblogs.java.net/blog/tpavek/gaps.PNG

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 invokingsetAutocreateGaps(true) andsetAutocreateContainerGaps(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 newGroupLayout 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 setHorizontalGroupand setVerticalGroup methods. Groups are created viacreateSequentialGroup andcreateParallelGroup 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 theGroupLayout class: LEADING,TRAILING and CENTER. These constants are used for both dimensions; in the horizontal dimensionLEADING 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'sgetMinimumSize(), getPreferredSize() andgetMaximumSize() methods). Thus you don't need to specify anything for most of the components, e.g. to makeJTextField 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 orJButton resizable.

GroupLayout defines constants that provide precise control over resize behavior. They can be used as parameters in theadd(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 useGroupLayout.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 usingGroupLayout.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 springpushing 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.

http://weblogs.java.net/blog/tpavek/same_size_stretched.PNG

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 thatc4 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 purposeGroupLayout 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 aboutGroupLayout 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.netas 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 usingGroupLayout? 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.

Filter Blog