Section 7.2
More about Layouts and Components


MANY OF THE CLASSES IN THE AWT represent visual elements of a graphical user interface, such as buttons, text-input boxes, and menus. All these classes, except for a few related to menus, are subclasses of the class java.awt.Component.

Component is an abstract class, so that you can only create objects belonging to its subclasses, not to Component itself. The subclasses that represent standard GUI elements are: Button, Checkbox, Choice, Label, List, Scrollbar, TextArea, and TextField. The Canvas class, which we've used in several examples, is also a subclass of Component. Objects of these classes have predefined behaviors. For the most part, all you have to do is add them to your program and they take care of themselves. When the user interacts with one of these components and some action is required, the component generates an event that your program can detect and react to. I will discuss the standard GUI components in the next section.

To appear on the screen, a component must be added to a container. A container in this context is a component that can contain other components. Containers are represented by the class java.awt.Container, which is a subclass of Component. The Container class, like Component, is an abstract class. Container has two direct subclasses, Window and Panel. A Window represents an independent top-level window that is not contained in any other component. Window is not really meant to be used directly. It has two subclasses: Frame, to represent ordinary windows that can have their own menu bars, and Dialog to represent dialog boxes that are used for limited interactions with the user. I will discuss the window classes in Section 7.

A Panel, on the other hand, is a container that does not exist independently. The Applet class is a subclass of Panel, and as you have seen, an applet does not exist on its own; it is must be displayed on a Web page or in some other window. Any panel must be contained inside something else, either a Window, another Panel, or -- in the case of an Applet -- a page in a Web browser.

(Illustration of nested components) The fact that panels can contain other panels means that you can have many levels of components containing other components, as shown in the illustration on the right. This leads to two questions: How are components added to a container? How are their sizes and positions controlled?

The sizes and positions of the components in a container are usually controlled by a layout manager. Different layout managers implement different ways of arranging components. There are several predefined layout manager classes in the AWT: FlowLayout, GridLayout, BorderLayout, CardLayout and GridBagLayout. It is also possible to define new layout managers, if none of these suit your purpose. Every container is assigned a default layout manager when it is first created. For Panels, including Applets, the default layout manager belongs to the class FlowLayout. For Windows, the default layout manager is a BorderLayout. You can change the layout manager of a container using its setLayout() method. It is possible to set the LayoutManager of a container to be null. This allows you to take complete charge of laying out the components in the container. I will discuss this option further in Section 4.

As for adding components to a container, that's easy. You just use one of the container's add() methods. There are several add() methods. Which one you should use depends on what type of LayoutManager is being used by the container, so I will discuss the appropriate add() methods as I go along.

I have often found it to be fairly difficult to get the exact layout that I want in my applets and windows. I will briefly discuss each of the layout manager classes here, but using them well will require practice and experimentation.


FlowLayout

A FlowLayout simply lines up its components without trying to be particularly neat about it. After laying out as many items as will fit in a row across the container, it will move on to the next row. The components in a given row can be either left-aligned, right-aligned, or centered, and there can be horizontal and vertical gaps between components. If the default constructor, "new FlowLayout()" is used, then the components on each row will be centered and the horizontal and vertical gaps will be five pixels. The constructor

FlowLayout(int align, int hgap, int vgap)

can be used to specify alternative alignment and gaps. The possible values of align are FlowLayout.LEFT, FlowLayout.RIGHT, and FlowLayout.CENTER. A nifty trick is to use a very large value of hgap. This forces the FlowLayout to put exactly one component in each row, since there won't be room on a single row for two components and the horizontal gap between them. The appropriate add() method for FlowLayouts has a single parameter of type Component, specifying the component to be added.

For example, suppose that we want an applet to contain one button, located in the upper right corner of the applet. The default layout manager for an applet is a FlowLayout that uses center alignment. It would center the button horizontally. We need to give the applet a new layout manager that uses right alignment, which will shove the button to the right edge of the applet. The following init() method will do this:

            public void init() {
               setLayout( new FlowLayout(FlowLayout.RIGHT, 5, 5) );
               add( new Button("Press me!") );
            }

