Articles index








Create a Custom Swing/AWT Layout Manager in Java

Last updated: 2007-08-22

One of the great things about Java AWT and Swing is their flexibility. As a Java developer, you can fine-tune your applications' GUIs as much as you like while retaining cross-platform compatibility. This is particularly true when it comes to LayoutManagers. LayoutManagers allow you to position your GUI components however you like, while still remaining flexible enough to account for differences in fonts, look-and-feels, component shapes, and other variances the occur between platforms and implementations.

The Java Development Kit (JDK) ships with a number of LayoutManagers to arrange your components in whatever layout you might have in mind. Some—like BorderLayout, FlowLayout and GridLayout—are simple, requiring little configuration and serving specific needs. Others—for example, GridBagLayout, SpringLayout and GroupLayout—are more complex, but generally allow you to define any layout you can think of. Sometimes, however, you come across a simple, specific layout need that is not met by one of the existing simpler LayoutManagers. You could use one of the more complex LayoutManagers to solve your need. Or, you can spin your own.


^-- What I wanted
 

^-- What GridLayout would provide
 

^-- What BoxLayout would provide

I came across such a need awhile ago. In a nutshell, I wanted a LayoutManager that would stack my components vertically, aligned to the left. I couldn't use GridLayout because I wanted my components to retain their own preferred sizes, particularly their preferred height; GridLayout forces each component to the same dimensions. BoxLayout came closer, but its insistence on aligning components to the center was a deal-breaker. Furthermore, I later found situations in which I wanted all my components to share the width of the widest component, something that BoxLayout was incapable of.

The goal of RowLayout

It is for that reason that I created RowLayout. Let's first define what this LayoutManager is designed to do:

  • Stack components vertically, in the order in which they are added.
  • Retain each component's preferred height.
  • Provide a switch to indicate whether each component should retain its preferred width, or if the width of the widest component should define the width of every other component.
  • Allow an int value to indicate, in pixels, the vertical gap between components.

What does a LayoutManager manage?

First, let's take a step backward. What is it, exactly, that an AWT LayoutManager does? Essentially, a LayoutManager's purpose is to properly size—and in many cases, properly position—components. A LayoutManager is assigned to a container, and it is that LayoutManager's job to determine and set the correct x and y coordinates, along with the correct width and height, for each component contained within that container. It does this by performing calculations based on its own rules, and then calling setBounds(x, y, width, height) on each component. It is also the job of the LayoutManager to determine the minimum and preferred sizes of its component. Normally, these sizes are calculated based on the sizes and positions of the contained components.

Let's take a look at the full code listing of RowLayout next.

Full listing of RowLayout

Below is the entire listing for RowLayout:

package com.taubler.guicomponents;

import java.awt.*;

public class RowLayout implements LayoutManager {

	private int prefWidth;
	private int prefHeight;
	private int minWidth;
	private int minHeight;
	private int rowGap = 0;
	private boolean stretch;

	public RowLayout() {}

	public RowLayout(boolean stretchComponents) {
		this.stretch = stretchComponents;
	}

	public RowLayout(int gapSize) {
		setRowGap(gapSize);
	}

	public RowLayout(int gapSize, boolean stretchComponents) {
		this.stretch = stretchComponents;
		setRowGap(gapSize);
	}

	public void setRowGap(int gapSize) {
		rowGap = gapSize;
	}

	public void setStretch(boolean stretchComponents) {
		this.stretch = stretchComponents;
	}

	 // required but unused
	public void addLayoutComponent(String name, Component comp) {
	}

	 // required but unused
	public void removeLayoutComponent(Component comp) {
	}

	public void layoutContainer(Container parent) {
		setSizes(parent);
		Insets insets = parent.getInsets();
		int x = insets.left;
		int y = insets.top;
		int x2 = insets.right;
		int wDiff = x + x2;

		int ncomponents = parent.getComponentCount();
		for (int i = 0; i < ncomponents; ++i) {
			java.awt.Component comp = parent.getComponent(i);
			java.awt.Dimension d = comp.getPreferredSize();
			if (d == null)
				d = comp.getMinimumSize();
			int w = 0;
			int h = 0;
			if (d != null) {
				w = d.width;
				h = d.height;
			}
			if (stretch) {
				w = prefWidth;
			}
			if (prefWidth - wDiff < w) {
				w = prefWidth - wDiff;
			}
			comp.setBounds(x, y, w, h);
			y += (h + rowGap);
		}
	}

	public Dimension minimumLayoutSize(Container parent) {
		setSizes(parent);
		Dimension dim = new Dimension(minWidth, minHeight);
		return dim;
	}

	public Dimension preferredLayoutSize(Container parent) {
		setSizes(parent);
		Dimension dim = new Dimension(prefWidth, prefHeight);
		return dim;
	}

