Sophisticated Layout Management

   


We have managed to lay out the user interface components of our sample applications so far by using only the border layout, flow layout, and grid layout. For more complex tasks, this is not going to be enough. In this section, we 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 applications deal with this exceedingly poorly. The buttons do not grow, and the larger font is simply crammed into the same space as before. The same problem can occur when the strings in an application are translated to a foreign language. For example, the German word for "Cancel" is "Abbrechen." If a button has been designed with just enough room for the string "Cancel", then the German version will look broken, with a clipped command string.

Why don't Windows buttons simply grow to accommodate the labels? Because the designer of the user interface gave no instructions in which direction they should grow. After the dragging and dropping and arranging, the dialog editor merely remembers the pixel position and size of each component. It does not remember why the components were arranged in this fashion.

The Java layout managers are a much better approach to component layout. With a layout manager, the layout comes with instructions about the relationships among the components. This was particularly important in the original AWT, which used native user interface elements. The size of a button or list box in Motif, Windows, and the Macintosh could vary widely, and an application or applet would not know a priori on which platform it would display its user interface. To some extent, that degree of variability has gone away with Swing. If your application forces a particular look and feel, such as the Metal look and feel, then it looks identical on all platforms. However, if you let users of your application choose their favorite look and feel, then you again need to rely on the flexibility of layout managers to arrange the components.

Of course, to achieve complex layouts, you will need to have more control over the layout than the border layout, flow layout, and grid layout give you. In this section, we 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 application will look.

TIP

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. Figure 9-33 shows the inheritance hierarchy for Component.

Figure 9-33. Inheritance hierarchy for the Component class


NOTE

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 specify component placement:

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

With the grid layout, you need to add components sequentially:

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

The grid layout is useful for arranging 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, the box layout 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, because each box is laid out independently, you cannot use box layouts to arrange neighboring components both horizontally and vertically.

JDK 1.4 saw yet another attempt to design a replacement for the grid bag layout the spring layout. We discuss the spring layout on page 440, and we leave it to you to decide whether it succeeds in its goal.

Swing also contains an overlay layout that lets you place components on top of each other. This layout manager is not generally useful, and we don't discuss it.

Finally, there is a card layout that was used in the original AWT to produce tabbed dialogs. Because 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 affords. 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(); 

You then 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:

  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 a call to 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 being allowed to 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.

TIP

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 with

 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 10 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 two buttons in a box as far apart 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-13 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 Figure 9-34).

Example 9-13. 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.setVisible(true); 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.       add(vbox, BorderLayout.CENTER); 70.    } 71. 72.    public static final int DEFAULT_WIDTH = 200; 73.    public static final int DEFAULT_HEIGHT = 200; 74. } 


 javax.swing.Box 1.2 

  • static Box createHorizontalBox()

  • static Box createVerticalBox()

    create a container that arranges its content 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.

Figure 9-34. Box layouts


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 tables are edited: you start out with a grid and then merge adjacent cells if need be.) The components need not fill the entire cell area, and you can specify their alignment within cells.

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.

NOTE

According to the JDK documentation of the BoxLayout class: "Nesting multiple panels with different combinations of horizontal and vertical [sic] gives an effect similar to GridBagLayout, without the complexity." However, as you can see from Figure 9-34, 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 should consider the GridBagLayout class.


Consider the font selection dialog of Figure 9-35. It consists of the following components:

  • Two combo boxes to specify the font face and size

  • Labels for these two combo boxes

  • Two checkboxes to select bold and italic

  • A text area for the sample string

Figure 9-35. Font dialog box


Now, chop up the dialog box into a grid of cells, as shown in Figure 9-36. (The rows and columns need not have equal size.) Each checkbox spans two columns, and the text area spans four rows.

Figure 9-36. Dialog box grid used in design


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.

For each component, create an object of type GridBagConstraints. Set field values of the GridBagConstraints object to specify how the components are laid out within the grid bag.

4.

Then (finally), add the component with the constraints by using the call:

 add(component, constraints); 

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

 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; panel.add(style, bold); 

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

The gridx, gridy, gridwidth, and gridheight Parameters

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 Figure 9-35, we set the weightx field of the labels to be 0. This allows the labels to remain a constant width when you resize the window. On the other hand, if you set the weights for all areas to 0, the container will huddle in the center of its allotted area rather than stretching to fill it.

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

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