Although FlowLayouts are useful in certain circumstances, I almost always set the layout manager of an applet to be a BorderLayout or a GridLayout. I use the FlowLayout mainly when I want to use a panel to hold a strip of controls along the top or bottom of an applet. The examples in Section 6.6 used panels in this way.


BorderLayout

(BorderLayout) A BorderLayout places one component in the center of a container. The central component is surrounded by up to four other components that border it to the "North", "South", "East", and "West", as shown in the diagram at the right. Each of the four bordering components is optional. The layout manager first allocates space to the bordering components. Any space that is left over goes to the center component.

If a container uses a BorderLayout, then components should be added to the container using a version of the add() method that has two parameters. The first parameter is the component that is being added to the container. The second parameter specifies where the component is to be placed. It must be one of the constants BorderLayout.CENTER, BorderLayout.NORTH, BorderLayout.SOUTH, BorderLayout.EAST, or BorderLayout.WEST. For example, the following code creates a panel with drawArea as its center component and with scroll bars to the right and below:

       Panel panel = new Panel();
       panel.setLayout(new BorderLayout());
               // To use BorderLayout with a Panel, you have
               //     to change the panel's layout manager; otherwise,
               //     a FlowLayout is used.
       panel.add(drawArea, BorderLayout.CENTER);
               // Assume drawArea already exists.
       panel.add(hScroll, BorderLayout.SOUTH);
               // Assume hScroll is a horizontal scroll bar
               //     component that already exists.
       panel.add(vScroll, BorderLayout.EAST);
               // Assume vScroll is a vertical scroll bar
               //     component that already exists.

Sometimes, you want to leave space between the components in a container. You can specify horizontal and vertical gaps in the constructor of a BorderLayout object. For example, if you say

panel.setLayout(new BorderLayout(5,7));

then the layout manager will insert horizontal gaps of 5 pixels between components and vertical gaps of 7 pixels between components. (The horizontal gap is inserted between the center and west components and between the center and east components; the vertical gap is inserted between the center and north components and between the center and south components.)


GridLayout

(2-by-3 grid) A GridLayout lays out components in a grid of equal sized rectangles. The illustration shows how the components would be arranged in a grid layout with 3 rows and 2 columns. If a container uses a GridLayout, the appropriate add method takes a single parameter of type Component (for example: add(myButton)). Components are added to the grid in the order shown; that is, each row is filled from left to right before going on the next row.

The constructor for a GridLayout with R rows and C columns takes the form "new GridLayout(R,C)". If you want to leave horizontal gaps of H pixels between columns and vertical gaps of V pixels between rows, use "new GridLayout(R,C,H,V)" instead.

When you use a GridLayout, it's probably good form to add just enough components to fill the grid. However, this is not required. In fact, as long as you specify a non-zero value for the number of rows, then the number of columns is essentially ignored. The system will use just as many columns as are necessary to hold all the components that you add to the container. If you want to depend on this behavior, you should probably specify zero as the number of columns. You can also specify the number of rows as zero. In that case, you must give a non-zero number of columns. The system will use the specified number of columns, with just as many rows as necessary to hold the components that are added to the container.

Horizontal grids, with a single row, and vertical grids, with a single column, are very common. For example, suppose that button1, button2, and button3 are buttons and that you'd like to display them in a horizontal row in a panel. If you use a horizontal grid for the panel, then the buttons will completely fill that panel and will all be the same size. The panel can be created as follows:

        Panel buttonBar = new Panel();
        buttonBar.setLayout(new GridLayout(1,3));
            // (Note:  The "3" here is pretty much ignored, and
            //  you could also say "new GridLayout(1,0)".
            //  To leave gaps between the buttons, you could use
            //  "new GridLayout(1,0,5,5)".)
        buttonBar.add(button1);
        buttonBar.add(button2);
        buttonBar.add(button3);

You might find this button bar to be more attractive than the ones in the examples in the Section 6.6, which used the default FlowLayout layout manager.



GridBagLayout

