Java AWT

Previous Chapter 7
Layouts
Next
 

7.10 Designing Your Own LayoutManager

What if you can't find a LayoutManager that fits your requirements, or you find yourself repeatedly building the same multipanel display? In cases like these, you can build your own layout manager. It's really not that difficult; you only need to implement the five methods of the LayoutManager interface, plus a constructor and any additional methods your design requires. In this section, we'll review the LayoutManager interface and then construct a custom LayoutManager called CornerLayout.

LayoutManager Methods

A custom LayoutManager must implement the following five methods (ten methods if you implement LayoutManager2). For many layout managers, several of these methods can be stubs that don't do anything.

public void addLayoutComponent (String name, Component component)

The addLayoutComponent() method is called by the add(name, component) method of Container. If your new LayoutManager does not have named component areas or does not pass generic positioning information via name, this method will be a stub with no code; you can let the container keep track of the components for you. Otherwise, this method must keep track of the component added, along with the information in name.

How would you implement this method? For layouts that have named component areas (like BorderLayout), you could use a private instance variable to hold the component for each area. For layouts like CardLayout, which lets you refer to individual components by name, you might want to store the components and their names in an internal Hashtable.

public void removeLayoutComponent (Component component)

This method is called by the remove() and removeAll() methods of Container. If you are storing information in internal instance variables or tables, you can remove the information about the given Component from the tables at this point. If you're not keeping track of the components yourself, this method can be a stub that does nothing.

public Dimension preferredLayoutSize (Container target)

This method is called by preferredSize() to calculate the desired size of target.[1] Obviously, the preferred size of the container depends on the layout strategy that you implement. To compute the preferred size, you usually need to call the preferredSize() method of every component in the container.

[1] This is still true in Java 1.1; the new method, getPreferredSize(), just calls the deprecated method, preferredSize().

Computing the preferred size can be messy. However, some layout strategies let you take a shortcut. If your layout policy is "I'm going to cram all the components into the space given to me, whether they fit or not," you can compute the preferred size of your layout simply by calling target.size() or (in Java 1.1) target.getSize().

public Dimension minimumLayoutSize (Container target)

This method is called by minimumSize() to calculate the minimum size of target. The minimum size of the container depends on the layout strategy that you implement. To compute the minimum size, you usually need to call the minimumSize() method of every component in the container.

As with preferredLayoutSize(), you can sometimes save a lot of work by returning target.size().

public void layoutContainer (Container target)

This method is called when target is first displayed and whenever it is resized. It is responsible for arranging the components within the container. Depending upon the type of LayoutManager you are creating, you will either loop through all the components in the container with the getComponent() method or use the named components that you saved in the addLayoutComponent() method. To position and size the components, call their reshape() or setBounds() methods.

A New LayoutManager: CornerLayout

CornerLayout is a simple but useful layout manager that is similar in many respects to BorderLayout. Like BorderLayout, it positions components in five named regions: "Northeast", "Northwest", "Southeast", "Southwest", and "Center". These regions correspond to the four corners of the container, plus the center. The "Center" region has three modes. NORMAL, the default mode, places the "Center" component in the center of the container, with its corners at the inner corner of the other four regions. FULL_WIDTH lets the center region occupy the full width of the container. FULL_HEIGHT lets the center region occupy the full height of the container. You cannot specify both FULL_HEIGHT and FULL_WIDTH; if you did, the "Center" component would overlap the corner components and take over the container. Figure 7.14 shows a CornerLayout in each of these modes.

Not all regions are required. If a complete side is missing, the required space for the container decreases. Ordinarily, the other components would grow to fill this vacated space. However, if the container is sized to its preferred size, so are the components. Figure 7.15 shows this behavior.

Example 7.3 is the code for the CornerLayout. It shows the Java 1.0 version of the layout manager. At the end of this section, I show the simple change needed to adapt this manager to Java 1.1.

Example 7.3: The CornerLayout LayoutManager

