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.
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.
Inheritance hierarchy for the Component class
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();
or
Box b = Box.createVerticalBox();
Then, you add components in the usual way:
b.add(okButton); b.add(cancelButton);
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:
- It computes the maximum (!) height of the tallest component.
- It tries to grow all components vertically to that height.
- 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.
- The preferred width of each component is obtained. All preferred widths are added up.
- 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.
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:
textField.setMaximumSize(textField.getPreferredSize());
Fillers
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:
b.add(label); b.add(Box.createHorizontalStrut(10)); b.add(textField);
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:
b.add(button1); b.add(Box.createGlue()); b.add(button2);
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 BoxLayoutTest.java
1. import java.awt.*; 2. import java.awt.event.*; 3. import javax.swing.*; 4. 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); 11. frame.show(); 12. } 13. } 14. 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"); 23. setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); 24. 25. // construct the top horizontal box 26. 27. JLabel label1 = new JLabel("Name:"); 28. JTextField textField1 = new JTextField(10); 29. textField1.setMaximumSize(textField1.getPreferredSize()); 30. 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); 36. 37. // construct the middle horizontal box 38. 39. JLabel label2 = new JLabel("Password:"); 40. JTextField textField2 = new JTextField(10); 41. textField2.setMaximumSize(textField2.getPreferredSize()); 42. 43. 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); 49. 50. // construct the bottom horizontal box 51. 52. JButton button1 = new JButton("Ok"); 53. JButton button2 = new JButton("Cancel"); 54. 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); 60. 61. // add the three horizontal boxes inside a vertical box 62. 63. Box vbox = Box.createVerticalBox(); 64. vbox.add(hbox1); 65. vbox.add(hbox2); 66. vbox.add(Box.createGlue()); 67. vbox.add(hbox3); 68. 69. Container contentPane = getContentPane(); 70. contentPane.add(vbox, BorderLayout.CENTER); 71. } 72. 73. public static final int DEFAULT_WIDTH = 200; 74. public static final int DEFAULT_HEIGHT = 200; 75. }
Box layouts
javax.swing.Box 1.2

- 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

- 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.
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.
Font dialog box
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.
Dialog box grid used in design
To describe the layout to the grid bag manager, you must go through the following convoluted procedure.
- 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.
- Set this GridBagLayout object to be the layout manager for the component.
- Create an object of type GridBagConstraints. The GridBagConstraints object will specify how the components are laid out within the grid bag.
- 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(); panel.setLayout(layout); 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.
Padding
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.
- Step 2. Find a grid such that the small components are each contained in a cell and the larger components span multiple cells.
Example FontDialog.java
1. import java.awt.*; 2. import java.awt.event.*; 3. import javax.swing.*; 4. import javax.swing.event.*; 5. 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); 12. frame.show(); 13. } 14. } 15. 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"); 25. setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); 26. 27. Container contentPane = getContentPane(); 28. GridBagLayout layout = new GridBagLayout(); 29. contentPane.setLayout(layout); 30. 31. ActionListener listener = new FontAction(); 32. 33. // construct components 34. 35. JLabel faceLabel = new JLabel("Face: "); 36. 37. face = new JComboBox(new String[] 38. { 39. "Serif", "SansSerif", "Monospaced", 40. "Dialog", "DialogInput" 41. }); 42. 43. face.addActionListener(listener); 44. 45. JLabel sizeLabel = new JLabel("Size: "); 46. 47. size = new JComboBox(new String[] 48. { 49. "8", "10", "12", "15", "18", "24", "36", "48" 50. }); 51. 52. size.addActionListener(listener); 53. 54. bold = new JCheckBox("Bold"); 55. bold.addActionListener(listener); 56. 57. italic = new JCheckBox("Italic"); 58. italic.addActionListener(listener); 59. 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()); 66. 67. // add components to grid 68. 69. GridBagConstraints constraints = new GridBagConstraints(); 70. 71. constraints.fill = GridBagConstraints.NONE; 72. constraints.anchor = GridBagConstraints.EAST; 73. constraints.weightx = 0; 74. constraints.weighty = 0; 75. 76. add(faceLabel, constraints, 0, 0, 1, 1); 77. add(sizeLabel, constraints, 0, 1, 1, 1); 78. 79. constraints.fill = GridBagConstraints.HORIZONTAL; 80. constraints.weightx = 100; 81. 82. add(face, constraints, 1, 0, 1, 1); 83. add(size, constraints, 1, 1, 1, 1); 84. 85. constraints.weighty = 100; 86. constraints.fill = GridBagConstraints.NONE; 87. constraints.anchor = GridBagConstraints.CENTER; 88. 89. add(bold, constraints, 0, 2, 2, 1); 90. add(italic, constraints, 0, 3, 2, 1); 91. 92. constraints.fill = GridBagConstraints.BOTH; 93. add(sample, constraints, 2, 0, 1, 4); 94. } 95. 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. } 115. 116. public static final int DEFAULT_WIDTH = 300; 117. public static final int DEFAULT_HEIGHT = 200; 118. 119. private JComboBox face; 120. private JComboBox size; 121. private JCheckBox bold; 122. private JCheckBox italic; 123. private JTextArea sample; 124. 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

