Java ScreenShot

Screenshot Core Java 2: Volume I - Fundamentals

Table of Contents
 9.  User Interface Components with Swing

Sophisticated Layout Management

We have managed to lay out the user interface components of our sample apps so far by using only the border layout and flow layout. For more complex tasks, this is not going to be enough. In this section, we will give you a detailed discussion of the advanced layout managers that the standard Java library provides to organize components. Windows programmers may well wonder why Java makes so much fuss about layout managers. After all, in Windows, layout management is not a big deal: First, you use a dialog editor to drag and drop your components onto the surface of the dialog, and then you use editor tools to line up components, to space them equally, to center them, and so on. If you are working on a big project, you probably don't have to worry about component layout 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 apps 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 translating the strings in an app 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 between 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 app 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 app 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 app choose their favorite look and feel, then you again need to rely on the flexibility of layout managers to arrange the components. Of course, to achieve complex layouts, you will need to have more control over the layout than the border layout and flow layout give you. In this section, we will discuss the layout managers that the standard Java library has to offer. Using a sophisticated layout manager combined with the appropriate use of multiple panels will give you complete control over how your app will look.

Java graphics exclamatory_icon.gif

If none of the layout schemes fit your needs, break the surface of your window into separate panels and lay out each panel separately. Then, use another layout manager to organize the panels.

First, let's review a few basic principles. As you know, in the AWT, components are laid out inside containers. Buttons, text fields, and other user interface elements are components and can be placed inside containers. Therefore, these classes extend the class Component. Containers such as panels can themselves be put inside other containers. Therefore, the class Container derives from Component. Screenshot-34 shows the inheritance hierarchy for Component.
Screenshot-34. Inheritance hierarchy for the Component class

Java graphics 09fig34.gif

Java graphics notes_icon.gif

Note that some objects belong to classes extending Component even though they are not user interface components and cannot be inserted into containers. Top-level windows such as JFrame and JApplet cannot be contained inside another window or panel.

As you have seen, to organize the components in a container, you first specify a layout manager. For example, the statement
panel.setLayout(new GridLayout(4, 4));

will use the GridLayout class to lay out the panels. After you set the layout manager, you add components to the container. The add method of the container passes the component and any placement directions to the layout manager. With the border layout manager, you give a string to indicate component placement:

panel.add(new JTextField(), BorderLayout.SOUTH);

With the grid layout that you will see shortly, you need to add components sequentially:

panel.add(new JCheckBox("italic"));
panel.add(new JCheckBox("bold"));

The grid layout is useful to arrange components in a grid, somewhat like the rows and columns of a spreadsheet. However, all rows and columns of the grid have identical size, which is not all that useful in practice. To overcome the limitations of the grid layout, the AWT supplies the grid bag layout. It, too, lays out components in rows and columns, but 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. Actually, in most common situations, the grid bag layout is not that hard to use, and we tell you a strategy that should make grid bag layouts relatively painless. 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. The box layout simply arranges a sequence of components horizontally or vertically. When arranging components horizontally, it is similar to the flow layout; however, components do not "wrap" to a new row when one row is full. By placing a number of horizontal box layouts inside a vertical box layout (or the other way around), you can give some order to a set of components in a two-dimensional area. However, since each box is laid out independently, you cannot use box layouts to arrange neighboring components both horizontally and vertically. The Swing set also contains an overlay layout that lets you place components on top of each other. This layout manager is not generally useful, and we won't discuss it. Finally, there is a card layout that was used in the original AWT to produce tabbed dialogs. Since Swing has a much better tabbed dialog container (called JTabbedPane—see Volume 2), we do not cover the card layout here. We end the discussion of layout managers by showing you how you can bypass layout management altogether and place components manually, and by showing you how you can write your own layout manager.

Box Layout

The box layout lets you lay out a single row or column of components with more flexibility than the grid layout. There is even a container—the Box class—whose default layout manager is the BoxLayout (unlike the JPanel class whose default layout manager is the FlowLayout). Of course, you can also set the layout manager of a JPanel to the box layout, but it is simpler to just start with a Box container. The Box class also contains a number of static methods that are useful for managing box layouts. To create a new container with a box layout, you can simply call

Box b = Box.createHorizontalBox();


Box b = Box.createVerticalBox();

Then, you add components in the usual way:


In a horizontal box, the components are arranged left to right. In a vertical box, the components are arranged top to bottom. Let us look at the horizontal layout more closely. Each component has three sizes:

  • The preferred size – the width and height at which the component would like to be displayed;
  • The maximum size – the largest width and height at which the component is willing to be displayed;
  • The minimum size – the smallest width and height at which the component is willing to be displayed.

Here are details about what the box layout manager does:

  1. It computes the maximum (!) height of the tallest component.

  2. It tries to grow all components vertically to that height.

  3. If a component does not actually grow to that height when requested, then its y-alignment is queried by calling its getAlignmentY method. That method returns a floating-point number between 0 (align on top) and 1 (align on bottom). The default in the Component class is 0.5 (center). The value is used to align the component vertically.

  4. The preferred width of each component is obtained. All preferred widths are added up.

  5. If the total preferred width is less than the box width, then the components are expanded, by letting them grow to their maximum width. Components are then placed, from left to right, with no additional space between them. If the total preferred width is greater than the box width, the components are shrunk, potentially down to their minimum width but no further. If the components don't all fit at their minimum width, some of them will not be shown.

For vertical layouts, the process is analogous.

Java graphics exclamatory_icon.gif