import java.awt.*;
/**
 * An 'educational' layout. CornerLayout will layout a container
 * using members named "Northeast", "Northwest", "Southeast",
 * "Southwest", and "Center".
 *
 * The "Northeast", "Northwest", "Southeast" and "Southwest" components
 * get sized relative to the adjacent corner's components and
 * the constraints of the container's size. The "Center" component will
 * get any space left over. 
 */
public class CornerLayout implements LayoutManager {
    int hgap;
    int vgap;
    int mode;
    public final static int NORMAL = 0;
    public final static int FULL_WIDTH = 1;
    public final static int FULL_HEIGHT = 2;
    Component northwest;
    Component southwest;
    Component northeast;
    Component southeast;
    Component center;

The CornerLayout class starts by defining instance variables to hold the gaps and mode and the components for each corner of the screen. It also defines three constants that control the behavior of the center region: NORMAL, FULL_WIDTH, and FULL_HEIGHT.

    /**
     * Constructs a new CornerLayout.
     */
    public CornerLayout() {
        this (0, 0, CornerLayout.NORMAL);
    }
    public CornerLayout(int mode) {
        this (0, 0, mode);
    }
    public CornerLayout(int hgap, int vgap) {
        this (hgap, vgap, CornerLayout.NORMAL);
    }
    public CornerLayout(int hgap, int vgap, int mode) {
        this.hgap = hgap;
        this.vgap = vgap;
        this.mode = mode;
    }

The constructors for CornerLayout are simple. The default (no arguments) constructor creates a CornerLayout with no gaps; the "Center" region is NORMAL mode. The last constructor, which is called by the other three, stores the gaps and the mode in instance variables.

    public void addLayoutComponent (String name, Component comp) {
        if ("Center".equals(name)) {
            center = comp;
        } else if ("Northwest".equals(name)) {
            northwest = comp;
        } else if ("Southeast".equals(name)) {
            southeast = comp;
        } else if ("Northeast".equals(name)) {
            northeast = comp;
        } else if ("Southwest".equals(name)) {
            southwest = comp;
        }
    }

addLayoutComponent() figures out which region a component has been assigned to, and saves the component in the corresponding instance variable. If the name of the component isn't "Northeast", "Northwest", Southeast", "Southwest", or "Center", the component is ignored.

    public void removeLayoutComponent (Component comp) {
        if (comp == center) {
            center = null;
        } else if (comp == northwest) {
            northwest = null;
        } else if (comp == southeast) {
            southeast = null;
        } else if (comp == northeast) {
            northeast = null;
        } else if (comp == southwest) {
            southwest = null;
        }
    }

removeLayoutComponent() searches for a given component in each region; if it finds the component, removeLayoutComponent() discards it by setting the instance variable to null.

    public Dimension minimumLayoutSize (Container target) {
        Dimension dim = new Dimension(0, 0);
        Dimension northeastDim = new Dimension (0,0);
        Dimension northwestDim = new Dimension (0,0);
        Dimension southeastDim = new Dimension (0,0);
        Dimension southwestDim = new Dimension (0,0);
        Dimension centerDim    = new Dimension (0,0);
        if ((northeast != null) && northeast.isVisible ()) {
            northeastDim = northeast.minimumSize ();
        }
        if ((southwest != null) && southwest.isVisible ()) {
            southwestDim = southwest.minimumSize ();
        }
        if ((center != null) && center.isVisible ()) {
            centerDim = center.minimumSize ();
        }
        if ((northwest != null) && northwest.isVisible ()) {
            northwestDim = northwest.minimumSize ();
        }
        if ((southeast != null) && southeast.isVisible ()) {
            southeastDim = southeast.minimumSize ();
        }
        dim.width = Math.max (northwestDim.width, southwestDim.width) +
                        hgap + centerDim.width + hgap +
                        Math.max (northeastDim.width, southeastDim.width);
        dim.height = Math.max (northwestDim.height, northeastDim.height) +
                        + vgap + centerDim.height + vgap +
                        Math.max (southeastDim.height, southwestDim.height);
        Insets insets = target.insets();
        dim.width += insets.left + insets.right;
        dim.height += insets.top + insets.bottom;
        return dim;
    }

minimumLayoutSize() computes the minimum size of the layout by finding the minimum sizes of all components. To compute the minimum width, minimumLayoutSize() adds the width of the center, plus the greater of the widths of the western regions (northwest and southwest), plus the greater of the widths of the eastern regions (northeast and southeast), then adds the appropriate gaps and insets. The minimum height is computed similarly; the method takes the greater of the minimum heights of the northern regions, the greater of the minimum heights of the southern regions, and adds them to the minimum height of the center region, together with the appropriate gaps and insets.

