Implementing a Position Layout Manager in Java

by Scott M. Consolatti, IBM


Abstract

This article provides source code for working around a common problem in Java: the lack of a position layout manager for setting fixed X and Y coordinates and fixed widths and heights.Java provides layout managers for such properties as border, flow, and grid bag, so that the size and position of components in a container will update themselves automatically as the size and position of the container changes. A flow layout is analogous to an HTML Web page's default layout, in which, if you resize your Web browser, the components of top to bottom. However, Java does not provide a position layout for setting a particular X and Y coordinate and setting a particular width and height for the component. Many Java programmers simply set their container's layout manager to null, add their components to the container, and then set their X and Y coordinates manually. Sun, however, warns that this method is not officially supported and that the results on various platforms and implementations of the Java Virtual Machine (JVM) are not guaranteed to be consistent. The better solution is to write your own position layout manager, and this article teaches you how to do so. The entire source code of the solution is provided in this article so that you may begin using it immediately in your Java programs.

The Primary Interface

Java provides for the writing of your own layout managers via the LayoutManager interface in the java.awt package. In addition, version 1.1 of the Java Development Kit (JDK) introduced a new LayoutManager2 interface, with the goal of having all layout managers consistently use constraint objects to define the constraints, or size and position information, of each component in a container. As the documentation says, however, "It does not yet provide full, general support for custom constraint-based layout managers." Still lacking is a consistent way of setting these constraint objects into the layout manager and then later getting the constraint object for a particular component back. To avoid these problems, I prefer using my own LayoutManager3 (shown below), which adds abstract setConstraints and getConstraints methods for working around this shortcoming until Java provides full support.

Choosing Your Constraints

Before you can make your layout manager work, you should start by defining the constraints that will be assigned to each component. As shown below in my PositionConstraints class, constraints are simple model objects that should be cloneable. I want each component to be at a particular size and position, and I also want some basic anchoring and filling capabilities, similar to grid bag layout, resulting in the following constraint information:

  • A component's position, or X and Y coordinates, held in the X and Y instance variables of PositionConstraints.

  • A component's size, held in the width and height instance variables, with the ability to specify -1 to use the component's preferred width or height.

  • Whether or not a component should be anchored at a particular compass point in a container, such as north, east, or southwest. This information is held in the anchor instance variable. Public static constants are predefined for each compass point, such as NORTH, EAST, etc.

  • Whether or not a component should fill the width or height of a container. This information is held in the fill instance variable. Public static constants are predefined for filling HORIZONTAL, VERTICAL, or BOTH.

  • Whether or not a component should be inset a certain number of pixels from the edge of a container. This information is held in the insets instance variable.

Telling Your Layout Manager What to Do With Your Constraints

Once you have your constraints, you can create your layout manager to work with those constraints. As in my PositionLayout class (shown below), this involves implementing the LayoutManager interface and filling in the required methods that the interface defines as abstract, such as layoutContainer, minimumLayoutSize, and preferredLayoutSize.

minimumLayoutSize and preferredLayoutSize both call one common method, layoutSize, which loops through all of the components in the parent container, looking up their constraints and keeping a total of the width and height required by the container to display all of the components. If a constraint has a -1 for width or height, the component's minimum or preferred width and height are used. Anchor and fill constraints and insets of both the parent container and component constraints are also factored into the calculations.

While minimumLayoutSize and preferredLayoutSize define the overall size of the parent container when its parent container is validated, layoutContainer actually sizes and positions the components within the available space. This is achieved, again, by looping through all of the components in the parent container, looking up their constraints, and, this time, calling setBounds on each component to set its size and position. Again, if a constraint has a -1 for width or height, preferred size is used, and anchor, fill, and insets information is accounted for.

We must also fill in the required methods for LayoutManager3: getConstraints and setConstraints. As with grid bag layout, I chose to hold constraint objects in a private hashtable, named consTable in this example. setConstraints puts a key:value pair in the hashtable, with the component being the key and the constraints being the value. If no valid constraints are specified, default constraints are used, and care is taken to ensure that the constraints that go into the hashtable are always uniquely cloned. getConstraints uses the common lookupConstraints method that is also used by layoutSize and layoutContainer, which simply gets the constraints back out of the hashtable and returns them. Again, if no constraints exist for a component, default constraints are used. It is hoped that, as Sun provides general support for custom constraint-based layout managers in the future, it will encapsulate this hashtable mechanism so that you will not have to manage it yourself in each layout manager.