The fill and anchor Parameters

If you don't want a component to stretch out and fill the entire area, you set the fill constraint. You have four possibilities for this parameter: the valid values are used in the forms 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 GridBagConstraints. Set the left, top, right and bottom values of the Insets object to the amount of space that you want to have around the component. This is called the external padding.

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

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

The AWT documentation recommends that instead of setting the gridx and gridy values to absolute positions, you set them to the constant 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.

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


A Helper Class to Tame the Grid Bag Constraints

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

  • Its name is short: GBC instead of GridBagConstraints

  • It extends GridBagConstraints, so you can use shorter names such as GBC.EAST for the constants.

  • Use a GBC object when adding a component, such as

     add(component, new GBC(1, 2)); 

  • There are two constructors to set the most common parameters: gridx and gridy, or gridx, gridy, gridwidth, and gridheight.

     add(component, new GBC(1, 2, 1, 4)); 

  • There are convenient setters for the fields that come in x/y pairs:

     add(component, new GBC(1, 2).setWeight(100, 100)); 

  • The setter methods return this, so you can chain them:

     add(component, new GBC(1, 2).setAnchor(GBC.EAST).setWeight(100, 100)); 

  • The setInsets methods construct the Insets object for you. To get one-pixel insets, simply call

     add(component, new GBC(1, 2).setAnchor(GBC.EAST).setInsets(1)); 

Example 9-14 shows the complete code for the font dialog example. Here is the code that adds the components to the grid bag:

 add(faceLabel, new GBC(0, 0).setAnchor(GBC.EAST)); add(face, new GBC(1, 0).setFill(GBC.HORIZONTAL).setWeight(100, 0).setInsets(1)); add(sizeLabel, new GBC(0, 1).setAnchor(GBC.EAST)); add(size, new GBC(1, 1).setFill(GBC.HORIZONTAL).setWeight(100, 0).setInsets(1)); add(bold, new GBC(0, 2, 2, 1).setAnchor(GBC.CENTER).setWeight(100, 100)); add(italic, new GBC(0, 3, 2, 1).setAnchor(GBC.CENTER).setWeight(100, 100)); add(sample, new GBC(2, 0, 1, 4).setFill(GBC.BOTH).setWeight(100, 100)); 

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

NOTE

The Sun tutorial at http://java.sun.com/docs/books/tutorial/uiswing/layout/gridbag.html suggests that you reuse the same GridBagConstraints object for all components. We find the resulting code hard to read and error prone. For example, look at the demo at http://java.sun.com/docs/books/tutorial/uiswing/events/containerlistener.html. Was it really intended that the buttons are stretched horizontally, or did the programmer just forget to turn off the BOTH setting for the fill constraint?


Example 9-14. 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.setVisible(true); 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.       GridBagLayout layout = new GridBagLayout(); 28.       setLayout(layout); 29. 30.       ActionListener listener = new FontAction(); 31. 32.       // construct components 33. 34.       JLabel faceLabel = new JLabel("Face: "); 35. 36.       face = new JComboBox(new String[] 37.          { 38.             "Serif", "SansSerif", "Monospaced", 39.             "Dialog", "DialogInput" 40.          }); 41. 42.       face.addActionListener(listener); 43. 44.       JLabel sizeLabel = new JLabel("Size: "); 45. 46.       size = new JComboBox(new String[] 47.          { 48.             "8", "10", "12", "15", "18", "24", "36", "48" 49.          }); 50. 51.       size.addActionListener(listener); 52. 53.       bold = new JCheckBox("Bold"); 54.       bold.addActionListener(listener); 55. 56.       italic = new JCheckBox("Italic"); 57.       italic.addActionListener(listener); 58. 59.       sample = new JTextArea(); 60.       sample.setText("The quick brown fox jumps over the lazy dog"); 61.       sample.setEditable(false); 62.       sample.setLineWrap(true); 63.       sample.setBorder(BorderFactory.createEtchedBorder()); 64. 65.       // add components to grid, using GBC convenience class 66. 67.       add(faceLabel, new GBC(0, 0).setAnchor(GBC.EAST)); 68.       add(face, new GBC(1, 0).setFill(GBC.HORIZONTAL).setWeight(100, 0).setInsets(1)); 69.       add(sizeLabel, new GBC(0, 1).setAnchor(GBC.EAST)); 70.       add(size, new GBC(1, 1).setFill(GBC.HORIZONTAL).setWeight(100, 0).setInsets(1)); 71.       add(bold, new GBC(0, 2, 2, 1).setAnchor(GBC.CENTER).setWeight(100, 100)); 72.       add(italic, new GBC(0, 3, 2, 1).setAnchor(GBC.CENTER).setWeight(100, 100)); 73.       add(sample, new GBC(2, 0, 1, 4).setFill(GBC.BOTH).setWeight(100, 100)); 74.    } 75. 76.    public static final int DEFAULT_WIDTH = 300; 77.    public static final int DEFAULT_HEIGHT = 200; 78. 79.    private JComboBox face; 80.    private JComboBox size; 81.    private JCheckBox bold; 82.    private JCheckBox italic; 83.    private JTextArea sample; 84. 85.    /** 86.       An action listener that changes the font of the 87.       sample text. 88.    */ 89.    private class FontAction implements ActionListener 90.    { 91.       public void actionPerformed(ActionEvent event) 92.       { 93.          String fontFace = (String) face.getSelectedItem(); 94.          int fontStyle = (bold.isSelected() ? Font.BOLD : 0) 95.             + (italic.isSelected() ? Font.ITALIC : 0); 96.          int fontSize = Integer.parseInt((String) size.getSelectedItem()); 97.          Font font = new Font(fontFace, fontStyle, fontSize); 98.          sample.setFont(font); 99.          sample.repaint(); 100.       } 101.    } 102. } 