    public Dimension preferredLayoutSize (Container target) {
        Dimension dim = new Dimension(0, 0);
        Dimension northeastDim = new Dimension (0,0);
        Dimension northwestDim = new Dimension (0,0);
        Dimension southeastDim = new Dimension (0,0);
        Dimension southwestDim = new Dimension (0,0);
        Dimension centerDim    = new Dimension (0,0);
        if ((northeast != null) && northeast.isVisible ()) {
            northeastDim = northeast.preferredSize ();
        }
        if ((southwest != null) && southwest.isVisible ()) {
            southwestDim = southwest.preferredSize ();
        }
        if ((center != null) && center.isVisible ()) {
            centerDim = center.preferredSize ();
        }
        if ((northwest != null) && northwest.isVisible ()) {
            northwestDim = northwest.preferredSize ();
        }
        if ((southeast != null) && southeast.isVisible ()) {
            southeastDim = southeast.preferredSize ();
        }
        dim.width = Math.max (northwestDim.width, southwestDim.width) +
                        hgap + centerDim.width + hgap +
                        Math.max (northeastDim.width, southeastDim.width);
        dim.height = Math.max (northwestDim.height, northeastDim.height) +
                        + vgap + centerDim.height + vgap +
                        Math.max (southeastDim.height, southwestDim.height);
        Insets insets = target.insets();
        dim.width += insets.left + insets.right;
        dim.height += insets.top + insets.bottom;
        return dim;
    }

preferredLayoutSize() computes the preferred size of the layout. The method is almost identical to minimumLayoutSize(), except that it uses the preferred dimensions of each component.

