9. Layout Managers
Objectives

This chapter covers the following Java certification exam objective:

  • Write code using component, container, and layout manager classes of the java.awt package to present a GUI with specified appearance and resize behavior, and distinguish the responsibilities of layout managers from those of containers.

Java's layout manager approach to Graphical User Interfaces is a novelty. Many GUI systems encourage GUI programmers to think in terms of precise specification of the size and location of interface components. Java changes all that. The Abstract Windowing Toolkit (AWT) provides a handful of layout managers, each of which implements its own layout policy. In Java, you create a GUI by choosing one or more layout managers and letting them take care of the details.

When you started working with layout managers, you probably had two impressions:

  • You no longer bore the burden of specifying the exact position and dimensions of each component.
  • You no longer had the power to specify the exact position and dimensions of each component.

Some people enjoy working with layout managers, and others resent them.

They are here to stay, so the job at hand is to master this feature of the language. Acquiring this competence requires three things:

  • An understanding of why Java uses layout managers
  • An understanding of the layout policies of the more basic layout managers
  • Some practice

The next section explains why Java uses layout managers. Then, after some intervening theory about how layout managers work, the last three sections of this chapter describe Java's three simplest layout managers: Flow Layout, Grid Layout, and Border Layout. (A fourth layout manager, Grid Bag Layout, is more complex than these three by an order of magnitude). Grid Bag Layout is mentioned briefly in this chapter, then discussed in detail in the context of the developer's exam.

Why Java Uses Layout Managers

There are two reasons why Java's AWT uses layout managers. The first reason is a bit theoretical, and you may or may not find yourself convinced by it. The second reason is thoroughly practical.

