Sophisticated Layout Management Core Java

We have managed to lay out the user interface components of our sample applications sofar by using only the border layout, flow layout, and grid layout. For more complextasks, this is not going to be enough. In this section, we discuss advanced layout managementin detail.

Windows programmers may well wonder why Java makes so much fuss about layoutmanagers. After all, in Windows, layout management is not a big deal: First, you use adialog editor to drag and drop your components onto the surface of a dialog, and thenyou use editor tools to line up components, to space them equally, to center them, andso on. If you are working on a big project, you probably don’t have to worry about componentlayout at all—a skilled user interface designer does all this for you.

The problem with this approach is that the resulting layout must be manually updated if the size of the components changes. Why would the component size change? There are two common cases. First, a user may choose a larger font for button labels and other dialog text. If you try this out for yourself in Windows, you will find that many applications deal with this exceedingly poorly. The buttons do not grow, and the larger font is simply crammed into the same space as before. The same problem can occur when the strings in an application are translated to a foreign language. For example, the German

word for “Cancel” is “Abbrechen.” If a button has been designed with just enough room for the string “Cancel”, then the German version will look broken, with a clipped command string.

Why don’t Windows buttons simply grow to accommodate the labels? Because the designer of the user interface gave no instructions in which direction they should grow. After the dragging and dropping and arranging, the dialog editor merely remembers the pixel position and size of each component. It does not remember why the components were arranged in this fashion. The Java layout managers are a much better approach to component layout. With a layout manager, the layout comes with instructions about the relationships among the components. This was particularly important in the original AWT, which used native user interface elements. The size of a button or list box in Motif, Windows, and the Macintosh could vary widely, and an application or applet would not know a priori on which platform it would display its user interface. To some extent, that degree of variability has gone away with Swing. If your application forces a particular look and feel, such as the Metal look and feel, then it looks identical on all platforms. However, if you let users of your application choose their favorite look and feel, then you again need to rely on the flexibility of layout managers to arrange the components.

Since Java 1.0, the AWT includes the grid bag layout that lays out components in rows and columns. The row and column sizes are flexible and components can span multiple rows and columns. This layout manager is very flexible, but it is also very complex. The mere mention of the words “grid bag layout” has been known to strike fear in the hearts of Java programmers.

In an unsuccessful attempt to design a layout manager that would free programmers from the tyranny of the grid bag layout, the Swing designers came up with the box layout. According to the JDK documentation of the BoxLayout class: “Nesting multiple panels with different combinations of horizontal and vertical [sic] gives an effect similar to Grid- BagLayout, without the complexity.” However, because each box is laid out independently, you cannot use box layouts to arrange neighboring components both horizontally and vertically.

Java SE 1.4 saw yet another attempt to design a replacement for the grid bag layout—the spring layout. You use imaginary springs to connect the components in a container. As the container is resized, the springs stretch or shrink, thereby adjusting the positions of the components. This sounds tedious and confusing, and it is. The spring layout quickly sank into obscurity. In 2005, the NetBeans team invented the Matisse technology, which combines a layout tool and a layout manager. A user interface designer uses the tool to drop components into a container and to indicate which components should line up. The tool translates the designer’s intentions into instructions for the group layout manager. This is much more convenient than writing layout management code by hand. The group layout manager is now a part of Java SE 6. Even if you don’t use NetBeans as your IDE, we think you should consider using its GUI builder tool. You can design your GUI in Net- Beans and paste the resulting code into your IDE of choice.

In the coming sections, we cover the grid bag layout because it is commonly used and is still the easiest mechanism for producing layout code for older Java versions. We will tell you a strategy that makes grid bag layouts relatively painless in common situations.

Next, we cover the Matisse tool and the group layout manager. You will want to know how the group layout manager works so that you can check whether Matisse recorded the correct instructions when you visually positioned your components. We end the discussion of layout managers by showing you how you can bypass layout management altogether and place components manually, and how you can write your own layout manager.

The Grid Bag Layout

The grid bag layout is the mother of all layout managers. You can think of a grid bag ayout as a grid layout without the limitations. In a grid bag layout, the rows and columnscan have variable sizes. You can join adjacent cells to make room for larger components.

(Many word processors, as well as HTML, have the same capability when tables are edited: you start out with a grid and then merge adjacent cells if need be.) The components need not fill the entire cell area, and you can specify their alignment within cells.

