Recipe 14.18 Program: Custom Layout Manager


Problem

None of the standard layout managers do quite what you need.

Solution

Roll your own. All you need to do is implement the methods of the java.awt.LayoutManager interface.

Discussion

While many people are intimidated by the thought of writing their own layout manager, it beats the alternative of using only "the big five" layouts (BorderLayout, CondLayout, FlowLayout, GridBagLayout, and GridLayout). BorderLayout isn't quite flexible enough, and GridBaglayout is too complex for many applications. Suppose, for instance, that you wanted to lay out an arbitrary number of components in a circle. In a typical X Windows or Windows application, you would write the geometry calculations within the code for creating the components to be drawn. This would work, but the code for the geometry calculations would be unavailable to anybody who needed it later. The LayoutManager interface is another great example of how the Java API's design promotes code reuse: if you write the geometry calculations as a layout manager, then anybody needing this type of layout could simply instantiate your CircleLayout class to get circular layouts.

As another example, consider the layout shown in Figure 14-17, where the labels column and the textfield column have different widths. Using the big five layouts, there's no good way to ensure that the columns line up and that you have control over their relative widths. Suppose you wanted the label field to take up 40% of the panel and the entry field to take up 60%. I'll implement a simple layout manager here, both to show you how easy it is and to give you a useful class for making panels like the one shown.

Figure 14-17. EntryLayout in action
figs/jcb2_1417.gif


The methods for the LayoutManager interface are shown in Table 14-2.

Table 14-2. LayoutManager methods

Method name

Description

preferredLayoutSize(Container )

Like getPreferredSize( ) for a component: the "best" size for the container

minimumLayoutSize( Container )

Same, but for the minimum workable size

layoutContainer(Container )

Perform the layout calculations, and resize and reposition all the components at the current size of the container

addLayoutComponent(String, Component)

Associate a constraint with a given component (you normally store these mappings in a java.util.HashMap( ))

removeLayoutComponent( Component)

Remove a component from the HashMap


If you don't need Constraint objects (like BorderLayout.NORTH or a GridBagConstraint object), you can ignore the last two methods. Well, you can't ignore them completely. Since this is an interface, you must implement them. But they can be as simple as {}, that is, a null-bodied method.

That leaves only three serious methods. The first, preferredLayoutSize( ), normally loops through all the components either in the HashMap if using constraints, or in an array returned by the container's getComponents( ) method asking each for its preferred size and adding them up, while partly doing the layout calculations. And minimumLayoutSize( ) is the same for the smallest possible layout that will work. It may be possible for these methods to delegate either to a common submethod or to invoke layoutContainer( ), depending upon how the given layout policy works.

The most important method is layoutContainer( ). This method needs to examine all the components and decide where to put them and how big to make each one. Having made the decision, it can use setBounds( ) to set each one's position and size.

Other than a bit of error checking, that's all that's involved. Here's an example, EntryLayout , that implements the multicolumn layout shown in Figure 14-17. Quoting its Javadoc documentation:

A simple layout manager, for Entry areas like:

Login: ______________

Password: ______________

Basically two (or more) columns of different, but constant, widths.

Construct instances by passing an array of the column width percentages (as doubles, fractions from 0.1 to 0.9, so 40%, 60% would be {0.4, 0.6}). The length of this array uniquely determines the number of columns. Columns are forced to be the relevant widths. As with GridLayout, the number of items added must be an even multiple of the number of columns. If not, exceptions may be thrown!

First, let's look at the program that uses this layout to produce Figure 14-17. This program simply creates a JFrame, gets the contentPane container, and sets its layout to an instance of EntryLayout, passing an array of two doubles representing the relative widths (decimal fractions, not percentages) into the EntryLayout constructor. Then we add an even number of components, and call pack( ) which in turn calls our preferredLayoutSize( ) and setVisible(true):