Just as with the constraints for the components contained within, my position layout manager itself also has a width and a height. Again, if -1 is defined for width or height, it means that the parent container will be elastic in size and will grow and shrink to the preferred size of all of the contained components. If an explicit width or height is set, the parent container will always be a fixed size, regardless of any contained components and their size and location. setConstraints defines special code to handle the case of your fixing or making preferred the size of a component that happens to be a container that itself has a position layout manager. In that case, I make sure to fix or make preferred the size of the nested layout manager so that it matches that of its corresponding container.

Using Your New Position Layout Manager

Your position layout manager is now complete and ready for use. My sample PositionExample class at the end of this article shows an example of some of the ways you can size and position components with the position layout manager. With your position layout manager, you'll be ready to arrange components at traditional X and Y coordinates, and you will be assured of consistent results across various platforms and implementations of the JVM.

LayoutManager3.java 

         //--------------------------------------------------------------------
         // A simple alternative to LayoutManager2 that provides for a
         // consistent way of getting and setting constraints for the
         // components in a container.
         //--------------------------------------------------------------------
         import java.awt.*;
         import java.io.*;

         public interface LayoutManager3 extends LayoutManager, Cloneable
         {
           public abstract Object getConstraints(Component comp);
           public abstract void setConstraints(Component comp, Object cons);
           public abstract Object clone();
         }


 PositionConstraints.java 

         //--------------------------------------------------------------------
         // The constraints for each component in a position layout manager.
         // If the width or height is set to -1 (the default), then the 
         // component will automatically size to its preferred size in that
         // dimension.
         //--------------------------------------------------------------------
         import java.awt.*;

         public class PositionConstraints implements Cloneable
         {
           public static final int NONE =        0;

           public static final int BOTH =        1;
           public static final int HORIZONTAL =  2;
           public static final int VERTICAL =    3;

           public static final int NORTH =       1;
           public static final int NORTHEAST =   2;
           public static final int EAST =        3;
           public static final int SOUTHEAST =   4;
           public static final int SOUTH =       5;
           public static final int SOUTHWEST =   6;
           public static final int WEST =        7;
           public static final int NORTHWEST =   8;

           public int    x;
           public int    y;
           public int    width;
           public int    height;
           public int    anchor;
           public int    fill;
           public Insets insets;


           //-1 for width or height means use the preferred size
           public PositionConstraints()
           {
             x = 0;
             y = 0;
             width = -1;
             height = -1;
             anchor = NONE;
             fill = NONE;
             insets = new Insets(0, 0, 0, 0);
           }

           public Object clone()
           {
             PositionConstraints p = new PositionConstraints();
             p.x = x;
             p.y = y;
             p.width = width;
             p.height = height;
             p.anchor = anchor;
             p.fill = fill;
             p.insets = (Insets)insets.clone();
             return p;
           }
         }


 PositionLayout.java 

         //--------------------------------------------------------------------
         // A custom constraint-based layout manager for laying out components
         // in the traditional positional way (a component at any X and Y
         // coordinate with any width and height).
         //--------------------------------------------------------------------
         import java.awt.*;
         import java.beans.*;
         import java.util.*;

         public class PositionLayout implements LayoutManager3
         {
           private static final int PREFERRED = 0;
           private static final int MINIMUM =   1;

           private Hashtable   consTable = new Hashtable();
           private PositionConstraints
                               defaultConstraints = new PositionConstraints();
           public int          width;
           public int          height;
           

           //-1 for width or height means use the preferred size
           public PositionLayout()
           {
             width = -1;
             height = -1;
           }

           public PositionLayout(int w, int h)
           {
             width = w;
             height = h;
           }
           
           public void addLayoutComponent(String name, Component comp)
           {
             //Not used. The user should call setConstraints, and then just
             //use the generic add(comp) method, like with GridBagLayout.
           }

           public Object getConstraints(Component comp)
           {
             return lookupConstraints(comp).clone();
           }

           public void layoutContainer(Container parent)
           {
             Component               comp;
             Component[]             comps;
             PositionConstraints     cons;
             Dimension               d;
             Dimension               pD;
             int                     x;
             int                     y;

             comps = parent.getComponents();
             Insets insets = parent.getInsets();
             pD = parent.getSize();
             for (int i = 0; i < comps.length; i++)
             {
               comp = comps[i];
               cons = lookupConstraints(comp);
               x = cons.x + cons.insets.left + insets.left;
               y = cons.y + cons.insets.top + insets.top;
               d = comp.getPreferredSize();
               if (cons.width != -1)
                 d.width = cons.width;
               if (cons.height != -1)
                 d.height = cons.height;
               if ((cons.fill == PositionConstraints.BOTH) ||
                   (cons.fill == PositionConstraints.HORIZONTAL))
               {
                 x = insets.left + cons.insets.left;
                 d.width = pD.width - cons.insets.left - cons.insets.right - 
                               insets.left - insets.right;
               }
               if ((cons.fill == PositionConstraints.BOTH) ||
                   (cons.fill == PositionConstraints.VERTICAL))
               {
                 y = insets.top + cons.insets.top;
                 d.height = pD.height - cons.insets.top - cons.insets.bottom - 
                               insets.top - insets.bottom;
               }
               switch (cons.anchor)
               {
                 case PositionConstraints.NORTH:
                   x = (pD.width - d.width) / 2;
                   y = cons.insets.top + insets.top;
                   break;
                 case PositionConstraints.NORTHEAST:
                   x = pD.width - d.width - cons.insets.right - insets.right;
                   y = cons.insets.top + insets.top;
                   break;
                 case PositionConstraints.EAST:
                   x = pD.width - d.width - cons.insets.right - insets.right;
                   y = (pD.height - d.height) / 2;
                   break;
                 case PositionConstraints.SOUTHEAST:
                   x = pD.width - d.width - cons.insets.right - insets.right;
                   y = pD.height - d.height - cons.insets.bottom - 
                                                               insets.bottom;
                   break;
                 case PositionConstraints.SOUTH:
                   x = (pD.width - d.width) / 2;
                   y = pD.height - d.height - cons.insets.bottom - 
                                                               insets.bottom;
                   break;
                 case PositionConstraints.SOUTHWEST:
                   x = cons.insets.left + insets.left;
                   y = pD.height - d.height - cons.insets.bottom - 
                                                               insets.bottom;
                   break;
                 case PositionConstraints.WEST:
                   x = cons.insets.left + insets.left;
                   y = (pD.height - d.height) / 2;
                   break;
                 case PositionConstraints.NORTHWEST:
                   x = cons.insets.left + insets.left;
                   y = cons.insets.top + insets.top;
                   break;
                 default:
                   break;
               }
               comp.setBounds(x, y, d.width, d.height);
             }
           }

           private Dimension layoutSize(Container parent, int type)
           {
             int   newWidth;
             int   newHeight;

             if ((width == -1) || (height == -1))
             {
               Component               comp;
               Component[]             comps;
               PositionConstraints     cons;
               Dimension               d;
               int                     x;
               int                     y;

               newWidth = newHeight = 0;
               comps = parent.getComponents();
               for (int i = 0; i < comps.length; i++)
               {
                 comp = comps[i];
                 cons = lookupConstraints(comp);
                 if (type == PREFERRED)
                   d = comp.getPreferredSize();
                 else
                   d = comp.getMinimumSize();
                 if (cons.width != -1)
                   d.width = cons.width;
                 if (cons.height != -1)
                   d.height = cons.height;
                 if (cons.anchor == PositionConstraints.NONE)
                 {
                   x = cons.x;
                   y = cons.y;
                 }
                 else
                 {
                   x = cons.insets.left;
                   y = cons.insets.top;
                 }
                 if ((cons.fill != PositionConstraints.BOTH) && 
                     (cons.fill != PositionConstraints.HORIZONTAL))
                   newWidth = Math.max(newWidth, x + d.width);
                 else
                   newWidth = Math.max(newWidth, d.width + cons.insets.left +
                                                           cons.insets.right);
                 if ((cons.fill != PositionConstraints.BOTH) && 
                     (cons.fill != PositionConstraints.VERTICAL))
                   newHeight = Math.max(newHeight, y + d.height);
                 else
                   newHeight = Math.max(newHeight, d.height + cons.insets.top +
                                                           cons.insets.bottom);
               }
               if (width != -1)
                 newWidth = width;
               if (height != -1)
                 newHeight = height;
             }
             else
             {
               newWidth = width;
               newHeight = height;
             }
             Insets insets = parent.getInsets();
             return (new Dimension(newWidth + insets.left + insets.right,
                                   newHeight + insets.top + insets.bottom));
           }

           private PositionConstraints lookupConstraints(Component comp)
           {
             PositionConstraints p = (PositionConstraints)consTable.get(comp);
             if (p == null)
             {
               setConstraints(comp, defaultConstraints);
               p = defaultConstraints;
             }
             return p;
           }

           public Dimension minimumLayoutSize(Container parent)
           {
             return layoutSize(parent, MINIMUM);
           }

           public Dimension preferredLayoutSize(Container parent)
           {
             return layoutSize(parent, PREFERRED);
           }

           public void removeLayoutComponent(Component comp)
           {
             consTable.remove(comp);
           }

           public void setConstraints(Component comp, Object cons)
           {
             if ((cons == null) || (cons instanceof PositionConstraints))
             {
               PositionConstraints pCons;
               if (cons == null)
                 pCons = (PositionConstraints)defaultConstraints.clone();
               else
                 pCons = (PositionConstraints)
                                       ((PositionConstraints)cons).clone();
               consTable.put(comp, pCons);
               //The following is necessary for nested position layout
               //managers. When the constraints of the component are set
               //to be elastic or non-elastic, then check to see if the
               //component itself is a container with a position layout
               //manager and, if so, set the layout manager itself to be
               //elastic or non-elastic as necessary.
               if (Beans.isInstanceOf(comp, Container.class))
                 if (((Container)Beans.getInstanceOf(comp,
                             Container.class)).getLayout()
                         instanceof PositionLayout)
                 {   
                   PositionLayout layout;
                   layout = (PositionLayout)
                                   ((Container)Beans.getInstanceOf(comp, 
                                         Container.class)).getLayout();
                   layout.width = pCons.width;
                   layout.height = pCons.height;
                 }
             }
           }

           public Object clone()
           {
             PositionLayout p = new PositionLayout(width, height);
             return p;
           }
         }


 PositionExample.java 

         //--------------------------------------------------------------------
         // An example showing how to create a container with a position
         // layout and how to add children components to it at various positions.
         //--------------------------------------------------------------------
         import java.awt.*;

         public class PositionExample extends Frame
         {
           PositionExample()
           {
             super();
             setSize(400, 300);

             // Create and set a position layout
             PositionLayout posLayout = new PositionLayout();
             setLayout(posLayout);

             // Create children with various position constraints
             PositionConstraints pCons;

             pCons = new PositionConstraints();
             pCons.x = 300;
             pCons.y = 210;
             Button preferredSizeButton = new Button("x=300, y=210");
             posLayout.setConstraints(preferredSizeButton, pCons);
             add(preferredSizeButton);

             pCons = new PositionConstraints();
             pCons.x = 75;
             pCons.y = 50;
             pCons.width = 200;
             pCons.height = 60;
             Button fixedSizeButton = new Button("x=75, y=50, w=200, h=60");
             posLayout.setConstraints(fixedSizeButton, pCons);
             add(fixedSizeButton);

             pCons = new PositionConstraints();
             pCons.anchor = PositionConstraints.SOUTHWEST;
             Button anchorButton = new Button("Anchor southwest");
             posLayout.setConstraints(anchorButton, pCons);
             add(anchorButton);

             pCons = new PositionConstraints();
             pCons.anchor = PositionConstraints.NORTH;
             pCons.fill = PositionConstraints.HORIZONTAL;
             pCons.insets = new Insets(8, 8, 8, 8);
             Button insetButton = 
                     new Button("Anchor north, fill horizontal, inset 8");
             posLayout.setConstraints(insetButton, pCons);
             add(insetButton);

             pCons = new PositionConstraints();
             pCons.fill = PositionConstraints.BOTH;
             Button fillButton = new Button("Fill both");
             posLayout.setConstraints(fillButton, pCons);
             add(fillButton);
           }

           public static void main(String args[])
           {
             (new PositionExample()).setVisible(true);
           }
         }




Java is a trademark of Sun Microsystems, Inc.

Other companies, products, and service names may be trademarks or service marks of others.

Copyright    Trademark



  Java Feature Java Home  
IBM HomeOrderEmployment