- 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.
A spring
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.)
Summing springs
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(); contentPane.setLayout(layout); contentPane.add(b1); contentPane.add(b2); contentPane.add(b3);
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).
Equally Spaced Buttons
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).
Springs and Struts
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).
Lining up Columns
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.
Horizontal springs attached to a component
You get these springs as follows:
Spring west = layout.getConstraints(component).getX(); Spring width = layout.getConstraints(component).getWidth(); Spring east = layout.getConstraint(SpringLayout.EAST, component);
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, component);
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.
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 | |||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
west | width | east - west | |||||||||||||||||||||||||||||||||||||||||||||||||
width | east | west + width | |||||||||||||||||||||||||||||||||||||||||||||||||
east | west | east - width![]() 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, Spring.max(layout.getConstraints(faceLabel).getWidth(), Spring.max(layout.getConstraints(sizeLabel).getWidth())); 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 SpringLayoutTest.java1. import java.awt.*; 2. import java.awt.event.*; 3. import javax.swing.*; 4. import javax.swing.event.*; 5. 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); 12. frame.show(); 13. } 14. } 15. 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"); 25. setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); 26. 27. Container contentPane = getContentPane(); 28. SpringLayout layout = new SpringLayout(); 29. contentPane.setLayout(layout); 30. 31. ActionListener listener = new FontAction(); 32. 33. // construct components 34. 35. JLabel faceLabel = new JLabel("Font Face: "); 36. 37. face = new JComboBox(new String[] 38. { 39. "Serif", "SansSerif", "Monospaced", 40. "Dialog", "DialogInput" 41. }); 42. 43. face.addActionListener(listener); 44. 45. JLabel sizeLabel = new JLabel("Size: "); 46. 47. size = new JComboBox(new String[] 48. { 49. "8", "10", "12", "15", "18", "24", "36", "48" 50. }); 51. 52. size.addActionListener(listener); 53. 54. bold = new JCheckBox("Bold"); 55. bold.addActionListener(listener); 56. 57. italic = new JCheckBox("Italic"); 58. italic.addActionListener(listener); 59. 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()); 66. 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); 74. 75. // add strings to lay out components 76. Spring strut = Spring.constant(10); 77. 78. Spring labelsEast = Spring.sum(strut, 79. Spring.max( 80. layout.getConstraints(faceLabel).getWidth(), 81. layout.getConstraints(sizeLabel).getWidth())); 82. 83. layout.putConstraint(SpringLayout.EAST, faceLabel, 84. labelsEast, SpringLayout.WEST, contentPane); 85. layout.putConstraint(SpringLayout.EAST, sizeLabel, 86. labelsEast, SpringLayout.WEST, contentPane); 87. 88. layout.putConstraint(SpringLayout.NORTH, faceLabel, 89. strut, SpringLayout.NORTH, contentPane); 90. layout.putConstraint(SpringLayout.NORTH, face, 91. strut, SpringLayout.NORTH, contentPane); 92. 93. Spring secondRowNorth = Spring.sum(strut, 94. Spring.max( 95. layout.getConstraint( 96. SpringLayout.SOUTH, faceLabel), 97. layout.getConstraint( 98. SpringLayout.SOUTH, face))); 99. 100. layout.putConstraint(SpringLayout.NORTH, sizeLabel, 101. secondRowNorth, SpringLayout.NORTH, contentPane); 102. layout.putConstraint(SpringLayout.NORTH, size, 103. secondRowNorth, SpringLayout.NORTH, contentPane); 104. 105. layout.putConstraint(SpringLayout.WEST, face, 106. strut, SpringLayout.EAST, faceLabel); 107. layout.putConstraint(SpringLayout.WEST, size, 108. strut, SpringLayout.EAST, sizeLabel); 109. 110. layout.putConstraint(SpringLayout.WEST, bold, 111. strut, SpringLayout.WEST, contentPane); 112. layout.putConstraint(SpringLayout.WEST, italic, 113. strut, SpringLayout.WEST, contentPane); 114. 115. Spring s = Spring.constant(10, 10000, 10000); 116. 117. Spring thirdRowNorth = Spring.sum(s, 118. Spring.max( 119. layout.getConstraint( 120. SpringLayout.SOUTH, sizeLabel), 121. layout.getConstraint( 122. SpringLayout.SOUTH, size))); 123. 124. layout.putConstraint(SpringLayout.NORTH, bold, 125. thirdRowNorth, SpringLayout.NORTH, contentPane); 126. 127. layout.putConstraint(SpringLayout.NORTH, italic, s, 128. SpringLayout.SOUTH, bold); 129. 130. layout.putConstraint(SpringLayout.SOUTH, contentPane, s, 131. SpringLayout.SOUTH, italic); 132. 133. Spring secondColumnWest = Spring.sum(strut, 134. Spring.max( 135. layout.getConstraint( 136. SpringLayout.EAST, face), 137. layout.getConstraint( 138. SpringLayout.EAST, size))); 139. 140. layout.putConstraint(SpringLayout.WEST, sample, 141. secondColumnWest, SpringLayout.WEST, contentPane); 142. 143. layout.putConstraint(SpringLayout.SOUTH, sample, 144. Spring.minus(strut), SpringLayout.SOUTH, contentPane); 145. layout.putConstraint(SpringLayout.NORTH, sample, 146. strut, SpringLayout.NORTH, contentPane); 147. 148. layout.putConstraint(SpringLayout.EAST, contentPane, 149. strut, SpringLayout.EAST, sample); 150. } 151. 152. public static final int DEFAULT_WIDTH = 300; 153. public static final int DEFAULT_HEIGHT = 200; 154. 155. private JComboBox face; 156. private JComboBox size; 157. private JCheckBox bold; 158. private JCheckBox italic; 159. private JTextArea sample; 160. 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![]()
|