The Source for Java Technology Collaboration
User: Password:



Tomas Pavek

Tomas Pavek's Blog

Getting to know GroupLayout, part 1

Posted by tpavek on February 22, 2006 at 08:58 AM | Comments (16)

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.



Comments
Comments are listed in date ascending order (oldest first) | Post Comment

  • GroupLayout is really neat (though the 140k hurts).
    But is it too late to make changes to the API?

    IMO there are a few changes that would simplify for the coder. This is especially true if varargs and auto boxing are taken into account.

    Take for instance that last line you have which would be simpler, and much shorter, as:

    layout.linkHorizontalSize(c3, c4);

    Don't you think?

    Cheers,
    Mikael Grev

    Posted by: mgrev on February 22, 2006 at 10:04 AM

  • You are right. This is planned for the next version. For now we needed the library to run also on 1.4...

    Posted by: tpavek on February 22, 2006 at 10:39 AM

  • @Mikael: As Tomas said, GroupLayout might become part of the JDK. Stay tuned :)

    Posted by: gfx on February 22, 2006 at 12:53 PM

  • It is nice to see new options for Swing developers. I wonder how much benefit there is in GroupLayout for developers who code by hand? After reading the article I don't see anything super revolutionary, at least from the standpoint of someone who uses TableLayout heavily.

    Posted by: bwy on February 23, 2006 at 09:44 AM

  • A big benefit is the default spacing system -- you don't specify gaps between components in pixels (though you can), but use "default gaps" that are computed dynamically at runtime based on the actual look and feel to follow its guidelines. Look at the "Gaps" paragraph for more details.

    Big help also is that you can have the default gaps inserted automatically. This will be even more visible in the bigger example I'll post soon.

    There is also the precise baseline alignment...

    Compared to TableLayout and other grid-based layout managers, GroupLayout is more flexible for layouts that don't look like a grid, while you still can do grids if you want. It is also more suitable in situations where the GUI is to be internationalized. I think I'll write something on this topic as well, it is not so obvious from the introductory article.

    Posted by: tpavek on February 23, 2006 at 10:18 AM

  • How about taking advantage of vargs for the linkSize() method to save typing new Component[]? instead of layout.linkSize(new Component[] { c3, c4 }, GroupLayout.HORIZONTAL); Have layout.linkSize(GroupLayout.HORIZONTAL,c3,c4); Thanks, Dave

    Posted by: dwalend on February 24, 2006 at 05:10 AM

  • 140k is uncompressed, its smaller after compression and MUCH smaller after PACK200 or obfuscation.

    Posted by: vprise on February 26, 2006 at 11:00 AM

  • Regarding the 140k. The size on the hard drive or download size does not bother me. But the uncompressed size is the best meassurement on how big it will be in memory when the classed are loaded. And in that context if would be better if it was smaller.

    Cheers, Mikael

    Posted by: mgrev on February 26, 2006 at 03:09 PM

  • ICould you please show how the code would look like, if C4 was aligned with C1 and spawn 3 cells horizontally (I understand GroupLayout is not about cells in a grid)?

    Posted by: vanosten on February 27, 2006 at 02:50 AM

  • So you basically want C4 to span the other three components along the horizontal axis, i.e. to be left-aligned with C1 and righ-aligned with C3. This means C4 should be in parallel with all three components. The code might look like:
        layout.setHorizontalGroup(
            layout.createParallelGroup(GroupLayout.LEADING, false)
                .add(layout.createSequentialGroup()
                    .add(c1)
                    .add(c2)
                    .add(c3))
                .add(c4, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE,
                         Short.MAX_VALUE)
        );
    Vertical layout is the same as in the original example.

    Posted by: tpavek on February 27, 2006 at 03:08 AM

  • Cool! I start to get the concept. GroupLayout really rocks. I used GridBagLayout until now, but that will change, even (or especially) because I code the GUI by hand.

    Is there some "hidden" cool feature so you can align across panels? E.g. at the bottom of GUI Building in NetBeans IDE 5.0 you can see that the textfield for the email address is aligned LEADING with the textfields for Name information. I guess that this is just a coincidence due to the length of the labels' texts or it might be hardcoded pixels. But I d' rather have "soft" constraints. And as far as I know

    1. using panels is the only way to have labeled borders for a group of components
    2. you cannot reuse a layoutmanager across panels

    Posted by: vanosten on February 27, 2006 at 03:42 AM

  • You hit it right on the head. Aligning across panels is not possible, which is a problem because you need a panel to paint a border. The two textfields in the tutorial are aligned just by coincidence.

    We've been thinking about having one layout manager for more containers, but it seems quite tricky to implement. Perhaps we could do something simpler like just make linkSize(...) work across containers.

    Otherwise, it should be possible (theoretically) to layout the panel with the border in parallel with the components instead of having it as a container. I haven't tried this yet. (Maybe a topic for a blog continuation ;)

    Posted by: tpavek on February 27, 2006 at 05:02 AM

  • It would be great if you would please provide a small tutorial on how one can create a simple GUI that accepts two numbers from a user and provides the addition, subtration, multiplication by making choices from a togglebox. I am new to Java and Matisse and have gone through all the tutorials on the Matisse website, which were very helpful. I am still confused about how one would go about adding functionality to the buttons and textboxes. (I am thinking on the lines of a simple tutorial like you find for VB.NET or VB6). Regards

    Posted by: sguls2 on April 13, 2006 at 03:34 AM

  • Thank you for responding to my previous comment. I managed to work though the issues that I was having and after a couple of hours of work was able to create a simple GUI with associated functionality. As you correctly pointed out a tutorial for beginners would be of tremendous benefit as it would greatly reduce the learning curve. I am willing to make a tutorial based on the GUI I developed, would you be willing to include that in the Matisse tutorial set?

    Posted by: sguls2 on April 14, 2006 at 01:35 AM



Only logged in users may post comments. Login Here.


Powered by
Movable Type 3.01D
 Feed java.net RSS Feeds