Example 9-15 shows the code of the GBC helper class.

Example 9-15. GBC.java
   1. /*   2. GBC - A convenience class to tame the GridBagLayout   3.   4. Copyright (C) 2002 Cay S. Horstmann (http://horstmann.com)   5.   6. This program is free software; you can redistribute it and/or modify   7. it under the terms of the GNU General Public License as published by   8. the Free Software Foundation; either version 2 of the License, or   9. (at your option) any later version.  10.  11. This program is distributed in the hope that it will be useful,  12. but WITHOUT ANY WARRANTY; without even the implied warranty of  13. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  14. GNU General Public License for more details.  15.  16. You should have received a copy of the GNU General Public License  17. along with this program; if not, write to the Free Software  18. Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA  19. */  20.  21. import java.awt.*;  22.  23. /**  24.    This class simplifies the use of the GridBagConstraints  25.    class.  26. */  27. public class GBC extends GridBagConstraints  28. {  29.    /**  30.       Constructs a GBC with a given gridx and gridy position and  31.       all other grid bag constraint values set to the default.  32.       @param gridx the gridx position  33.       @param gridy the gridy position  34.    */  35.    public GBC(int gridx, int gridy)  36.    {  37.       this.gridx = gridx;  38.       this.gridy = gridy;  39.    }  40.  41.    /**  42.       Constructs a GBC with given gridx, gridy, gridwidth, gridheight  43.       and all other grid bag constraint values set to the default.  44.       @param gridx the gridx position  45.       @param gridy the gridy position  46.       @param gridwidth the cell span in x-direction  47.       @param gridheight the cell span in y-direction  48.    */  49.    public GBC(int gridx, int gridy, int gridwidth, int gridheight)  50.    {  51.       this.gridx = gridx;  52.       this.gridy = gridy;  53.       this.gridwidth = gridwidth;  54.       this.gridheight = gridheight;  55.    }  56.  57.    /**  58.       Sets the anchor.  59.       @param anchor the anchor value  60.       @return this object for further modification  61.    */  62.    public GBC setAnchor(int anchor)  63.    {  64.       this.anchor = anchor;  65.       return this;  66.    }  67.  68.    /**  69.       Sets the fill direction.  70.       @param fill the fill direction  71.       @return this object for further modification  72.    */  73.    public GBC setFill(int fill)  74.    {  75.       this.fill = fill;  76.       return this;  77.    }  78.  79.    /**  80.       Sets the cell weights.  81.       @param weightx the cell weight in x-direction  82.       @param weighty the cell weight in y-direction  83.       @return this object for further modification  84.    */  85.    public GBC setWeight(double weightx, double weighty)  86.    {  87.       this.weightx = weightx;  88.       this.weighty = weighty;  89.       return this;  90.    }  91.  92.    /**  93.       Sets the insets of this cell.  94.       @param distance the spacing to use in all directions  95.       @return this object for further modification  96.    */  97.    public GBC setInsets(int distance)  98.    {  99.       this.insets = new Insets(distance, distance, distance, distance); 100.       return this; 101.    } 102. 103.    /** 104.       Sets the insets of this cell. 105.       @param top the spacing to use on top 106.       @param left the spacing to use to the left 107.       @param bottom the spacing to use on the bottom 108.       @param right the spacing to use to the right 109.       @return this object for further modification 110.    */ 111.    public GBC setInsets(int top, int left, int bottom, int right) 112.    { 113.       this.insets = new Insets(top, left, bottom, right); 114.       return this; 115.    } 116. 117.    /** 118.       Sets the internal padding 119.       @param ipadx the internal padding in x-direction 120.       @param ipady the internal padding in y-direction 121.       @return this object for further modification 122.    */ 123.    public GBC setIpad(int ipadx, int ipady) 124.    { 125.       this.ipadx = ipadx; 126.       this.ipady = ipady; 127.       return this; 128.    } 129. } 