	private void setSizes(Container parent) {
		int nComps = parent.getComponentCount();
		Dimension d = null;

		// reset widths and heights
		prefWidth = 0;
		prefHeight = 0;
		minWidth = 0;
		minHeight = 0;

		Insets insets = parent.getInsets();
		int x = insets.left;
		int x2 = insets.right;
		int wDiff = x2 + x;

		int parentWidth = parent.getWidth();

		prefWidth += wDiff;

		for (int i = 0; i < nComps; i++) {
			Component c = parent.getComponent(i);
			if (c.isVisible()) {
				d = c.getPreferredSize();
				if (d == null)
					d = c.getMinimumSize();
				int thisWidth = d.width;
				if (thisWidth > prefWidth) {
					prefWidth = thisWidth + wDiff;
				}
				prefHeight += d.height;
				prefHeight += rowGap;
			}
		} // end for

		// below, parentWidth == 0 when the frame first launches
		if (parentWidth != 0 && prefWidth > parentWidth) {
			prefWidth = parentWidth;
		}

        prefHeight += insets.top + insets.bottom;
		minHeight = prefHeight;
		minWidth = prefWidth;
	}

}

First, let's look at some of the mechanisms designed to support the stated goals of RowLayout. You might have noticed two fields, stretch and rowGap, designed respectively to support the last two stated goals. stretch is a boolean indicating whether or not components should retain their own width (false), or whether their widths should be defined by that of the widest component (true). rowGap is an int that defines, in pixels, the vertical gap between components. As you'd expect, both fields can be set via bean-style setter methods; as well, there are overloaded constructors providing users with the options to initially set these values.

The LayoutManager interface

All LayoutManagers must implement the LayoutManager interface; RowLayout is no exception. The required methods from that interface include:

  • void addLayoutComponent(String name, Component comp);
  • void layoutContainer(Container parent);
  • Dimension minimumLayoutSize(Container parent);
  • Dimension preferredLayoutSize(Container parent);
  • void removeLayoutComponent(Component comp);
Of those within RowLayout, addLayoutComponent and removeLayoutComponent are the easiest to describe. RowLayout does not use those method calls, and so just provides an empty implementation.

Minimum and preferred sizes

minimumLayoutSize(Container parent) and preferredLayoutSize(Container parent) are methods that the container calls to determine its smallest acceptable size, and preferred size, respectively. So how do we determine those sizes? Both methods are passed a reference to the Container to which the LayoutManager has been assigned. This is useful, among other reasons, because it allows us access to the Components whose sizes we need to take into account. In LayoutManager, we iterate through those components in a setSizes(Container) method, which we will go through in a little bit. Suffice it to say for now that setSizes iterates through the container's components and calculates the values of the prefWidth, prefHeight, minWidth and minHeight fields. After delegating to setSizes(Container), minimumLayoutSize and preferredLayoutSize define the Dimension that will be returned, and return it.

Laying out the container

public void layoutContainer(Container parent) {
	setSizes(parent);
	Insets insets = parent.getInsets();
	int x = insets.left;
	int y = insets.top;
	int x2 = insets.right;
	int wDiff = x + x2;

	int ncomponents = parent.getComponentCount();
	for (int i = 0; i < ncomponents; ++i) {
		java.awt.Component comp = parent.getComponent(i);
		java.awt.Dimension d = comp.getPreferredSize();
		if (d == null)
			d = comp.getMinimumSize();
		int w = 0;
		int h = 0;
		if (d != null) {
			w = d.width;
			h = d.height;
		}
		if (stretch) {
			w = prefWidth - wDiff;
		}
		if (prefWidth - wDiff < w) {
			w = prefWidth - wDiff;
		}
		comp.setBounds(x, y, w, h);
		y += (h + rowGap);
	}
}
The layoutContainer(Container parent) method is a bit more involved. As with the previous two methods, we begin by delegating to setSizes to ensure that the preferred with has been set.

Next, we obtain an instance to the container's insets. This is important, because the container might have a border set, or might otherwise have non-zero insets. We use the container's insets.top value (which we set to y) as the y coordinate for the first component. The values of insets.left and insets.right are assigned to x and x2 respectively, and added together; that sum is assigned to wDiff.

That last value tells us how much total horizontal space we need to subtract from the container's width in order to get the amount of horizontal space available to components. In other words, the container's width, minus its left and right insets, leaves us with the space in the middle to fit a component. Finally, as with y, x represents the x coordinate for the first component (and all subsequent components).

