|
software | articles | professional |
| Articles index | |||
Create a Custom Swing/AWT Layout Manager in JavaLast updated: 2007-08-22One 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
^-- 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 The goal of RowLayout
It is for that reason that I created
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 Let's take a look at the full code listing of RowLayout next. Full listing of RowLayoutBelow is the entire listing for
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 The LayoutManager interfaceAll LayoutManagers must implement the
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
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);
}
}
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 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;
}
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 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
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, ConclusionHopefully 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!
|
|||
| Articles | blog | index |