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
MS-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 13-14, where the labels column and the textfield column have different widths. Using the big five layouts, there’s no good way to get this and still ensure that the columns line up and that you have control over the 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.
Here are the methods for the
LayoutManager
interface:
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( )
, will normally loop through all the
components -- either in the HashMap
if using
constraints, or in 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.
Finally, 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
multi-column layout shown in Figure 13-14. 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 13-14. This program simply creates a
JFrame
, gets the contentPane
container, and sets its layout to an instance of
EntryLayout
, passing an array of two
double
s representing the relative widths (decimal
fractions, not percentages) into the EntryLayout
constructor. Then we add an even number of components, and call
pack( )
-- which will in turn
call 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.
Finally, let’s look at the code for the layout manager itself,
shown in Example 13-8. 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 13-8. EntryLayout.java
// package com.darwinsys.entrylayout; import java.awt.*; import java.util.*; /** A simple layout manager, for Entry areas like: * <PRE> * Login: ______________ _ * Password: ______________ _ * </PRE> * ... */ 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( ); 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]); Rectangle r = new Rectangle( col == 0 ? 0 : hpad * (col-1) + (int)(contSize.width * widthPercentages[col-1]), vpad * (row) + (row * heights[row]) + (heights[row]-d.height), colWidth, d.height); // System.out.println(c.getClass( ) + "-->" + r); c.setBounds(r); } } }
As
mentioned in the Introduction, there
are a number of good books on window programming with Java. These
discuss 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 Section 19.10.
3.147.126.211