    public void layoutContainer (Container target) {
        Insets insets = target.insets();
        int top = insets.top;
        int bottom = target.size ().height - insets.bottom;
        int left = insets.left;
        int right = target.size ().width - insets.right;
        Dimension northeastDim = new Dimension (0,0);
        Dimension northwestDim = new Dimension (0,0);
        Dimension southeastDim = new Dimension (0,0);
        Dimension southwestDim = new Dimension (0,0);
        Dimension centerDim    = new Dimension (0,0);
        Point topLeftCorner, topRightCorner, bottomLeftCorner,
                        bottomRightCorner;
        if ((northeast != null) && northeast.isVisible ()) {
            northeastDim = northeast.preferredSize ();
        }
        if ((southwest != null) && southwest.isVisible ()) {
            southwestDim = southwest.preferredSize ();
        }
        if ((center != null) && center.isVisible ()) {
            centerDim = center.preferredSize ();
        }
        if ((northwest != null) && northwest.isVisible ()) {
            northwestDim = northwest.preferredSize ();
        }
        if ((southeast != null) && southeast.isVisible ()) {
            southeastDim = southeast.preferredSize ();
        }
        topLeftCorner = new Point (left +
                          Math.max (northwestDim.width, southwestDim.width),
                          top + 
                          Math.max (northwestDim.height, northeastDim.height));
        topRightCorner = new Point (right -
                          Math.max (northeastDim.width, southeastDim.width), 
                          top +
                          Math.max (northwestDim.height, northeastDim.height));
        bottomLeftCorner = new Point (left + 
                          Math.max (northwestDim.width, southwestDim.width),
                          bottom - 
                          Math.max (southwestDim.height, southeastDim.height));
        bottomRightCorner = new Point (right  -
                          Math.max (northeastDim.width, southeastDim.width),
                          bottom - 
                          Math.max (southwestDim.height, southeastDim.height));
        if ((northwest != null) && northwest.isVisible ()) {
            northwest.reshape (left, top,
                                left + topLeftCorner.x,
                                top + topLeftCorner.y);
        }
        if ((southwest != null) && southwest.isVisible ()) {
            southwest.reshape (left, bottomLeftCorner.y,
                                bottomLeftCorner.x - left,
                                bottom - bottomLeftCorner.y);
       }
        if ((southeast != null) && southeast.isVisible ()) {
            southeast.reshape (bottomRightCorner.x,
                        bottomRightCorner.y,
                        right - bottomRightCorner.x,
                        bottom - bottomRightCorner.y);
        }
        if ((northeast != null) && northeast.isVisible ()) {
            northeast.reshape (topRightCorner.x, top,
                                right - topRightCorner.x,
                                topRightCorner.y);
        }
        if ((center != null) && center.isVisible ()) {
            int x = topLeftCorner.x + hgap;
            int y = topLeftCorner.y + vgap;
            int width = bottomRightCorner.x - topLeftCorner.x - hgap * 2;
            int height = bottomRightCorner.y - topLeftCorner.y - vgap * 2;
            if (mode == CornerLayout.FULL_WIDTH) {
                x = left;
                width = right - left;
            } else if (mode == CornerLayout.FULL_HEIGHT) {
                y = top;
                height = bottom - top;
            }
            center.reshape (x, y, width, height);
        }
    }

layoutContainer() does the real work: it positions and sizes the components in our layout. It starts by computing the region of the target container that we have to work with, which is essentially the size of the container minus the insets. The boundaries of the working area are stored in the variables top, bottom, left, and right. Next, we get the preferred sizes of all visible components and use them to compute the corners of the "Center" region; these are stored in the variables topLeftCorner, topRightCorner, bottomLeftCorner, and bottomRightCorner.

Once we've computed the location of the "Center" region, we can start placing the components in their respective corners. To do so, we simply check whether the component is visible; if it is, we call its reshape() method. After dealing with the corner components, we place the "Center" component, taking into account any gaps (hgap and vgap) and the layout's mode. If the mode is NORMAL, the center component occupies the region between the inner corners of the other components. If the mode is FULL_HEIGHT, it occupies the full height of the screen. If it is FULL_WIDTH, it occupies the full width of the screen.

    public String toString() {
        Sting str;
        switch (mode) {
            case FULL_HEIGHT: str = "tall"; break;
            case FULL_WIDTH: str = "wide"; break;
            default: str = "normal"; break;
        }
        return getClass().getName () + "[hgap=" + hgap + ",vgap=" + vgap + 
            ",mode="+str+"]";
    }
}

toString() simply returns a string describing the layout.

Strictly speaking, there's no reason to update the CornerLayout for Java 1.1. Nothing about Java 1.1 says that new layout managers have to implement the LayoutManager2 interface. However, implementing LayoutManager2 isn't a bad idea, particularly since CornerLayout works with constraints; like BorderLayout, it has named regions. To extend CornerLayout so that it implements LayoutManager2, add the following code; we'll create a new CornerLayout2:

// Java 1.1 only
import java.awt.*;
public class CornerLayout2 extends CornerLayout implements LayoutManager2 {
    public void addLayoutComponent(Component comp, Object constraints) {
        if ((constraints == null) || (constraints instanceof String)) {
            addLayoutComponent((String)constraints, comp);
        } else {
            throw new IllegalArgumentException(
                  "cannot add to layout: constraint must be a string (or null)");
        }
    }
    public Dimension maximumLayoutSize(Container target) {
        return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
    }
    public float getLayoutAlignmentX(Container parent) {
        return Component.CENTER_ALIGNMENT;
    }
    public float getLayoutAlignmentY(Container parent) {
        return Component.CENTER_ALIGNMENT;
    }
    public void invalidateLayout(Container target) {
    }
}


Previous Home Next
Disabling the LayoutManager Book Index The sun.awt Layout Collection

Java in a Nutshell Java Language Reference Java AWT Java Fundamental Classes Exploring Java