TIP

Some GUI builders have tools for specifying the constraints visually see Figure 9-37 for the configuration dialog in NetBeans.

Figure 9-37. Specifying grid bag constraints in NetBeans




 java.awt.GridBagConstraints 1.0 

  • int gridx, gridy

    specifies the starting column and row of the cell. The default is 0.

  • int gridwidth, gridheight

    specifies the column and row extent of the cell. The default is 1.

  • double weightx, weighty

    specifies the capacity of the cell to grow. The default is 0.

  • int anchor

    indicates the alignment of the component inside the cell. You can choose between absolute positions

    NORTHWEST

    NORTH

    NORTHEAST

    WEST

    CENTER

    EAST

    SOUTHWEST

    SOUTH

    SOUTHEAST


    or their orientation-independent counterparts

    FIRST_LINE_START

    LINE_START

    FIRST_LINE_END

    PAGE_START

    CENTER

    PAGE_END

    LAST_LINE_START

    LINE_END

    LAST_LINE_END


    Use the latter if your application may be localized for right-to-left or top-to-bottom text. The default is CENTER.

  • int fill

    specifies the fill behavior of the component inside the cell, one of NONE, BOTH, HORIZONTAL, or VERTICAL. The default is NONE.

  • int ipadx, ipady

    specifies the "internal" padding around the component. The default is 0.

  • Insets insets

    specifies the "external" padding along the cell boundaries. The default is no padding.

  • GridBagConstraints(int gridx, int gridy, int gridwidth, int gridheight, double weightx, double weighty, int anchor, int fill, Insets insets, int ipadx, int ipady) 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 because 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, JDK 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 Figure 9-38, each spring has

  • A minimum value

  • A preferred value

  • A maximum value

  • An actual value

Figure 9-38. A spring


When the spring is compressed or expanded in the layout phase, the actual value is fixed; it falls between the minimum and maximum value and is as close to the preferred value as the other springs allow. 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 Figure 9-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 spring value is set so that the strain of each spring equals the strain of the sum. Thus, the values of the individual springs 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.)

Figure 9-39. Summing springs


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

 JButton b1 = new JButton("Yellow"); JButton b2 = new JButton("Blue"); JButton b3 = new JButton("Red"); 

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

 SpringLayout layout = new SpringLayout(); panel.setLayout(layout); panel.add(b1); panel.add(b2); panel.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, panel); 

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, panel, s, SpringLayout.EAST, b3); 

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

Figure 9-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 Figure 9-41).

Figure 9-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 Figure 9-42).

Figure 9-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 sides of 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 because the labelsEast spring starts there.

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

Now the two combo boxes line up because they are held by the same spring.

However, there is a slight blemish. We'd prefer the labels to be 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. Figure 9-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.

Figure 9-43. 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.

CAUTION

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