Consider the font selector of . It consists of the following components:

  • Two combo boxes to specify the font face and size
  • Labels for these two combo boxes
  • Two checkboxes to select bold and italic
  • A text area for the sample string

A font selector

A font selector

Now, chop up the container into a grid of cells. (The rows and columns need not have equal size.) Each checkbox spans two columns, and the text area spans four rows.

Dialog box grid used in design

Dialog box grid used in design

To describe the layout to the grid bag manager, use the following procedure:

  1. Create an object of type GridBagLayout. You don’t tell it how many rows and columns the underlying grid has. Instead, the layout manager will try to guess it from the information you give it later.
  2. Set this GridBagLayout object to be the layout manager for the component.
  3. For each component, create an object of type GridBagConstraints. Set field values of the GridBagConstraints object to specify how the components are laid out within the grid bag.
  4. Finally, add each component with its constraints by using the call

Here’s an example of the code needed. (We go over the various constraints in more detail in the sections that follow —so don’t worry if you don’t know what some of the constraints do.)

The trick is knowing how to set the state of the GridBagConstraints object. We go over the most important constraints for using this object in the sections that follow.

The gridx, gridy, gridwidth, and gridheight Parameters

The gridx, gridy, gridwidth, and gridheight constraints define where the component is located in the grid. The gridx and gridy values specify the column and row positions of the upperleft corner of the component to be added. The gridwidth and gridheight values determine how many columns and rows the component occupies.

The grid coordinates start with 0. In particular, gridx = 0 and gridy = 0 denotes the top-left corner. For example, the text area in our example has gridx = 2, gridy = 0 because it starts in column 2 (that is, the third column) of row 0. It has gridwidth = 1 and gridheight = 4 because it spans one column and four rows.

Weight Fields

You always need to set the weight fields ( weightx and weighty) for each area in a grid bag layout. If you set the weight to 0, then the area never grows or shrinks beyond its initial size in that direction. In the grid bag layout for Figure below, we set the weightx field of the labels to be 0. This allows the labels to remain a constant width when you resize the window. On the other hand, if you set the weights for all areas to 0, the container will huddle in the center of its allotted area rather than stretching to fill it.

Conceptually, the problem with the weight parameters is that weights are properties of rows and columns, not individual cells. But you need to specify them in terms of cells because the grid bag layout does not expose the rows and columns. The row and column weights are computed as the maxima of the cell weights in each row or column. Thus, if you want a row or column to stay at a fixed size, you need to set the weights of all components in it to zero.

Note that the weights don’t actually give the relative sizes of the columns. They tell what proportion of the “slack” space should be allocated to each area if the container exceeds its preferred size. This isn’t particularly intuitive. We recommend that you set all weights at 100. Then, run the program and see how the layout looks. Resize the dialog to see how the rows and columns adjust. If you find that a particular row or column should not grow, set the weights of all components in it to zero. You can tinker with other weight values, but it is usually not worth the effort.

The fill and anchor Parameters

If you don’t want a component to stretch out and fill the entire area, you set the fill constraint. You have four possibilities for this parameter: the valid values are used in the forms Grid Bag Constraints .NONE, Grid Bag Constraints .HORIZONTAL, Grid Bag Constraints .VERTICAL, and Grid Bag Constraints .BOTH.

If the component does not fill the entire area, you can specify where in the area you want it by setting the anchor field. The valid values are GridBagConstraints.CENTER (the default), Grid Bag Constraints .NORTH, Grid Bag Constraints .NORTHEAST, Grid Bag Constraints .EAST, and so on.

Padding

You can surround a component with additional blank space by setting the insets field of GridBagConstraints. Set the left, top, right and bottom values of the Insets object to the amount of space that you want to have around the component. This is called the external padding.

The ipadx and ipady values set the internal padding. These values are added to the minimum width and height of the component. This ensures that the component does not shrink down to its minimum size.

Alternative Method to Specify the gridx, gridy>, gridwidth, and gridheight Parameters

The AWT documentation recommends that instead of setting the gridx and gridy values to absolute positions, you set them to the constant Grid Bag Constraints .RELATIVE. Then, add the components to the grid bag layout in a standardized order, going from left to right in the first row, then moving along the next row, and so on.

You still specify the number of rows and columns spanned, by giving the appropriate grid height and gridwidth fields. Except, if the component extends to the last row or column, you aren’t supposed to specify the actual number, but the constant GridBagConstraints.REMAINDER.