The theory lies in the position that precise layout (that is, specification in pixels of each component's size and position) is a repetitious and often-performed task; therefore, according to the principles of object-oriented programming, layout functionality ought to be encapsulated into one or more classes to automate the task. Certainly the layout managers eliminate a lot of development tedium. Many programmers dislike the idea of layout managers at first, but come to appreciate them more and more as tedious chores are eliminated.

The practical reason for having layout managers stems from Java's platform independence. Java components borrow their behavior from the window system of the underlying hardware on which the Java Virtual Machine is running. Thus on a Macintosh, an AWT burton looks like any other Mac button; on a Motif platform, a Java button looks like any other Motif button, and so on. The problem here is that buttons and other components have different sizes when instantiated on different platforms.

For example, consider the button that is constructed by the following line of code:

Button b = new Button( "ok" );

On a Windows 95 machine, this button will be 32 pixels wide by 21 pixels high. On a Motif platform, the button will still be 32 pixels wide, but it will be 22 pixels high, even though it uses the same font. The difference seems small until you consider the effect such a difference would have on a column of many buttons. Other components can also vary in size from platform to platform. If Java encouraged precise pixel-level sizing and positioning, there would be a lot of Java GUIs that looked exquisite on their platform of origin, and terrible on other hosts.

There is no guarantee that fonts with identical names will truly be 100 percent identical from platform to platform; there could be minute differences. Therefore, Java cannot even guarantee that two strings drawn with the same text and font will display at the same size across platforms.

Similarly, there is no way to achieve size consistency among components, which have to deal with font differences and with decoration differences.

Java deals with this problem by delegating precision layout work to layout managers. The rest of this chapter investigates what layout managers are and explores the three most common managers.

Layout Manager Theory

There are five layout manager classes in the AWT toolkit. You might expect that there would be a common abstract superclass, called something like LayoutManager, from which these five layout managers would inherit common functionality.

In fact, there is a java.awt.LayoutManager, but it is an interface, not a class, because the layout managers are so different from one another that they have nothing in common except a handful of method names. (There is also a java.awt.LayoutManager2 interface, which the GridBag, Border, and Card layout managers implement. The certification exam does not cover the GridBag and Card layout managers, although these two are discussed in the developer's course.

Layout managers work in partnership with containers. In order to understand layout managers, it is important to understand what a container is and what happens when a component gets inserted into a container. The next two sections explore these topics; the information is not directly addressed by the certification exam, but some relevant theory at this point will make it much easier to understand the material that is required for the exam.

Containers and Components

Containers are Java components that can contain other components. There is a java.awt.Container class which, like java.awt.Button and java.awt.Choice, inherits from the java.awt.Component superclass. This inheritance relationship is shown in Figure 9.1.

FIGURE 9.1

Inheritance of java.awt.Container

The Container class was abstract, but now it isn't; its most commonly used concrete subclasses are Applet, Frame, and Panel, as shown in Figure 9.2. (Note that Applet is a subclass of Panel).

FIGURE 9.2

Common subclasses of java.awt.Container

Java GUIs reside in applets or in frames. For simple applets, you just put your components in your applet; for simple applications, you just put your components in your frame. (In both cases, you might wonder how the components end up where they do; layout managers are lurking in the background, taking care of details).

For more complicated GUIs, it is convenient to divide the applet or frame into smaller regions. These regions might constitute, for example, a toolbar or a matrix of radio buttons. In Java, GUI sub-regions are implemented most commonly with the Panel container.

Panels, just like applets and frames, can contain other components: buttons, canvases, check boxes, scroll bars, scroll panes, text areas, text fields, and of course other panels. Complicated GUIs sometimes have very complicated containment hierarchies of panels within panels within panels within panels, and so on, down through many layers of containment.

In Java, the term hierarchy is ambiguous. When discussing classes, hierarchy refers to the hierarchy of inheritance from superclass to subclass. When discussing GUIs, hierarchy refers to the containment hierarchy of applets or frames, which contain panels containing panels containing panels.

The GUI in Figure 9.3 is a moderate-size frame for specifying a color. You can see at a glance that the panel contains labels, scroll bars, text fields, and buttons. You have probably guessed that the frame also contains some panels, even though they cannot be seen. In fact, the frame contains five panels. Each of the six containers (the five panels, plus the frame itself) has its own layout manager: there are four instances of Grid layout managers, one Flow layout manager, and one Border layout manager.

FIGURE 9.3

A GUI with several levels of containment

Figure 9.4 schematically shows the frame's containment hierarchy. A JavaGUI programmer must master the art of transforming a proposed GUI into a workable and efficient containment hierarchy. This is a skill that comes with experience, once the fundamentals are understood. The Programmer's Java Certification Exam does not require you to develop any complicated containments, but it does require you to understand the fundamentals.

FIGURE 9.4

Containment hierarchy

The code that implements the color chooser is listed below:

1.import java.awt.*;

2.

3.public class Hier extends Frame {

4. Hier() {

5. super( "Containment Hierarchy Example" );

6. setBounds( 20, 20, 300, 180 );

7. setLayout( new BorderLayout( 0, 25 ) );

8.

9. // Build upper panel with 3 horizontal "strips"

10. String strings[] = { "Red", "Green", "Blue" };

11. Panel bigUpperPanel = newPanel();

12. bigUpperPanel.setLayout( newGridLayout( 1, 3, 20, 0 ) );

13. for ( int i = 0; i < 3; i++ ) {

14. // Add strips: each strip is a panel within bigUpperPanel

15. Panel levelPanel = new Panel();

16. levelPanel.setLayout( new GridLayout( 3, 1, 0, 10 ) );

17. levelPanel.add( newLabel( strings[ i ] ) );

18. levelPanel.add( new Scrollbar( Scrollbar.HORIZONTAL, i, 10, 0, 255 ) );

19. levelPanel.add( new TextField( "0" ) );

20. bigUpperPanel.add( levelPanel );

21. }

22. add( bigUpperPanel, BorderLayout.CENTER );

23.

24. // Build lower panel containing 3 buttons

25. Panel lowerPanel = new Panel();

26. lowerPanel.add( new Button( "Apply" ) );

27. lowerPanel.add( new Button( "Reset" ) );

28. lowerPanel.add( new Button( "Cancel" ) );

29. add( lowerPanel, BorderLayout.SOUTH );

30. }

31.}

As you can see from the listing, there is no code anywhere that specifies exactly where the labels, scroll bars, text fields, buttons, or panels should go. Instead, there are a number of calls (lines 7, 12, and 16) to layout manager constructors. In those same lines, the new layout managers are set as the managers for the corresponding containers. The lower panel constructed in line 25 uses its default layout manager, so it is not necessary to give it a new one.

A component inside a container receives certain properties from the container. For example, if a component is not explicitly assigned a font, it uses the same font that its container uses. The same principle holds true for foreground and background color. Layout managers, however, are different. A panel's default layout manager is always Flow. An applet's default layout manager is also always Flow. A frame's default layout manager is always Border.

After each panel is constructed and assigned an appropriate layout manager, the panel is populated with the components it is to contain. For example, the lower panel, constructed in line 25, is populated with buttons in lines 26, 27, and 28. Finally, the now-populated panel is added to the container that is to hold it (line 29).

The add() method call in line 29 does not specify which object is to execute the call. That is, the form of the call is add( params ), and not someObject. add( params ). In Java, every non-static method call is executed by some object; if you don't specify one, Java assumes that you intended the method to be executed by this. So line 29 is executed by the instance of Hier, which is the outermost container in the hierarchy. Line 23, which adds the big upper panel, is similar: no executing object is specified in the add() call, so the panel is added to this.

In lines 16-19, and also in lines 26-28, a container is specified to execute the add() call. In those lines, components are added to intermediate containers.

Each panel in the sample code is built in four steps:

1. Construct the panel.

2. Give the panel a layout manager.

3. Populate the panel.

4. Add the panel to its own container.

When a container is constructed (step 1), it is given a default layout manager. For panels, the default is a flow layout manager, and step 2 can be skipped if this is the desired manager. In step 3, populating the panel involves constructing components and adding them to the panel; if any of these components is itself a panel, steps 1-4 must be recursed.

A container confers with its layout manager to determine where components will be placed and (optionally) how they will be resized. If the container subsequently gets resized, the layout manager again lays out the container's components (probably with different results, since it has a different area to work with). This "conference" between the container and the layout manager is the subject of the next section.

Component Size and Position

Components know where they are and how big they are. That is to say, the java.awt.Component class has instance variables called x, y, width, and height. The x and y variables specify the position of the component's upper-left corner (as measured from the upper-left corner of the container that contains the component), and width and height are in pixels. Figure 9.5 illustrates the x, y, width, and height of a text field inside a panel inside an applet.

FIGURE 9.5

Position and size

A component's position and size can be changed by calling the component's setBounds() method. (In releases of the JDK before 1.1, the method was called reshape(); this has been deprecated in favor of setBounds()). It seems reasonable to expect that the following code, which calls setBounds() on a button, would create an applet with a fairly big button:

1.import java.awt.Button;

2.import java.applet.Applet;

3.

4.public class AppletWithBigButton extendsApplet {

5. public void init() {

6. Button b = new Button( "I'm enormous!" );

7. b.setBounds( 3, 3, 333, 333 ); // should make button really big

8. add( b );

9. }

10.}

If you have tried something like this, you know that the result is disappointing. A screen shot appears in Figure 9.6.

FIGURE 9.6

A disappointing button

It seems that line 7 should force the button to be 333 pixels wide by 333 pixels tall. In fact, the button is just the size it would be if line 7 were omitted or commented out.

Line 7 has no effect because after it executes, the button is added to the applet (line 8). Eventually (after a fairly complicated sequence of events), the applet calls on its layout manager to enforce its layout policy on the button. The layout manager decides where and how big the button should be: in this case, the layout manager wants the button to be just large enough to accommodate its label. When this size has been calculated, the layout manager calls setBounds() on the button, clobbering the work you did in line 7.

In general, it is futile to call setBounds() on a component, because layout managers always get the last word; that is, their call to setBounds() happens after yours. There are ways to defeat this functionality, but they tend to be complicated, difficult to maintain, and not in the spirit of Java.

Java's AWT toolkit wants you to let the layout managers do the layout work. Java impels you to use layout managers, and the Certification Exam expects you to know the layout policies of the more basic managers. These policies are covered in the-next several sections.

Layout Policies

Every Java component has a preferred size. The preferred size expresses how big the component would like to be, barring conflict with a layout manager. Preferred size is generally the smallest size necessary to render the component in a visually meaningful way.

For example, a button's preferred size is the size of its label text, plus a little border of empty space around the text, plus the shadowed decorations that mark the boundary ol the button. Thus a button's preferred size is "just big enough."

Preferred size is platform dependent, since component boundary decorations vary from system to system.

When a layout manager lays out its container's child components, it has to balance two considerations: the layout policy and each component's preferred size. First priority goes to enforcing layout policy. If honoring a compoponent's preferred size would mean violating the layout policy, then the layout manager overrules the component's preferred size.

Understanding a layout manager means understanding where it will place a component, and also how it will treat a component's preferred size. The next several sections discuss some of the some of the simpler layout managers: FlowLayout, GridLayout, and BorderLayout. These are the three managers that you must know for the Certification Exam.

The Flow Layout Manager

The FlowLayout manager arranges components in horizontal rows. It is the default manager type for panels and applets, so it is usually the first layout manager that programmers encounter. It is a common experience for new Java developers to add a few components to an applet and wonder how they came to be arranged so neatly. The following code is a good example:

1.import java.awt.*;

2.import java.applet.Applet;

3.

4.public class NeatRow extends Applet {

5. public void init() {

6. Label label = new Label( "Name:" );

7. add( label );

8. TextField textfield = newTextField( "Beowulf" );

9. add( textfield );

10. Button button = new Button( "Ok" );

11. add( button );

12. }

13.}

The resulting applet is shown in Figure 9.7.

FIGURE 9.7

Simple applet using Flow layout manager

If the same three components appear in a narrower applet, as shown in Figure 9.8, there is not enough space for all three to fit in a single row. The Flow layout manager fits as many components as possible into the top row and spills the remainder into a second row. The components always appear, left to right, in the order in which they were added to their container.