import java.awt.*; import java.awt.event.*; import javax.swing.*; /** Testbed for EntryLayout layout manager.  */ public class EntryLayoutTest {     /** "main program" method - construct and show */     public static void main(String[] av) {         final JFrame f = new JFrame("EntryLayout Demonstration");         Container cp = f.getContentPane( );         double widths[] = { .33, .66 };         cp.setLayout(new EntryLayout(widths));         cp.add(new JLabel("Login:", SwingConstants.RIGHT));         cp.add(new JTextField(10));         cp.add(new JLabel("Password:", SwingConstants.RIGHT));         cp.add(new JPasswordField(20));         cp.add(new JLabel("Security Domain:", SwingConstants.RIGHT));         cp.add(new JTextField(20));         // cp.add(new JLabel("Monkey wrench in works"));         f.pack( );         f.addWindowListener(new WindowCloser(f, true));         f.setLocation(200, 200);         f.setVisible(true);     } }

Nothing complicated about it. The last JLabel ("Monkey wrench in works") is commented out since, as noted, the LayoutManager throws an exception if the number of components is not evenly divisible by the number of columns. It was put in during testing and then commented out, but was left in place for further consideration. Note that this layout operates correctly with more than two columns, but it does assume that all columns are approximately the same height (relaxing this requirement has been left as an exercise for the reader).

Finally, let's look at the code for the layout manager itself, shown in Example 14-11. After some constants and fields and two constructors, the methods are listed in about the same order as the discussion earlier in this recipe: the dummy add/remove component methods; then the preferredSize( ) and minimumLayoutSize( ) methods (which delegate to computeLayoutSize); and, finally, layoutContainer, which does the actual laying out of the components within the container. As you can see, the entire EntryLayout layout manager class is only about 140 lines, including a lot of comments.

Example 14-11. EntryLayout.java
// package com.darwinsys.entrylayout; import java.awt.*; import java.util.*; /** A simple layout manager, for Entry areas like:  * <pre>  *    Login: _____________   * Password: _____________   * </pre>  * <b>Note: all columns must be approximately the same height!</b>  */ public class EntryLayout implements LayoutManager {     /** The array of widths, as decimal fractions (0.4 == 40%, etc.). */     protected final double[] widthPercentages;     /** The number of columns. */     protected final int COLUMNS;     /** The default padding */     protected final static int HPAD = 5, VPAD = 5;     /** The actual padding */     protected final int hpad, vpad;     /** True if the list of widths was valid. */     protected boolean validWidths = false;     /** Construct an EntryLayout with widths and padding specified.      * @param widths    Array of doubles specifying column widths.      * @param h            Horizontal padding between items      * @param v            Vertical padding between items      */     public EntryLayout(double[] widths, int h, int v) {         COLUMNS = widths.length;         widthPercentages = new double[COLUMNS];         for (int i=0; i<widths.length; i++) {             if (widths[i] >= 1.0)                 throw new IllegalArgumentException(                     "EntryLayout: widths must be fractions < 1");             widthPercentages[i] = widths[i];         }         validWidths = true;         hpad = h;         vpad = v;     }     /** Construct an EntryLayout with widths and with default padding amounts.      * @param widths    Array of doubles specifying column widths.      */     public EntryLayout(double[] widths) {         this(widths, HPAD, VPAD);     }     /** Adds the specified component with the specified constraint       * to the layout; required by LayoutManager but not used.      */     public void addLayoutComponent(String name, Component comp) {         // nothing to do     }     /** Removes the specified component from the layout;      * required by LayoutManager, but does nothing.      */     public void removeLayoutComponent(Component comp)  {         // nothing to do     }     /** Calculates the preferred size dimensions for the specified panel       * given the components in the specified parent container. */     public Dimension preferredLayoutSize(Container parent)  {         // System.out.println("preferredLayoutSize");         return computeLayoutSize(parent, hpad, vpad);     }     /** Find the minimum Dimension for the       * specified container given the components therein.      */     public Dimension minimumLayoutSize(Container parent)  {         // System.out.println("minimumLayoutSize");         return computeLayoutSize(parent, 0, 0);     }     /** The width of each column, as found by computLayoutSize( ). */     int[] widths;     /** The height of each row, as found by computLayoutSize( ). */     int[] heights;     /** Compute the size of the whole mess. Serves as the guts of       * preferredLayoutSize( ) and minimumLayoutSize( ).      */     protected Dimension computeLayoutSize(Container parent, int hpad, int vpad) {         if (!validWidths)             return null;         Component[] components = parent.getComponents( );         Dimension contSize = parent.getSize( );         int preferredWidth = 0, preferredHeight = 0;         widths = new int[COLUMNS];         heights = new int[components.length / COLUMNS];         // System.out.println("Grid: " + widths.length + ", " + heights.length);         int i;         // Pass One: Compute largest widths and heights.         for (i=0; i<components.length; i++) {             int row = i / widthPercentages.length;             int col = i % widthPercentages.length;             Component c = components[i];             Dimension d = c.getPreferredSize( );             widths[col] = Math.max(widths[col], d.width);             heights[row] = Math.max(heights[row], d.height);         }         // Pass two: aggregate them.         for (i=0; i<widths.length; i++)             preferredWidth += widths[i] + hpad;         for (i=0; i<heights.length; i++)             preferredHeight += heights[i] + vpad;         // Finally, pass the sums back as the actual size.         return new Dimension(preferredWidth, preferredHeight);     }     /** Lays out the container in the specified panel. */     public void layoutContainer(Container parent) {         // System.out.println("layoutContainer:");         if (!validWidths)             return;         Component[] components = parent.getComponents( );         Dimension contSize = parent.getSize( );         int x = 0;         for (int i=0; i<components.length; i++) {             int row = i / COLUMNS;             int col = i % COLUMNS;             Component c = components[i];             Dimension d = c.getPreferredSize( );             int colWidth = (int)(contSize.width * widthPercentages[col]);             if (col == 0) {                 x = hpad;             } else {                 x += hpad * (col-1) + (int)(contSize.width * widthPercentages[col-1]);             }             int y = vpad * (row) + (row * heights[row]) + (heights[row]-d.height);             Rectangle r = new Rectangle(x, y,                 colWidth, d.height);             c.setBounds(r);         }     } }

See Also

For more on layouts, see Jim Elliott's RelativeLayout, described in http://www.onjava.com/pub/a/onjava/2002/09/18/relativelayout.html. This is not to be confused with the like-named but much simpler RelativeLayout in the source distribution accompanying the book; Jim's is more complete.

As mentioned in the Introduction, there are many good books on windowed application programming with Java. O'Reilly's Java Swing discusses the many Swing components not covered here, such as JTable, JScrollPane, JList, and JTree, and many more. My JabaDex application contains examples of many of these, and some are used in later recipes in this book (for example, JTree is discussed in Recipe 19.9).



Java Cookbook
Java Cookbook, Second Edition
ISBN: 0596007019
EAN: 2147483647
Year: 2003
Pages: 409
Authors: Ian F Darwin

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net