This tells the layout manager that the component is the last one in its row. This scheme does seem to work. But it sounds really goofy to hide the actual placement information from the layout manager and hope that it will rediscover it. All this sounds like a lot of trouble and complexity. But in practice, the strategy in the following recipe makes grid bag layouts relatively trouble-free:

  1. Sketch out the component layout on a piece of paper.
  2. Find a grid such that the small components are each contained in a cell and the larger components span multiple cells.
  3. Label the rows and columns of your grid with 0, 1, 2, 3, . . . . You can now read off the gridx, gridy, gridwidth, and gridheight values.
  4. For each component, ask yourself whether it needs to fill its cell horizontally or vertically. If not, how do you want it aligned? This tells you the fill and anchor parameters.
  5. Set all weights to 100. However, if you want a particular row or column to always stay at its default size, set the weightx or weighty to 0 in all components that belong to that row or column.
  6. Write the code. Carefully double-check your settings for the GridBagConstraints. One wrong constraint can ruin your whole layout.
  7. Compile, run, and enjoy.

Some GUI builders even have tools for specifying the constraints visually

Specifying grid bag constraints in NetBeans

Specifying grid bag constraints in NetBeans

A Helper Class to Tame the Grid Bag Constraints

The most tedious aspect of the grid bag layout is writing the code that sets the constraints. Most programmers write helper functions or a small helper class for this purpose. We present such a class after the complete code for the font dialog example. This class has the following features:

  • Its name is short: GBC instead of GridBagConstraints.
  • It extends GridBagConstraints, so you can use shorter names such as GBC.EAST for the constants.
  • Use a GBC object when adding a component, such as add(component, new GBC(1, 2));
  • There are two constructors to set the most common parameters: gridx and gridy, or gridx, gridy, gridwidth, and gridheight.
  • There are convenient setters for the fields that come in x/y pairs:
    add(component, new GBC(1, 2).setWeight(100, 100));
  • The setter methods return this, so you can chain them:
    add(component, new GBC(1, 2).setAnchor(GBC.EAST).setWeight(100, 100));
  • The setInsets methods construct the Insets object for you. To get one-pixel insets, simply call
    add(component, new GBC(1, 2).setAnchor(GBC.EAST).setInsets(1));

Listing below shows the complete code for the font dialog example. Here is the code that adds the components to the grid bag:

Once you understand the grid bag constraints, this kind of code is fairly easy to read and debug.

java.awt.GridBagConstraints 1.0

  • int gridx, gridy
    specifies the starting column and row of the cell. The default is 0.
  • int gridwidth, gridheight
    specifies the column and row extent of the cell. The default is 1
  • double weightx, weighty
    specifies the capacity of the cell to grow. The default is 0.
  • int anchor
    indicates the alignment of the component inside the cell. You can choose between absolute positions:

or their orientation -independent counterparts: Use the latter if your application may be localized for right-to-left or top -tobottom

text. The default is CENTER.
  • int fill
    specifies the fill behavior of the component inside the cell, one of NONE, BOTH, HORIZONTAL, or VERTICAL. The default is NONE.
  • int ipadx, ipady
    specifies the “internal” padding around the component. The default is 0.
  • Insets insets
    specifies the “external” padding along the cell boundaries. The default is no padding.
  • GridBagConstraints(int gridx, int gridy, int gridwidth, int gridheight, double weightx,
    double weighty, int anchor, int fill, Insets insets, int ipadx, int ipady)

constructs a GridBagConstraints with all its fields specified in the arguments. Sun recommends that this constructor be used only by automatic code generators because it makes your source code very hard to read.

Group Layout

Before discussing the API of the GroupLayout class, let us have a quick look at the Matisse GUI builder in NetBeans. Here is the workflow for laying out the top of the dialog. Start a new project and add a new JFrame form. Drag a label until two guidelines appear that separate it from the container borders:

Place another label below the first row:

Place another label below the first row:

Drag a text field so that its baseline lines up with the baseline of the first label. Again, note the guidelines:

Drag a text field so that its baseline lines up with the baseline of the first label. Again, note the guidelines

Finally, line up a password field with the label to the left and the text field above.

Finally, line up a password field with the label to the left and the text field above.

Matisse translates these actions into the following Java code:

Matisse translates these actions into the following Java code

That looks a bit scary, but fortunately you don’t have to write the code. However, it is helpful to have a basic understanding of the layout actions so that you can spot errors. We will analyze the basic structure of the code. The API notes at the end of this section explain each of the classes and methods in detail.

