[Chapter 7] 7.10 Designing Your Own LayoutManager

#

| Chapter 7 Layouts |

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

#### Figure 7.14: CornerLayout

#### Figure 7.15: CornerLayout with missing regions

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) {
        }
    }

Disabling the LayoutManager |  | The sun.awt Layout Collection