A GridBagLayout is similar to a GridLayout in that the container is broken down into rows and columns of rectangles. However, a GridBagLayout is much more sophisticated because the rows do not all have to be of the same height, the columns do not all have to be of the same width, and a component in the container can spread over several rows and several columns. There is a separate class, GridBagConstraints, that is used to specify the position of a component, the number of rows and columns that it occupies, and several additional properties of the component.

Using a GridBagLayout is rather complicated, and I have used it on exactly two occasions in my own Java programming career. I will not explain it here; if you are interested, you should consult a Java reference.


CardLayout

CardLayouts differ from other layout managers in that in a container that uses a CardLayout, only one of its components is visible at any given time. Think of the components as a set of "cards". Only one card is visible at a time, but you can flip from one card to another. Methods are provided in the CardLayout class for flipping to the first card, to the last card, and to the next card in the deck. A name can be specified for each card as it is added to the container, and there is a method in the CardLayout class for flipping directly to the card with a specified name.

Suppose, for example, that you want to create a Panel that can show any one of three Panels: panel1, panel2, and panel3. Assume that panel1, panel2, and panel3 have already been created:

         cardPanel = new Panel();
              // assume cardPanel is declared as an instance variable
              // so that it can be used in other methods
         cards = new CardLayout();
              // assume cards is declared as an instance variable
              // so that it can be used in other methods
         cardPanel.setLayout(cards);
         cardPanel.add(panel1, "First");
              // add panel1 with name "First"
         cardPanel.add(panel2, "Second");
              // add panel2 with name "Second"
         cardPanel.add(panel3, "Third");
              // add panel3 with name "Third"

Elsewhere in your program, you could show panel1 by saying

         cards.show(cardPanel, "First");

or

         cards.first(cardPanel);

Other methods that are available are cards.last(cardPanel), cards.next(cardPanel), and cards.previous(cardPanel). Note that each of these methods takes the container as a parameter. To use a CardLayout effectively, you'll need to have instance variables to record both the layout manager (cards in the example) and the container (cardPanel in the example). You need both of these objects in order to flip from one card to another.


Components in Applets

When you are writing an Applet , you should remember that applets are themselves containers. This means that they have add() methods that can be used to add components and a setLayout() method that can be used to replace the default layout manager. In general, the entire layout of an applet should be set up in its init() method. This will often involve constructing sub-panels and adding them to the applet.

One final point: With most layout managers, you can specify horizontal and vertical gaps between components. But what if you want gaps between the edges of a container and the components that it contains? For that, you have to override the getInsets() method of the container. For an applet or panel, the definition of this method usually has the form:

        public Insets getInsets() {
           return new Insets(top,left,bottom,right);
        }

where top, left, bottom, and right are integers that specify the number of pixels to be inserted as a gap along each edge. However, things get a little more complicated in the case of a container that already has non-zero insets -- most commonly a window that uses insets to leave space for the borders of the window and the menu bar.

In Section 7, I'll explain how to use components and insets in independent windows that belong to the classes Frame and Dialog.


An Example

To finish this section, here is an applet that demonstrates various layout managers:

Sorry, but your browser
doesn't support Java.

The applet itself uses a BorderLayout with vertical gaps of 3 pixels. These gaps show up in blue, which is the background color of the applet as a whole. The blue border around the edges comes from a getInsets() method in the applet. The Center component of the applet is a panel. This panel is set to use a CardLayout as its layout manager. The layout contains six cards. Each card is itself another panel that contains several buttons. Each card uses a different type of layout manager (several of which are extremely stupid choices for laying out buttons).

The North component of the applet is a Choice menu, which contains the names of the six panels in the card layout. The user can switch among the cards by selecting items from this menu. The South component of the applet is a Label that displays an appropriate message whenever the user clicks on a button or chooses an item from the Choice menu.

The source code for this applet is in the file LayoutDemo.java. It consists mainly of a long init() method that creates all the buttons, panels, and other components and lays out the applet.


[ Next Section | Previous Section | Chapter Index | Main Index ]