Components are organized by placing them into objects of type GroupLayout.SequentialGroup or GroupLayout.ParallelGroup. These classes are subclasses of GroupLayout .Group. Groups can contain components, gaps, and nested groups. The various add methods of the group classes return the group object so that method calls can be chained, like this: group.addComponent(...).addPreferredGap(...).addComponent(...);

As you can see from the sample code, the group layout separates the horizontal and vertical layout computations. To visualize the horizontal computations, imagine that the components are flattened so they have zero height, like this:

There are two parallel sequences of components, corresponding to the (slightly simplified) code:

There are two parallel sequences of components, corresponding to the (slightly simplified) code

But wait, that can’t be right. If the labels have different lengths, the text field and the password field won’t line up. We have to tell Matisse that we want the fields to line up. Select both fields, rightclick, and select Align -> Left to Column from the menu. Also line up the labels.

This dramatically changes the layout code:

Aligning the labels and text fields in Matisse

Aligning the labels and text fields in MatisseAligning the labels and text fields in Matisse

Now the labels and fields are each placed in a parallel group. The first group has an alignment of TRAILING (which means alignment to the right when the text direction is left-to-right):

Aligning the labels and text fields in Matisse

It seems like magic that Matisse can translate the designer’s instructions into nested groups, but as Arthur C. Clarke said, any sufficiently advanced technology is indistinguishable from magic.

For completeness, let’s look at the vertical computation. Now you should think of the components as having no width. We have a sequential group that contains two parallel groups, separated by gaps:

Aligning the labels and text fields in Matisse

The corresponding code is

As you can see from the code, the components are aligned by their baselines. (The baseline is the line on which the component text is aligned.)

You can force a set of components to have equal size. For example, we may want to make sure that the text field and password field width match exactly. In Matisse, select both, right -click, and select Same Size -> Same Width from the menu.

Forcing two components to have the same width

Forcing two components to have the same width

Matisse adds the following statement to the layout code: layout .linkSize (Swing Constants .HORIZONTAL, new Component[] {jPassword Field1, jText Field1});

javax.swing.GroupLayout 6

  • GroupLayout(Container host)
    constructs a GroupLayout for laying out the components in the host container. that you still need to call setLayout on the host object.)
  • void setHorizontalGroup(GroupLayout.Group g)
  • void setVerticalGroup(GroupLayout.Group g)
    sets the group that controls horizontal or vertical layout.
  • void linkSize(Component... components)
  • void linkSize(int axis, Component... component)
    forces the given components to have the same size, or the same size along the given axis (one of Swing Constants .HORIZONTAL or Swing Constants .VERTICAL).
  • GroupLayout.SequentialGroup createSequentialGroup()
    creates a group that lays out its children sequentially.
  • GroupLayout.ParallelGroup createParallelGroup()
  • GroupLayout.ParallelGroup createParallelGroup(GroupLayout.Alignment align)
  • GroupLayout.ParallelGroup createParallelGroup(GroupLayout.Alignment align, boolean resizable)
    creates a group that lays out its children in parallel.
  • boolean getHonorsVisibility()
  • void setHonorsVisibility(boolean b)

gets or sets the honorsVisibility property. When true (the default), non-visible components are not laid out. When false, they are laid out as if they were visible.

This is useful when you temporarily hide some components and don’t want the layout to change. boolean get Auto CreateGaps()

  • void setAutoCreateGaps(boolean b)
  • boolean getAutoCreateContainerGaps()
  • void setAutoCreateContainerGaps(boolean b)

gets and sets the autoCreateGaps and auto Create Container Gaps properties. When true, gaps are automatically added between components or the at the container boundaries. The default is false. A true value is useful when you manually produce a GroupLayout.

  • GroupLayout.Group addComponent(Component c)
  • GroupLayout .Group add Component(Component c, int minimumSize, int preferredSize, int maximumSize)