It is unfortunate that BoxLayout tries to grow components beyond the preferred size. In particular, text fields have maximum width and height set to Integer.MAX_VALUE; that is, they are willing to grow as much as necessary. If you put a text field into a box layout, it will grow to monstrous proportions. Remedy: set the maximum size to the preferred size:


By default, there is no space between the components in a box layout. (Unlike the flow layout, the box layout does not have a notion of gaps between components.) To space the components out, you add invisible fillers. There are three kinds of fillers:

  • Struts
  • Rigid areas
  • Glue

A strut simply adds some space between components. For example, here is how you can add ten pixels of space between two components in a horizontal box:


You add a horizontal strut into a horizontal box, or a vertical strut into a vertical box, to add space between components. You can also add a vertical strut into a horizontal box, but that does not affect the horizontal layout. Instead, it sets the minimum height of the box. The rigid area filler is similar to a pair of struts. It separates adjacent components but also adds a height or width minimum in the other direction. For example,

b.add(Box.createRigidArea(new Dimension(5, 20));

adds an invisible area with minimum, preferred, and maximum width of 5 pixels and height of 20 pixels, and centered alignment. If added into a horizontal box, it acts like a strut of width 5 and also forces the minimum height of the box to be 20 pixels. By adding struts, you separate adjacent components by a fixed amount. Adding glue separates components as much as possible. The (invisible) glue expands to consume all available empty space, pushing the components away from each other. (We don't know why the designers of the box layout came up with the name "glue"—"spring" would have been a more appropriate name.) For example, here is how you space apart two buttons in a box as much as possible:


If the box contains no other components, then button1 is moved all the way to the left and button2 is moved all the way to the right. The program in Example 9-14 arranges a set of labels, text fields, and buttons, using a set of horizontal and vertical box layouts. Each row is placed in a horizontal box. Struts separate the labels from the text fields. Glue pushes the two buttons away from each other. The three horizontal boxes are placed in a vertical box, with glue pushing the button box to the bottom (see Screenshot-35).

Example 9-14
 1. import java.awt.*;
 2. import java.awt.event.*;
 3. import javax.swing.*;
 5. public class BoxLayoutTest
 6. {
 7. public static void main(String[] args)
 8. {
 9. BoxLayoutFrame frame = new BoxLayoutFrame();
10. frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
12. }
13. }
15. /**
16. A frame that uses box layouts to organize various components.
17. */
18. class BoxLayoutFrame extends JFrame
19. {
20. public BoxLayoutFrame()
21. {
22. setTitle("BoxLayoutTest");
25. // construct the top horizontal box
27. JLabel label1 = new JLabel("Name:");
28. JTextField textField1 = new JTextField(10);
29. textField1.setMaximumSize(textField1.getPreferredSize());
31. Box hbox1 = Box.createHorizontalBox();
32. hbox1.add(label1);
33. // separate with a 10-pixel strut
34. hbox1.add(Box.createHorizontalStrut(10));
35. hbox1.add(textField1);
37. // construct the middle horizontal box
39. JLabel label2 = new JLabel("Password:");
40. JTextField textField2 = new JTextField(10);
41. textField2.setMaximumSize(textField2.getPreferredSize());
44. Box hbox2 = Box.createHorizontalBox();
45. hbox2.add(label2);
46. // separate with a 10-pixel strut
47. hbox2.add(Box.createHorizontalStrut(10));
48. hbox2.add(textField2);
50. // construct the bottom horizontal box
52. JButton button1 = new JButton("Ok");
53. JButton button2 = new JButton("Cancel");
55. Box hbox3 = Box.createHorizontalBox();
56. hbox3.add(button1);
57. // use "glue" to push the two buttons apart
58. hbox3.add(Box.createGlue());
59. hbox3.add(button2);
61. // add the three horizontal boxes inside a vertical box
63. Box vbox = Box.createVerticalBox();
64. vbox.add(hbox1);
65. vbox.add(hbox2);
66. vbox.add(Box.createGlue());
67. vbox.add(hbox3);
69. Container contentPane = getContentPane();
70. contentPane.add(vbox, BorderLayout.CENTER);
71. }
73. public static final int DEFAULT_WIDTH = 200;
74. public static final int DEFAULT_HEIGHT = 200;
75. }

Screenshot-35. Box layouts

Java graphics 09fig35.gif

javax.swing.Box 1.2

Java graphics api_icon.gif
  • static Box createHorizontalBox()
  • static Box createVerticalBox()

    create a container that arranges its contents horizontally or vertically.

  • static Component createHorizontalGlue()
  • static Component createVerticalGlue()
  • static Component createGlue()

    create an invisible component that can expand infinitely horizontally, vertically, or in both directions.

  • static Component createHorizontalStrut(int width)
  • static Component createVerticalStrut(int height)
  • static Component createRigidArea(Dimension d)

    create an invisible component with fixed width, fixed height, or fixed width and height.

java.awt.Component 1.0

Java graphics api_icon.gif
  • float getAlignmentX() 1.1
  • float getAlignmentY() 1.1

    return the alignment along the x- or y-axis, a value between 0 and 1. The value 0 denotes alignment on top or left, 0.5 is centered, 1 is aligned on bottom or right.

The Grid Bag Layout

The grid bag layout is the mother of all layout managers. You can think of a grid bag layout as a grid layout without the limitations. In a grid bag layout, the rows and columns can 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 editing tables: 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. Fair warning: using grid bag layouts can be incredibly complex. The payoff is that they have the most flexibility and will work in the widest variety of situations. Keep in mind that the purpose of layout managers is to keep the arrangement of the components reasonable under different font sizes and operating systems, so it is not surprising that you need to work somewhat harder than when you design a layout just for one environment.

Java graphics notes_icon.gif

According to the SDK documentation of the BoxLayout class: "Nesting multiple panels with different combinations of horizontal and vertical [sic] gives an effect similar to GridBagLayout, without the complexity." However, as you can see from Screenshot-35, the effect that you can achieve from multiple box layouts is plainly not useful in practice. No amount of fussing with boxes, struts, and glue will ensure that the components line up. When you need to arrange components so that they line up horizontally and vertically, you need to use the GridBagLayout class.

Consider the font selection dialog of Screenshot-36. It consists of the following components:
  • Two combo boxes to specify the font face and size;
  • Labels for these two combo boxes;
  • Two check boxes to select bold and italic;
  • A text area for the sample string.
Screenshot-36. Font dialog box

Java graphics 09fig36.gif

Now, chop up the dialog box into a grid of cells, as shown in Screenshot-37. (The rows and columns do not need to have equal size.) As you can see, each check box spans two columns, and the text area spans four rows.

Screenshot-37. Dialog box grid used in design

Java graphics 09fig37.gif

To describe the layout to the grid bag manager, you must go through the following convoluted 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. Create an object of type GridBagConstraints. The GridBagConstraints object will specify how the components are laid out within the grid bag.

  4. For each component, fill in the GridBagConstraints object. Then (finally), add the component with the constraints by using the call:

    add(component, constraints);

Here's an example of the code needed. (We will 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.)

GridBagLayout layout = new GridBagLayout();
GridBagConstraints constraints = new GridBagConstraints();
constraints.weightx = 100;
constraints.weighty = 100;
constraints.gridx = 0;
constraints.gridy = 2;
constraints.gridwidth = 2;
constraints.gridheight = 1;
contentPane.add(style, bold);

It is obviously best to write a small helper function for this kind of repetitive code—see the listing in Example 9-15 for an example of one. The trick is knowing how to set the state of the GridBagConstraints object. We will go over the most important constraints for using this object in the sections that follow.

The gridx, gridy, gridwidth, and gridheight Parameters

These constraints define where the component is located in the grid. The gridx and gridy values specify the column and row positions of the upper-left 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 Screenshot-36, 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 need to set the fill constraint. You have four possibilities for this parameter: the valid values are used in the forms GridBagConstraints.NONE, GridBagConstraints.HORIZONTAL, GridBagConstraints.VERTICAL, and GridBagConstraints.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), GridBagConstraints.NORTH, GridBagConstraints.NORTHEAST, GridBagConstraints.EAST, and so on.


You can surround a component with additional blank space by setting the insets field of the GridBagLayout. 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.

An 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 GridBagConstraints.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 gridheight 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.

for Making a Grid Bag Layout

Step 1. Sketch out the component layout on a piece of paper.

Step 2. Find a grid such that the small components are each contained in a cell and the larger components span multiple cells.

Step 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.

Step 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.

Step 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.

Step 6. Write the code. Carefully double-check your settings for the GridBagConstraints. One wrong constraint can ruin your whole layout.

Step 7. Compile, run, and enjoy.

Example 9-15 is the complete code to implement the font dialog example.
Example 9-15
 1. import java.awt.*;
 2. import java.awt.event.*;
 3. import javax.swing.*;
 4. import javax.swing.event.*;
 6. public class FontDialog
 7. {
 8. public static void main(String[] args)
 9. {
 10. FontDialogFrame frame = new FontDialogFrame();
 11. frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 13. }
 14. }
 16. /**
 17. A frame that uses a grid bag layout to arrange font
 18. selection components.
 19. */
 20. class FontDialogFrame extends JFrame
 21. {
 22. public FontDialogFrame()
 23. {
 24. setTitle("FontDialog");
 27. Container contentPane = getContentPane();
 28. GridBagLayout layout = new GridBagLayout();
 29. contentPane.setLayout(layout);
 31. ActionListener listener = new FontAction();
 33. // construct components
 35. JLabel faceLabel = new JLabel("Face: ");
 37. face = new JComboBox(new String[]
 38. {
 39. "Serif", "SansSerif", "Monospaced",
 40. "Dialog", "DialogInput"
 41. });
 43. face.addActionListener(listener);
 45. JLabel sizeLabel = new JLabel("Size: ");
 47. size = new JComboBox(new String[]
 48. {
 49. "8", "10", "12", "15", "18", "24", "36", "48"
 50. });
 52. size.addActionListener(listener);
 54. bold = new JCheckBox("Bold");
 55. bold.addActionListener(listener);
 57. italic = new JCheckBox("Italic");
 58. italic.addActionListener(listener);
 60. sample = new JTextArea();
 61. sample.setText(
 62. "The quick brown fox jumps over the lazy dog");
 63. sample.setEditable(false);
 64. sample.setLineWrap(true);
 65. sample.setBorder(BorderFactory.createEtchedBorder());
 67. // add components to grid
 69. GridBagConstraints constraints = new GridBagConstraints();
 71. constraints.fill = GridBagConstraints.NONE;
 72. constraints.anchor = GridBagConstraints.EAST;
 73. constraints.weightx = 0;
 74. constraints.weighty = 0;
 76. add(faceLabel, constraints, 0, 0, 1, 1);
 77. add(sizeLabel, constraints, 0, 1, 1, 1);
 79. constraints.fill = GridBagConstraints.HORIZONTAL;
 80. constraints.weightx = 100;
 82. add(face, constraints, 1, 0, 1, 1);
 83. add(size, constraints, 1, 1, 1, 1);
 85. constraints.weighty = 100;
 86. constraints.fill = GridBagConstraints.NONE;
 87. constraints.anchor = GridBagConstraints.CENTER;
 89. add(bold, constraints, 0, 2, 2, 1);
 90. add(italic, constraints, 0, 3, 2, 1);
 92. constraints.fill = GridBagConstraints.BOTH;
 93. add(sample, constraints, 2, 0, 1, 4);
 94. }
 96. /**
 97. A convenience method to add a component to given grid bag
 98. layout locations.
 99. @param c the component to add
100. @param constraints the grid bag constraints to use
101. @param x the x grid position
102. @param y the y grid position
103. @param w the grid width
104. @param h the grid height
105. */
106. public void add(Component c, GridBagConstraints constraints,
107. int x, int y, int w, int h)
108. {
109. constraints.gridx = x;
110. constraints.gridy = y;
111. constraints.gridwidth = w;
112. constraints.gridheight = h;
113. getContentPane().add(c, constraints);
114. }
116. public static final int DEFAULT_WIDTH = 300;
117. public static final int DEFAULT_HEIGHT = 200;
119. private JComboBox face;
120. private JComboBox size;
121. private JCheckBox bold;
122. private JCheckBox italic;
123. private JTextArea sample;
125. /**
126. An action listener that changes the font of the
127. sample text.
128. */
129. private class FontAction implements ActionListener
130. {
131. public void actionPerformed(ActionEvent event)
132. {
133. String fontFace = (String)face.getSelectedItem();
134. int fontStyle = (bold.isSelected() ? Font.BOLD : 0)
135. + (italic.isSelected() ? Font.ITALIC : 0);
136. int fontSize = Integer.parseInt(
137. (String)size.getSelectedItem());
138. Font font = new Font(fontFace, fontStyle, fontSize);
139. sample.setFont(font);
140. sample.repaint();
141. }
142. }
143. }

java.awt.GridBagConstraints 1.0

Java graphics api_icon.gif
  • int gridx, gridy

    indicates the starting column and row of the cell.

  • int gridwidth, gridheight

    indicates the column and row extent of the cell.

  • double weightx, weighty

    indicates the capacity of the cell to grow.

  • int anchor

    indicates the alignment of the component inside the cell, one of CENTER, NORTH, NORTHEAST, EAST, SOUTHEAST, SOUTH, SOUTHWEST, WEST, or NORTHWEST.

  • int fill

    indicates the fill behavior of the component inside the cell, one of NONE, BOTH, HORIZONTAL, or VERTICAL.

  • int ipadx, ipady

    indicates the "internal" padding around the component.

  • Insets insets

    indicates the "external" padding along the cell boundaries.

  • GridBagConstraints(int gridx, int gridy, int gridwidth, int gridheight, double weightx, double weighty, int anchor, int fill, Insets insets, int ipadx, int ipady) 1.2

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

The Spring Layout

Ever since programmers met the GridBagLayout, they begged the Java team for a layout manager that is equally flexible but more intuitive. Finally SDK 1.4 features a contender, the SpringLayout. In this section you will see how it measures up. With the spring layout, you attach springs to each component. A spring is a device for specifying component positions. As shown in Screenshot-38, each spring has

  • A minimum value;
  • A preferred value;
  • A maximum value;
  • An actual value.
Screenshot-38. A spring

Java graphics 09fig38.gif

When the spring is compressed or expanded in the layout phase, the actual value is set, by moving the preferred value towards the minimum or maximum value. Then the actual value determines the position of the component to which it has been attached. The spring class defines a sum operation that takes two springs and produces a new spring that combines the characteristics of the individual springs. When you lay out a number of components in a row, you attach several springs so that their sum spans the entire container—see Screenshot-39. That sum spring is now compressed or expanded so that its value equals the dimension of the container. This operation exerts a strain on the individual springs. Each string value is set so that the strain of each string equals the strain of the sum. Thus, the values of the individual strings are determined, and the layout is fixed. (If you are interested in the details of the strain computations, check the API documentation of the Spring class for more information.)

Screenshot-39. Summing springs

Java graphics 09fig39.gif

Let's run through a simple example. Suppose you want to lay out three buttons horizontally.

JButton b1 = new JButton("One");
JButton b2 = new JButton("Two");
JButton b3 = new JButton("Three");

You first set the layout manager of the content pane to a SpringLayout and add the components.

Container contentPane = getContentPane();
SpringLayout layout = new SpringLayout();

Now construct a spring with a good amount of compressibility. The static Spring.constant method produces a spring with given minimum, preferred, and maximum values. (The spring isn't actually constant—it can be compressed or expanded.)

Spring s = Spring.constant(0, 10000, 10000);

Next, attach one copy of the spring from the west side of the container to the west side of b1:

layout.putConstraint(SpringLayout.WEST, b1, s,
 SpringLayout.WEST, contentPane);

The putConstraint method adds the given spring so that it ends at the first parameter set (the west side of b1 in our case) and starts from the second parameter set (the west side of the content pane). Next, you link up the other springs:

layout.putConstraint(SpringLayout.WEST, b2, s,
 SpringLayout.EAST, b1);
layout.putConstraint(SpringLayout.WEST, b3, s,
 SpringLayout.EAST, b2);

Finally, you hook up a spring with the east wall of the container:

layout.putConstraint(SpringLayout.EAST, contentPane, s,
 SpringLayout.EAST, b3);

The result is that the four springs are compressed to the same size, and the buttons are equally spaced (see Screenshot-40).

Screenshot-40. Equally Spaced Buttons

Java graphics 09fig40.gif

Alternatively, you may want to vary the distances. Let's suppose you want to have a fixed distance between the buttons. Use a strut—a spring that can't be expanded or compressed. You get such a spring with the single-parameter version of the Spring.constant method:

Spring strut = Spring.constant(10);

If you add two struts between the buttons, but leave the springs at the ends, the result is a button group that is centered in the container (see Screenshot-41).

Screenshot-41. Springs and Struts

Java graphics 09fig41.gif

Of course, you don't really need the spring layout for such a simple arrangement. Let's look at something more complex, a portion of the font dialog of the preceding example. We have two combo boxes with labels, and we want to have the west sides of both combo boxes start after the longer label (see Screenshot-42).

Screenshot-42. Lining up Columns

Java graphics 09fig42.gif

This calls for another spring operation. You can form the maximum of two springs with the static Spring.max method. The result is a spring that is as long as the longer of the two inputs. We get the maximum of the two east sides like this:

Spring labelsEast = Spring.max(
 layout.getConstraint(SpringLayout.EAST, faceLabel),
 layout.getConstraint(SpringLayout.EAST, sizeLabel));

Note that the getConstraint method yields a spring that reaches all the way from the west side of the container to the given sidesof the component Let's add a strut so that there is some space between the labels and the combo boxes:

Spring combosWest = Spring.sum(labelsEast, strut);

Now we attach this spring to the west side of both combo boxes. The starting point is the start of the container, since the labelsEast spring starts there.

layout.putConstraint(SpringLayout.WEST, face, combosWest,
 SpringLayout.WEST, contentPane);
layout.putConstraint(SpringLayout.WEST, size, combosWest,
 SpringLayout.WEST, contentPane);

Now the two combo boxes line up because they are held by the same spring. However, there is a slight blemish. We'd prefer if the labels were right aligned. It is possible to achieve this effect as well, but it requires a more precise understanding of spring attachments. Let's look at the horizontal springs in detail. Vertical springs follow the same logic. Screenshot-43 shows the three ways in which horizontal springs can be attached:

  • Connecting the west side of the component with the west side of the component;
  • Traversing the width of the component;
  • Connecting the west side of the component with the east side of the component.
Screenshot-43. Horizontal springs attached to a component

Java graphics 09fig43.gif

You get these springs as follows:

Spring west = layout.getConstraints(component).getX();
Spring width = layout.getConstraints(component).getWidth();
Spring east = layout.getConstraint(SpringLayout.EAST,

The getConstraints method yields an object of type SpringLayout.Constraints. You can think of such an object as a rectangle, except that the x, y, width, and height values are springs, not numbers. The getConstraint method yields a single spring that reaches to one of the four component boundaries. You can also get the west spring as

Spring west = layout.getConstraint(SpringLayout.WEST,

Of course, the three springs are related: The spring sum of west and width must equal east. When the component constraints are first set, the width is set to a spring whose parameters are the minimum, preferred, and maximum width of the component. The west is set to 0.

Java graphics caution_icon.gif

If you don't set the west (and north) spring of a component, then the component stays at offset 0 in the container.

If a component has two springs set and you add a third one, then it becomes overconstrained. One of the existing springs is removed and its value is computed as the sum or difference of the other springs. Table 9-3 shows which spring is recomputed.
Table 9-3. Adding a Spring to an Overconstrained Component

Added Spring

Removed Spring

Replaced By



east - west



west + width



east - width

Java graphics notes_icon.gif

The difference between two springs may not be intuitive, but it makes sense in the spring algebra. There is no Java method for spring subtraction. If you need to compute the difference of two springs, use

Spring.sum(s, Spring.minus(t))
Now you know enough about springs to solve the "right alignment" problem. Compute the maximum of the widths of the two labels. Then set the east spring of both labels to that maximum. As you can see from Table 9-3, the label widths don't change, the west springs are recomputed, and the labels become aligned at the eastern boundary.
Spring labelsEast = Spring.sum(strut,
layout.putConstraint(SpringLayout.EAST, faceLabel, labelsEast,
 SpringLayout.WEST, contentPane);
layout.putConstraint(SpringLayout.EAST, sizeLabel, labelsEast,
 SpringLayout.WEST, contentPane);

Example 9-16 shows how to lay out the font dialog with springs. If you look at the code, you will probably agree that the spring layout is quite a bit less intuitive than the grid bag layout. Hopefully, tools will appear that make the spring layout more approachable. However, in the meantime we recommend that you stick with the grid bag layout for complex layouts.

Example 9-16
 1. import java.awt.*;
 2. import java.awt.event.*;
 3. import javax.swing.*;
 4. import javax.swing.event.*;
 6. public class SpringLayoutTest
 7. {
 8. public static void main(String[] args)
 9. {
 10. FontDialogFrame frame = new FontDialogFrame();
 11. frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 13. }
 14. }
 16. /**
 17. A frame that uses a spring layout to arrange font
 18. selection components.
 19. */
 20. class FontDialogFrame extends JFrame
 21. {
 22. public FontDialogFrame()
 23. {
 24. setTitle("FontDialog");
 27. Container contentPane = getContentPane();
 28. SpringLayout layout = new SpringLayout();
 29. contentPane.setLayout(layout);
 31. ActionListener listener = new FontAction();
 33. // construct components
 35. JLabel faceLabel = new JLabel("Font Face: ");
 37. face = new JComboBox(new String[]
 38. {
 39. "Serif", "SansSerif", "Monospaced",
 40. "Dialog", "DialogInput"
 41. });
 43. face.addActionListener(listener);
 45. JLabel sizeLabel = new JLabel("Size: ");
 47. size = new JComboBox(new String[]
 48. {
 49. "8", "10", "12", "15", "18", "24", "36", "48"
 50. });
 52. size.addActionListener(listener);
 54. bold = new JCheckBox("Bold");
 55. bold.addActionListener(listener);
 57. italic = new JCheckBox("Italic");
 58. italic.addActionListener(listener);
 60. sample = new JTextArea();
 61. sample.setText(
 62. "The quick brown fox jumps over the lazy dog");
 63. sample.setEditable(false);
 64. sample.setLineWrap(true);
 65. sample.setBorder(BorderFactory.createEtchedBorder());
 67. contentPane.add(faceLabel);
 68. contentPane.add(sizeLabel);
 69. contentPane.add(face);
 70. contentPane.add(size);
 71. contentPane.add(bold);
 72. contentPane.add(italic);
 73. contentPane.add(sample);
 75. // add strings to lay out components
 76. Spring strut = Spring.constant(10);
 78. Spring labelsEast = Spring.sum(strut,
 79. Spring.max(
 80. layout.getConstraints(faceLabel).getWidth(),
 81. layout.getConstraints(sizeLabel).getWidth()));
 83. layout.putConstraint(SpringLayout.EAST, faceLabel,
 84. labelsEast, SpringLayout.WEST, contentPane);
 85. layout.putConstraint(SpringLayout.EAST, sizeLabel,
 86. labelsEast, SpringLayout.WEST, contentPane);
 88. layout.putConstraint(SpringLayout.NORTH, faceLabel,
 89. strut, SpringLayout.NORTH, contentPane);
 90. layout.putConstraint(SpringLayout.NORTH, face,
 91. strut, SpringLayout.NORTH, contentPane);
 93. Spring secondRowNorth = Spring.sum(strut,
 94. Spring.max(
 95. layout.getConstraint(
 96. SpringLayout.SOUTH, faceLabel),
 97. layout.getConstraint(
 98. SpringLayout.SOUTH, face)));
100. layout.putConstraint(SpringLayout.NORTH, sizeLabel,
101. secondRowNorth, SpringLayout.NORTH, contentPane);
102. layout.putConstraint(SpringLayout.NORTH, size,
103. secondRowNorth, SpringLayout.NORTH, contentPane);
105. layout.putConstraint(SpringLayout.WEST, face,
106. strut, SpringLayout.EAST, faceLabel);
107. layout.putConstraint(SpringLayout.WEST, size,
108. strut, SpringLayout.EAST, sizeLabel);
110. layout.putConstraint(SpringLayout.WEST, bold,
111. strut, SpringLayout.WEST, contentPane);
112. layout.putConstraint(SpringLayout.WEST, italic,
113. strut, SpringLayout.WEST, contentPane);
115. Spring s = Spring.constant(10, 10000, 10000);
117. Spring thirdRowNorth = Spring.sum(s,
118. Spring.max(
119. layout.getConstraint(
120. SpringLayout.SOUTH, sizeLabel),
121. layout.getConstraint(
122. SpringLayout.SOUTH, size)));
124. layout.putConstraint(SpringLayout.NORTH, bold,
125. thirdRowNorth, SpringLayout.NORTH, contentPane);
127. layout.putConstraint(SpringLayout.NORTH, italic, s,
128. SpringLayout.SOUTH, bold);
130. layout.putConstraint(SpringLayout.SOUTH, contentPane, s,
131. SpringLayout.SOUTH, italic);
133. Spring secondColumnWest = Spring.sum(strut,
134. Spring.max(
135. layout.getConstraint(
136. SpringLayout.EAST, face),
137. layout.getConstraint(
138. SpringLayout.EAST, size)));
140. layout.putConstraint(SpringLayout.WEST, sample,
141. secondColumnWest, SpringLayout.WEST, contentPane);
143. layout.putConstraint(SpringLayout.SOUTH, sample,
144. Spring.minus(strut), SpringLayout.SOUTH, contentPane);
145. layout.putConstraint(SpringLayout.NORTH, sample,
146. strut, SpringLayout.NORTH, contentPane);
148. layout.putConstraint(SpringLayout.EAST, contentPane,
149. strut, SpringLayout.EAST, sample);
150. }
152. public static final int DEFAULT_WIDTH = 300;
153. public static final int DEFAULT_HEIGHT = 200;
155. private JComboBox face;
156. private JComboBox size;
157. private JCheckBox bold;
158. private JCheckBox italic;
159. private JTextArea sample;
161. /**
162. An action listener that changes the font of the
163. sample text.
164. */
165. private class FontAction implements ActionListener
166. {
167. public void actionPerformed(ActionEvent event)
168. {
169. String fontFace = (String)face.getSelectedItem();
170. int fontStyle = (bold.isSelected() ? Font.BOLD : 0)
171. + (italic.isSelected() ? Font.ITALIC : 0);
172. int fontSize = Integer.parseInt(
173. (String)size.getSelectedItem());
174. Font font = new Font(fontFace, fontStyle, fontSize);
175. sample.setFont(font);
176. sample.repaint();
177. }
178. }
179. }

javax.swing.SpringLayout 1.4

Java graphics api_icon.gif
  • SpringLayout.Constraints getConstraints(Component c)

    Gets the constraints of the given component.



    One of the components or the container managed by this layout manager

  • void putConstraint(String endSide, Component end, Spring s, String startSide, Component start)
  • void putConstraint(String endSide, Component end, int pad, String startSide, Component start)

    Set the given side of the end component to a spring that is obtained by adding the spring s, or a strut with size pad, to the spring that reaches from the left end of the container to the given side of the start container.


    endSide, startSide




    the component to which a spring is added



    one of the summands of the added spring



    the size of the strut summand



    the component to which the other summand spring reaches

javax.swing.SpringLayout.Constraints 1.4

Java graphics api_icon.gif
  • Spring getX()
  • Spring getY()

    These methods return the spring reaching from the start of the container to the west or north end of the constrained component.

  • Spring getWidth()
  • Spring getHeight()

    These methods return the spring spanning the width or height of the constrained component.

  • Spring getConstraint(String side)
  • void setConstraint(String edge, Spring s)

    These methods get or set a spring reaching from the start of the container to the given side of the constrained component.



    One of the constants WEST, EAST, NORTH, or SOUTH of the SpringLayout class



    the spring to set

javax.swing.Spring 1.4

Java graphics api_icon.gif
  • static Spring constant(int preferred)

    Constructs a strut with the given preferred size. The minimum and maximum sizes are set to the preferred size.

  • static Spring constant(int minimum, int preferred, int maximum)

    Constructs a spring with the given minimum, preferred, and maximum sizes.

  • static Spring sum(Spring s, Spring t)

    Returns the spring sum of s and t.

  • static Spring max(Spring s, Spring t)

    Returns the spring maximum of s and t.

  • static Spring minus(Spring s)

    Returns the opposite of the spring s.

  • int getMinimumValue()
  • int getPreferredValue()
  • int getMaximumValue()

    These methods return the minimum, preferred, and maximum value of this spring.

  • int getValue()
  • void setValue(int newValue)

    These methods get and set the spring value. When setting the value of a compound spring, the values of the components are set as well.

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 apps, 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.

    JButton ok = new JButton("Ok");
    ok.setBounds(10, 10, 30, 15);

java.awt.Component 1.0

Java graphics api_icon.gif
  • void setBounds(int x, int y, int width, int height)

    moves and resizes a component.


    x, y

    The new top-left corner of the component


    width, height

    The new size of the component

Custom Layout Managers

In principle, it is possible to design your own LayoutManager class that manages components in a special way. For example, you could arrange all components in a container to form a circle. This will almost always be a major effort and a real time sink, but as Screenshot-44 shows, the results can be quite dramatic.

Screenshot-44. Circle layout

Java graphics 09fig44.gif

If you do feel you can't live without your own layout manager, here is what you do. Your own layout manager must implement the LayoutManager interface. You need to override the following five methods.

void addLayoutComponent(String s, Component c);
void removeLayoutComponent(Component c);
Dimension preferredLayoutSize(Container parent);
Dimension minimumLayoutSize(Container parent);
void layoutContainer(Container parent);

The first two functions 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 functions compute the space required for the minimum and the preferred layout of the components. These are usually the same quantity. The fifth function does the actual work and invokes setBounds on all components.

Java graphics notes_icon.gif

The AWT has a second interface called LayoutManager2 with 10 methods to implement rather than 5. The main point of the LayoutManager2 interface is to allow the user to use the add method with constraints. For example, the BorderLayout and GridBagLayout implement the LayoutManager2 interface.

Example 9-17 is a simple implementation of the CircleLayout manager, which, amazingly and uselessly enough, lays out the components along a circle inside the parent.
Example 9-17
 1. import java.awt.*;
 2. import java.awt.event.*;
 3. import javax.swing.*;
 5. public class CircleLayoutTest
 6. {
 7. public static void main(String[] args)
 8. {
 9. CircleLayoutFrame frame = new CircleLayoutFrame();
 10. frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 11. frame.pack();
 13. }
 14. }
 16. /**
 17. A frame that shows buttons arranged along a circle.
 18. */
 19. class CircleLayoutFrame extends JFrame
 20. {
 21. public CircleLayoutFrame()
 22. {
 23. setTitle("CircleLayoutTest");
 25. Container contentPane = getContentPane();
 26. contentPane.setLayout(new CircleLayout());
 27. contentPane.add(new JButton("Yellow"));
 28. contentPane.add(new JButton("Blue"));
 29. contentPane.add(new JButton("Red"));
 30. contentPane.add(new JButton("Green"));
 31. contentPane.add(new JButton("Orange"));
 32. contentPane.add(new JButton("Fuchsia"));
 33. contentPane.add(new JButton("Indigo"));
 34. }
 35. }
 37. /**
 38. A layout manager that lays out components along a circle.
 39. */
 40. class CircleLayout implements LayoutManager
 41. {
 42. public void addLayoutComponent(String name,
 43. Component comp)
 44. {}
 46. public void removeLayoutComponent(Component comp)
 47. {}
 49. public void setSizes(Container parent)
 50. {
 51. if (sizesSet) return;
 52. int n = parent.getComponentCount();
 54. preferredWidth = 0;
 55. preferredHeight = 0;
 56. minWidth = 0;
 57. minHeight = 0;
 58. maxComponentWidth = 0;
 59. maxComponentHeight = 0;
 61. // compute the maximum component widths and heights
 62. // and set the preferred size to the sum of
 63. // the component sizes.
 64. for (int i = 0; i < n; i++)
 65. {
 66. Component c = parent.getComponent(i);
 67. if (c.isVisible())
 68. {
 69. Dimension d = c.getPreferredSize();
 70. maxComponentWidth = Math.max(maxComponentWidth,
 71. d.width);
 72. maxComponentHeight = Math.max(maxComponentHeight,
 73. d.height);
 74. preferredWidth += d.width;
 75. preferredHeight += d.height;
 76. }
 77. }
 78. minWidth = preferredWidth / 2;
 79. minHeight = preferredHeight / 2;
 80. sizesSet = true;
 81. }
 83. public Dimension preferredLayoutSize(Container parent)
 84. {
 85. setSizes(parent);
 86. Insets insets = parent.getInsets();
 87. int width = preferredWidth + insets.left
 88. + insets.right;
 89. int height = preferredHeight +
 90. + insets.bottom;
 91. return new Dimension(width, height);
 92. }
 94. public Dimension minimumLayoutSize(Container parent)
 95. {
 96. setSizes(parent);
 97. Insets insets = parent.getInsets();
 98. int width = minWidth + insets.left + insets.right;
 99. int height = minHeight + + insets.bottom;
100. return new Dimension(width, height);
101. }
103. public void layoutContainer(Container parent)
104. {
105. setSizes(parent);
107. // compute center of the circle
109. Insets insets = parent.getInsets();
110. int containerWidth = parent.getSize().width
111. - insets.left - insets.right;
112. int containerHeight = parent.getSize().height
113. - - insets.bottom;
115. int xcenter = insets.left + containerWidth / 2;
116. int ycenter = + containerHeight / 2;
118. // compute radius of the circle
120. int xradius = (containerWidth - maxComponentWidth) / 2;
121. int yradius = (containerHeight - maxComponentHeight) / 2;
122. int radius = Math.min(xradius, yradius);
124. // lay out components along the circle
126. int n = parent.getComponentCount();
127. for (int i = 0; i < n; i++)
128. {
129. Component c = parent.getComponent(i);
130. if (c.isVisible())
131. {
132. double angle = 2 * Math.PI * i / n;
134. // center point of component
135. int x = xcenter + (int)(Math.cos(angle) * radius);
136. int y = ycenter + (int)(Math.sin(angle) * radius);
138. // move component so that its center is (x, y)
139. // and its size is its preferred size
140. Dimension d = c.getPreferredSize();
141. c.setBounds(x - d.width / 2, y - d.height / 2,
142. d.width, d.height);
143. }
144. }
145. }
147. private int minWidth = 0;
148. private int minHeight = 0;
149. private int preferredWidth = 0;
150. private int preferredHeight = 0;
151. private boolean sizesSet = false;
152. private int maxComponentWidth = 0;
153. private int maxComponentHeight = 0;
154. }

java.awt.LayoutManager 1.0

Java graphics api_icon.gif
  • void addLayoutComponent(String name, Component comp)

    adds a component to the layout.



    An identifier for the component placement



    The component to be added

  • void removeLayoutComponent(Component comp)

    removes a component from the layout.



    The component to be removed

  • Dimension preferredLayoutSize(Container parent)

    returns the preferred size dimensions for the container under this layout.



    The container whose components are being laid out

  • Dimension minimumLayoutSize(Container parent)

    returns the minimum size dimensions for the container under this layout.



    The container whose components are being laid out

  • void layoutContainer(Container parent)

    lays out the components in a container.



    The container whose components are being laid out

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 there are plenty of users who 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 the Swing set handles traversal order. The Swing set attempts to traverse your components in a reasonable way, first left-to-right and then top-to-bottom. For example, in the font dialog example, the components are traversed in the following order:

  1. Face combo box

  2. Sample text area (Hit CTRL+TAB to move to the next field; the TAB character is considered text input)

  3. Size combo box

  4. Bold check box

  5. Italic combo box

Screenshot-45. Geometric traversal order

Java graphics 09fig45.gif

Java graphics notes_icon.gif

In the old AWT, the traversal order was determined by the order in which you inserted components into a container. In Swing, the insertion order does not matter—only the layout of the components is considered.

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.
Java graphics notes_icon.gif

As of SDK 1.4, you call


to remove a component from the focus traversal. In older versions of the SDK, you had to override the isFocusTraversable method, but that method is now deprecated.

In summary, there are two standard traversal policies in SDK 1.4.
  • Pure AWT apps 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 apps 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.
Java graphics notes_icon.gif

The "cycle" notion is a bit confusing. After reaching the last element in a child container, the focus does not go back to its first element, but instead to the container's successor. There is API support for true cycles, including keystrokes that move up and down in a cycle hierarchy. However, the standard traversal policy does not use hierarchical cycles. It flattens the cycle hierarchy into a linear (depth-first) traversal.

Java graphics notes_icon.gif

In SDK 1.3, you were able to change the default traversal order by calling the setNextFocusableComponent method of the JComponent class. That method is now deprecated. To change the traversal order, try grouping related components into panels so that they form cycles. If that doesn't work, you have to either install a comparator that sorts the components differently, or completely replace the traversal policy. Neither operation seems to be intended for the faint of heart—see the Sun API documentation for details.


Java ScreenShot