NOTE

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, panel); layout.putConstraint(SpringLayout.EAST, sizeLabel, labelsEast, SpringLayout.WEST, panel); 

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. We hope to someday see tools 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. SpringLayoutTest.java

   1. 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.setVisible(true);  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.       JPanel panel = new JPanel();  28.       SpringLayout layout = new SpringLayout();  29.       panel.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("The quick brown fox jumps over the lazy dog");  62.       sample.setEditable(false);  63.       sample.setLineWrap(true);  64.       sample.setBorder(BorderFactory.createEtchedBorder());  65.  66.       panel.add(faceLabel);  67.       panel.add(sizeLabel);  68.       panel.add(face);  69.       panel.add(size);  70.       panel.add(bold);  71.       panel.add(italic);  72.       panel.add(sample);  73.  74.       // add springs to lay out components  75.       Spring strut = Spring.constant(10);  76.  77.       Spring labelsEast = Spring.sum(strut,  78.          Spring.max(  79.             layout.getConstraints(faceLabel).getWidth(),  80.             layout.getConstraints(sizeLabel).getWidth()));  81.  82.       layout.putConstraint(SpringLayout.EAST, faceLabel, labelsEast, SpringLayout .WEST, panel);  83.       layout.putConstraint(SpringLayout.EAST, sizeLabel, labelsEast, SpringLayout .WEST, panel);  84.  85.       layout.putConstraint(SpringLayout.NORTH, faceLabel, strut,  SpringLayout.NORTH,  panel);  86.       layout.putConstraint(SpringLayout.NORTH, face, strut, SpringLayout.NORTH, panel);  87.  88.       Spring secondRowNorth = Spring.sum(strut,  89.          Spring.max(  90.             layout.getConstraint(SpringLayout.SOUTH, faceLabel),  91.             layout.getConstraint(SpringLayout.SOUTH, face)));  92.  93.       layout.putConstraint(SpringLayout.NORTH, sizeLabel, secondRowNorth,  SpringLayout.NORTH,  94.          panel);  95.       layout.putConstraint(SpringLayout.NORTH, size, secondRowNorth, SpringLayout .NORTH, panel);  96.  97.       layout.putConstraint(SpringLayout.WEST, face, strut, SpringLayout.EAST, faceLabel);  98.       layout.putConstraint(SpringLayout.WEST, size, strut, SpringLayout.EAST, sizeLabel);  99. 100.       layout.putConstraint(SpringLayout.WEST, bold, strut, SpringLayout.WEST, panel); 101.       layout.putConstraint(SpringLayout.WEST, italic, strut, SpringLayout.WEST, panel); 102. 103.       Spring s = Spring.constant(10, 10000, 10000); 104. 105.       Spring thirdRowNorth = Spring.sum(s, 106.          Spring.max( 107.             layout.getConstraint(SpringLayout.SOUTH, sizeLabel), 108.             layout.getConstraint(SpringLayout.SOUTH, size))); 109. 110.       layout.putConstraint(SpringLayout.NORTH, bold, thirdRowNorth, SpringLayout .NORTH, panel); 111.       layout.putConstraint(SpringLayout.NORTH, italic, s, SpringLayout.SOUTH, bold); 112.       layout.putConstraint(SpringLayout.SOUTH, panel, s, SpringLayout.SOUTH, italic); 113. 114.       Spring secondColumnWest = Spring.sum(strut, 115.          Spring.max( 116.             layout.getConstraint(SpringLayout.EAST, face), 117.             layout.getConstraint(SpringLayout.EAST, size))); 118. 119.       layout.putConstraint(SpringLayout.WEST, sample, secondColumnWest, SpringLayout .WEST, panel); 120.       layout.putConstraint(SpringLayout.SOUTH, sample, Spring.minus(strut),  SpringLayout.SOUTH, 121.          panel); 122.       layout.putConstraint(SpringLayout.NORTH, sample, strut, SpringLayout.NORTH, panel); 123.       layout.putConstraint(SpringLayout.EAST, panel, strut, SpringLayout.EAST, sample); 124. 125.       add(panel); 126.    } 127. 128.    public static final int DEFAULT_WIDTH = 400; 129.    public static final int DEFAULT_HEIGHT = 200; 130. 131.    private JComboBox face; 132.    private JComboBox size; 133.    private JCheckBox bold; 134.    private JCheckBox italic; 135.    private JTextArea sample; 136. 137.    /** 138.       An action listener that changes the font of the 139.       sample text. 140.    */ 141.    private class FontAction implements ActionListener 142.    { 143.       public void actionPerformed(ActionEvent event) 144.       { 145.          String fontFace = (String) face.getSelectedItem(); 146.          int fontStyle = (bold.isSelected() ? Font.BOLD : 0) 147.             + (italic.isSelected() ? Font.ITALIC : 0); 148.          int fontSize = Integer.parseInt((String) size.getSelectedItem()); 149.          Font font = new Font(fontFace, fontStyle, fontSize); 150.          sample.setFont(font); 151.          sample.repaint(); 152.       } 153.    } 154. } 


 javax.swing.SpringLayout 1.4 

  • SpringLayout.Constraints getConstraints(Component c)

    gets the constraints of the given component.

    Parameters:

    c

    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.

    Parameters:

    endSide, startSide

    WEST, EAST, NORTH, or SOUTH

     

    end

    The component to which a spring is added

     

    s

    One of the summands of the added spring

     

    pad

    The size of the strut summand

     

    start

    The component to which the other summand spring reaches



 javax.swing.SpringLayout.Constraints 1.4 

  • Constraints(Component c) 5.0

    constructs a Constraints object whose positions, width, and springs match the given component.

  • Spring getX()

  • Spring getY()

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

  • Spring getWidth()

  • Spring getHeight()

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

  • Spring getConstraint(String side)

  • void setConstraint(String edge, Spring s)

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

    Parameters:

    side

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

     

    s

    The spring to set



 javax.swing.Spring 1.4 

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

  • static Spring scale(Spring s, float factor) 5.0

    scales the minimum, preferred, and maximum sizes of s by the given factor. If the factor is negative, the scaled opposite of s is returned.

  • static Spring width(Component c) 5.0

  • static Spring height(Component c) 5.0

    return a spring whose minimum, preferred, and maximum sizes equal the minimum, preferred, and maximum width or height of the given component.

  • int getMinimumValue()

  • int getPreferredValue()

  • int getMaximumValue()

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

  • int getValue()

  • void setValue(int newValue)

    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 applications, but there is nothing wrong with using it for a quick prototype.

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

  1. Set the layout manager to null.

  2. Add the component you want to the container.

  3. Then specify the position and size that you want.

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


 java.awt.Component 1.0 

  • void setBounds(int x, int y, int width, int height)

    moves and resizes a component.

    Parameters:

    x, y

    The new top-left corner of the component

     

    width, height

    The new size of the component