adds a component to this group. The size parameters can be actual (nonnegative) values, or the special constants Group Layout .DEFAULT _SIZE or Group Layout .PREFERRED _SIZE. When DEFAULT_SIZE is used, the component’s get Minimum Size, get Preferred Size, or get Maximum Size is called. When PREFERRED _SIZE is used, the component’s get Preferred Size method is called.

  • GroupLayout.Group addGap(int size)
  • GroupLayout.Group addGap(int minimumSize, int preferredSize, int maximumSize)
    adds a gap of the given rigid or flexible size.
  • GroupLayout.Group addGroup(GroupLayout.Group g)
    adds the given group to this group.
  • GroupLayout.ParallelGroup addComponent(Component c, GroupLayout.Alignment align)
  • GroupLayout.ParallelGroup addComponent(Component c, GroupLayout.Alignment align,
    int minimumSize, int preferredSize, int maximumSize)
  • GroupLayout.ParallelGroup addGroup(GroupLayout.Group g, GroupLayout.Alignment align) adds a component or group to this group, using the given alignment (one of BASELINE, LEADING, TRAILING, or CENTER).
  • GroupLayout.SequentialGroup addContainerGap()
  • GroupLayout.SequentialGroup addContainerGap(int preferredSize, int maximumSize)
    adds a gap for separating a component and the edge of the container.
  • GroupLayout.SequentialGroup addPreferredGap(LayoutStyle.ComponentPlacement type) adds a gap for separating components. The type is LayoutStyle .Component Placement .RELATED or LayoutStyle .Component Placement .UNRELATED.

Using No Layout Manager

There will be times when you don’t want to bother with layout managers but just want to drop a component at a fixed location (sometimes called absolute positioning). This is not a great idea for platform-independent applications, but there is nothing wrong with using it for a quick prototype.

Here is what you do to place a component at a fixed location:

  1. Set the layout manager to null.
  2. Add the component you want to the container.
  3. Then specify the position and size that you want:
  • void setBounds(int x, int y, int width, int height)
    moves and resizes a component.

Circle layout

Circle layout

Your own layout manager must implement the LayoutManager interface. You need to override the following five methods:

The first two methods are called when a component is added or removed. If you don’t keep any additional information about the components, you can make them do nothing. The next two methods compute the space required for the minimum and the preferred layout of the components. These are usually the same quantity. The fifth method does the actual work and invokes setBounds on all components.

Listing below shows the code for the CircleLayout manager, which, amazingly and uselessly enough, lays out the components along a circle inside the parent.

java.awt.LayoutManager 1.0

adds a component to the layout.
  • void removeLayoutComponent(Component comp)
    removes a component from the layout.
  • Dimension preferredLayoutSize(Container cont)
    returns the preferred size dimensions for the container under this layout.
  • Dimension minimumLayoutSize(Container cont)
    returns the minimum size dimensions for the container under this layout.
  • void layoutContainer(Container cont)
    lays out the components in a container.

Traversal Order

When you add many components into a window, you need to give some thought to the traversal order. When a window is first displayed, the first component in the traversal order has the keyboard focus. Each time the user presses the TAB key, the next component gains focus. (Recall that a component that has the keyboard focus can be manipulated with the keyboard. For example, a button can be “clicked” with the space bar when it has focus.) You may not personally care about using the TAB key to navigate through a set of controls, but plenty of users do. Among them are the mouse haters and those who cannot use a mouse, perhaps because of a handicap or because they are navigating the user interface by voice. For that reason, you need to know how Swing handles traversal order.

The traversal order is straightforward, first left to right and then top to bottom. For example, in the font dialog example, the components are traversed in the following order:

  • Face combo box
  • Sample text area (press CTRL+TAB to move to the next field; the TAB character is considered text input)
  • Size combo box
  • Bold checkbox
  • Italic checkbox

Geometric traversal order

Geometric traversal order

The situation is more complex if your container contains other containers. When the focus is given to another container, it automatically ends up within the top-left component in that container and then it traverses all other components in that container. Finally, the focus is given to the component following the container. You can use this to your advantage by grouping related elements in another container such as a panel.

In summary, there are two standard traversal policies in Java SE 1.4:

  • Pure AWT applications use the DefaultFocusTraversalPolicy. Components are included in the focus traversal if they are visible, displayable, enabled, and focusable, and if their native peers are focusable. The components are traversed in the order in which they were inserted in the container.
  • Swing applications use the LayoutFocusTraversalPolicy. Components are included in the focus traversal if they are visible, displayable, enabled, and focusable. The components are traversed in geometric order: left to right, then top to bottom. However, a container introduces a new “cycle”—its components are traversed first before the successor of the container gains focus.

Custom Layout Managers

You can design your own LayoutManager class that manages components in a special way. As a fun example, we show you how to arrange all components in a container to form a circle.


All rights reserved © 2018 Wisdom IT Services India Pvt. Ltd DMCA.com Protection Status

Core Java Topics