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:
- 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.
- 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
- using panels is the only way to have labeled borders for a group of components
- 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
|