Custom Layout Managers

In principle, you can 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 Figure 9-44 shows, the results can be quite dramatic.

Figure 9-44. Circle layout


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.

NOTE

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. CircleLayoutTest.java
   1. import java.awt.*;   2. import java.awt.event.*;   3. import javax.swing.*;   4.   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();  12.       frame.setVisible(true);  13.    }  14. }  15.  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");  24.  25.       setLayout(new CircleLayout());  26.       add(new JButton("Yellow"));  27.       add(new JButton("Blue"));  28.       add(new JButton("Red"));  29.       add(new JButton("Green"));  30.       add(new JButton("Orange"));  31.       add(new JButton("Fuchsia"));  32.       add(new JButton("Indigo"));  33.    }  34. }  35.  36. /**  37.    A layout manager that lays out components along a circle.  38. */  39. class CircleLayout implements LayoutManager  40. {  41.    public void addLayoutComponent(String name, Component comp)  42.    {}  43.  44.    public void removeLayoutComponent(Component comp)  45.    {}  46.  47.    public void setSizes(Container parent)  48.    {  49.       if (sizesSet) return;  50.       int n = parent.getComponentCount();  51.  52.       preferredWidth = 0;  53.       preferredHeight = 0;  54.       minWidth = 0;  55.       minHeight = 0;  56.       maxComponentWidth = 0;  57.       maxComponentHeight = 0;  58.  59.       // compute the maximum component widths and heights  60.       // and set the preferred size to the sum of the component sizes.  61.       for (int i = 0; i < n; i++)  62.       {  63.          Component c = parent.getComponent(i);  64.          if (c.isVisible())  65.          {  66.             Dimension d = c.getPreferredSize();  67.             maxComponentWidth = Math.max(maxComponentWidth, d.width);  68.             maxComponentHeight = Math.max(maxComponentHeight, d.height);  69.             preferredWidth += d.width;  70.             preferredHeight += d.height;  71.          }  72.       }  73.       minWidth = preferredWidth / 2;  74.       minHeight = preferredHeight / 2;  75.       sizesSet = true;  76.    }  77.  78.    public Dimension preferredLayoutSize(Container parent)  79.    {  80.       setSizes(parent);  81.       Insets insets = parent.getInsets();  82.       int width = preferredWidth + insets.left + insets.right;  83.       int height = preferredHeight + insets.top + insets.bottom;  84.       return new Dimension(width, height);  85.    }  86.  87.    public Dimension minimumLayoutSize(Container parent)  88.    { 89.       setSizes(parent); 90.       Insets insets = parent.getInsets(); 91.       int width = minWidth + insets.left + insets.right; 92.       int height = minHeight + insets.top + insets.bottom; 93.       return new Dimension(width, height); 94.    } 95. 96.    public void layoutContainer(Container parent) 97.    { 98.       setSizes(parent); 99. 100.       // compute center of the circle 101. 102.       Insets insets = parent.getInsets(); 103.       int containerWidth = parent.getSize().width - insets.left - insets.right; 104.       int containerHeight = parent.getSize().height - insets.top - insets.bottom; 105. 106.       int xcenter = insets.left + containerWidth / 2; 107.       int ycenter = insets.top + containerHeight / 2; 108. 109.       // compute radius of the circle 110. 111.       int xradius = (containerWidth - maxComponentWidth) / 2; 112.       int yradius = (containerHeight - maxComponentHeight) / 2; 113.       int radius = Math.min(xradius, yradius); 114. 115.       // lay out components along the circle 116. 117.       int n = parent.getComponentCount(); 118.       for (int i = 0; i < n; i++) 119.       { 120.          Component c = parent.getComponent(i); 121.          if (c.isVisible()) 122.          { 123.             double angle = 2 * Math.PI * i / n; 124. 125.             // center point of component 126.             int x = xcenter + (int)(Math.cos(angle) * radius); 127.             int y = ycenter + (int)(Math.sin(angle) * radius); 128. 129.             // move component so that its center is (x, y) 130.             // and its size is its preferred size 131.             Dimension d = c.getPreferredSize(); 132.             c.setBounds(x - d.width / 2, y - d.height / 2, d.width, d.height); 133.          } 134.       } 135.    } 136. 137.    private int minWidth = 0; 138.    private int minHeight = 0; 139.    private int preferredWidth = 0; 140.    private int preferredHeight = 0; 141.    private boolean sizesSet = false; 142.    private int maxComponentWidth = 0; 143.    private int maxComponentHeight = 0; 144. } 


 java.awt.LayoutManager 1.0 

  • void addLayoutComponent(String name, Component comp)

    adds a component to the layout.

    Parameters:

    name

    An identifier for the component placement

     

    comp

    The component to be added


  • void removeLayoutComponent(Component comp)

    removes a component from the layout.

    Parameters:

    comp

    The component to be removed


  • Dimension preferredLayoutSize(Container parent)

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

    Parameters:

    parent

    The container whose components are being laid out


  • Dimension minimumLayoutSize(Container parent)

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

    Parameters:

    parent

    The container whose components are being laid out


  • void layoutContainer(Container parent)

    lays out the components in a container.

    Parameters:

    parent

    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 plenty of users do. Among them are the mouse haters and those who cannot use a mouse, perhaps because of a handicap or because they are navigating the user interface by voice. For that reason, you need to know how Swing handles traversal order.