Next, we iterate through the container's components. For each component, we first obtain its preferred size and store it in Dimension d. Some components cannot logically provide a preferred size, and thus return null. In those cases, we then grab the component's minimum size and store it in d. We then declare int values of w and h, which will represent the component's final width and height, respectively, and set then to d.width and d.height. We then check to see if our RowLayout is set to stretch components. If that's the case, then we set w to prefWidth (which represents the container's preferredWidth and, as you'll see in a little bit, it was previously set to the width of the widest component) minus wDiff (the container's left and right insets).

Finally, we have this bit:

if (prefWidth - wDiff < w) {
	w = prefWidth - wDiff;
}

This is to handle cases where the component (in combination with its insets) has been resized to be narrower than the component through which we are currently iterating, In such as case, we simply set the component to be as wide as it possibly can be: the container's width minus its left and right insets. Finally, we set the component's bounds, and increment the current y coordinate, for use by the next component, by the current component's preferred height and any vertical gap that may have been set for this RowLayout.

Setting the sizes

private void setSizes(Container parent) {
	int nComps = parent.getComponentCount();
	Dimension d = null;

	// reset widths and heights
	prefWidth = 0;
	prefHeight = 0;
	minWidth = 0;
	minHeight = 0;

	Insets insets = parent.getInsets();
	int x = insets.left;
	int x2 = insets.right;
	int wDiff = x2 + x;

	int parentWidth = parent.getWidth();

	prefWidth += wDiff;

	for (int i = 0; i < nComps; i++) {
		Component c = parent.getComponent(i);
		if (c.isVisible()) {
			d = c.getPreferredSize();
			if (d == null)
				d = c.getMinimumSize();
			int thisWidth = d.width;
			if (thisWidth > prefWidth) {
				prefWidth = thisWidth + wDiff;
			}
			prefHeight += d.height;
			prefHeight += rowGap;
		}
	} // end for

	// below, parentWidth == 0 when the frame first launches
	if (parentWidth != 0 && prefWidth > parentWidth) {
		prefWidth = parentWidth;
	}

	prefHeight += insets.top + insets.bottom;
	minHeight = prefHeight;
	minWidth = prefWidth;
}
Now let's look at the other major method in RowLayout, setSizes(Container parent). This is the method that actually calculates all of the required locations and sizes, for the container as well as for each component.

It begins by resetting the member variables prefWidth, prefHeight, minWidth and minHeight. Like LayoutContainer(), it then retrieves the container's insets, and calculates the values for x, x2, and wDiff. Then it sets the variable parentWidth to the width of the container. Finally, it adds the value of wDiff (which, remember, if the sum of the containers left and right insets) to prefWidth; since prefWidth was reset to zero, this means that the containers preferred width, at an absolute minimum, will be equal to the sum of the container's left and right insets.

Now that the method is finished with its setup, we move on to iterating through the contained components. The first thing we need to check for on every component is its visibility; if the component is not visible, it should simply be skipped. Barring that, we retrieve the component's preferred size (or minimum size, if the preferred size is return as null) and store it in Dimension d. We then compare d.width to prefWidth. If d.width is larger, then its value—plus wDiff, the sum of insets.left and insets.right—is assigned to prefWidth. This ensure that once we've iterated through all of the components, prefWidth will represent the width of the widest component plus the left and right insets. We then increment the value of prefHeight by both d.height (the height of the current component) and any vertical gap that may have been assigned to this RowLayout.

Once we've iterated through the components, we still have a bit of work to do. One thing we need to do is to check the actual width of the parent component, and ensure that it is not exceeded by prefWidth. This can occur if, for example, the RowLayout has been assigned to a frame, and the user resizes the frame manually to make it smaller. In such a case, prefWidth is simply set to equal parentWidth (the actual width of the container) as shown below:

if (parentWidth != 0 && prefWidth > parentWidth) {
	prefWidth = parentWidth;
}

Note that we also test whether parentWidth == 0, and bypass the action if that's the case. The first time the container becomes visible and setSizes() is called, parentWidth will be zero... but we certainly don't want to set the preferred width to zero!

We then add the sum of the container's top and bottom insets to prefHeight, to ensure that our container will be tall enough to include any borders it might contain. Finally, like many layouts, RowLayout simply returns the same value for minimum width/height as it does for preferred width/height, so we just set minHeight and minWidth to prefHeight and prefWidth, respectively.

Conclusion

Hopefully this article has provided you with a LayoutManager that you'll find useful in your applications. Moreover, I hope that you know have the ability to take advantage of Swing's flexibility and begin creating creating your own layouts!



Privacy Policy: Any information collected, whether from online help, registration, or any other means, will be used only for support purposes. Such uses are: to send customers registration information, to verify that a user is registered in order to provide support, and to notify the user if an important or useful bug fix or upgrade has been made. Under no circumstances will customer information be sold or otherwise given to any other party, person, or organization. We're in the business to develop software, not sell people's information.