The traversal order is straightforward, first left to right and then top to bottom. For example, in the font dialog example, the components are traversed in the following order (see Figure 9-45):

  1. Face combo box

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

  3. Size combo box

  4. Bold checkbox

  5. Italic checkbox

Figure 9-45. Geometric traversal order


NOTE

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.

NOTE

As of JDK 1.4, you call

 component.setFocusable(false); 

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


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

  • Pure AWT applications use the DefaultFocusTraversalPolicy. Components are included in the focus traversal if they are visible, displayable, enabled, and focusable, and if their native peers are focusable. The components are traversed in the order in which they were inserted in the container.

  • Swing applications use the LayoutFocusTraversalPolicy. Components are included in the focus traversal if they are visible, displayable, enabled, and focusable. The components are traversed in geometric order: left to right, then top to bottom. However, a container introduces a new "cycle" its components are traversed first before the successor of the container gains focus.

NOTE

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. The API supports 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.


NOTE

In JDK 1.3, you could 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 intended for the faint of heart see the Sun API documentation for details.



       
    top



    Core Java 2 Volume I - Fundamentals
    Core Java(TM) 2, Volume I--Fundamentals (7th Edition) (Core Series) (Core Series)
    ISBN: 0131482025
    EAN: 2147483647
    Year: 2003
    Pages: 132

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net