Writing layout managers
One of the coolest things nobody seems to do in Java is writing custom Layout managers. A Layout Manager is the object responsible for positioning the buttons on a JPanel. In more general terms, it determines the size and position of the components within a container
.
Most java programmers will be familiar the default FlowLayout manager, the slightly more usable BorderLayout and the GridLayout, which seems to have been added for designing calculators. There's also a GridBagLayout, which lets you customise things by adding a load of constraints to every component and a few others I won't mention here.
Thing is, the simple layout managers never seem to do what you want them to (although, if you get to know them better, there's quite a few things they can do right) while the more customisable ones require lots of constraints to be set which means you might need up to 5 lines just to add a component to a container. If you have many components to add this becomes a considerable body of code. To make things worse the way the constraints are interpreted will often depend on the components that came before, which means switching two components around takes a lot more effort than it should.
So why use them at all? Viva la NetBeans ey? Well... Thing is...
I like layout managers. They contain very little "real" code and just let you lay out things the way you want to. They're re-usable and resizable (just like web pages) and once you've got a layout manager working the way you want to adding new components can be handled in a single line.
Let me show you the 'interface':
public class ExampleLayout implements LayoutManager
{
public void layoutContainer(Container parent)
{
// This is where you do most of the work
}
public Dimension preferredLayoutSize(Container parent)
{
// This is where you determine the size your layout will 'prefer'. IE which
// size it gets if nothing else overrules it.
return Dimension(width, height)
}
public Dimension minimumLayoutSize(Container parent)
{
// Here you can specify a minimum size. I usually just do this:
return preferredLayoutSize(parent);
}
public void addLayoutComponent(String where, Component comp)
{
// If you want to do things everytime an object is added do it here. Keep in
// mind the method is just called to _let you know_ something was added, so
// you don't _have_ to do anything here.
}
public void removeLayoutComponent(Component comp)
{
// Same as for adding.
}
}Of these five methods, I usually just implement two: layoutContainer and preferredLayoutSize. The minimum layout size method can just point straight to the preferred size one, and instead of using the add & remove methods to update the layout every time you add or remove something I let the layoutContainer method calculate everything all at once. If all goes well this method is only called once anyway, so performance isn't an issue.
Layout manager essentials
The following classes and methods are vital to building a layout manager:
- parent
- This is the object you're laying out. You will need it to access the components you're going to lay out, as well as the size of the area available to you and the area's insets
parent.getInsets()- This method returns an Insets object that
specifies the space that a container must leave at each of its edges
. You use it to get your initial x & y coordinates. parent.getSize()- This method returns a Dimension object that represents the current size of the parent container. To get the width of the area you're laying out call
parent.getSize().width. parent.getComponents()- Returns an array containing the components for you to lay out.
Component.setBounds(x, y, width, height)- And this is where the magic happens, using this method you assign every component its position and size.
An example
The following layout manager lets you create a layout like you'd see in an FTP client, or any other environment where you have two lists and some buttons in the middle. It has an equally sized left & right pane that grow and shrink as you resize the frame. The middle pane gets the width of whatever you add as a second component and the height of every component is set to fill the parent container.
It's not a flexible manager and the way it hides the 4th, 5th etc component is downright dirty. But it does its job beautifully. Have fun!
import java.awt.*;
/**
* A layout manager meant for the top level of a file/list view a la WS_FTP with 2 equally sized
* lists seperated by a button panel. Lays out the first three components side by side and hides all
* the others. The centre component is assigned its preferred width while the side components are
* kept at an equal width.
*
* All three components are given an equal height. If the parent container size is smaller than the
* preferred size the layout overflows, if the parent container is larger the components are scaled
* up: all three components are stretched to match the parents heigth, the outer components are
* stretched to fill the parents width
*/
public class DoubleListLayout implements LayoutManager
{
/**
* Creates a new DoubleListLayout layout manager with the default settings
*/
public DoubleListLayout() {}
/**
* Creates a new DoubleListLayout layout manager witht the given settings
* @param HGap the new horizontal gap (spacing between the components) to use
*/
public DoubleListLayout(int HGap)
{
this.HGap = HGap;
}
public void layoutContainer(Container parent)
{
// Get height & width
preferredLayoutSize(parent);
Insets insets = parent.getInsets();
int x = insets.left;
int y = insets.top;
int wTotal = parent.getSize().width - 2 * x;
int widthSides2 = (wTotal - widthMid - 2 * HGap) / 2;
if (widthSides > widthSides2) widthSides2 = widthSides; // overflow!
int height2 = parent.getSize().height - 2 * y;
if (height > height2) height2 = height; // allow overflow
Component[] components = parent.getComponents();
if (components[0] != null)
components[0].setBounds(x, y, widthSides2, height2);
if (components[1] != null)
components[1].setBounds(x + widthSides2 + HGap, y, widthMid, height2);
if (components[2] != null)
components[2].setBounds(x + widthSides2 + widthMid + 2 * HGap, y,
widthSides2, height2);
// Hide all but the first three components
int n = components.length;
for (int i = 3; i < n; i++) components[i].setVisible(false);
}
public Dimension preferredLayoutSize(Container parent)
{
Component[] components = parent.getComponents();
int x;
// Set height
height = 0;
for (int i = 0; i < 3; i++) {
if (components[i] != null) {
x = components[i].getPreferredSize().height;
if (x > height) height = x;
}
}
// Set width
widthMid = 0;
if (components[1] != null) widthMid = components[1].getPreferredSize().width;
widthSides = 0;
if (components[0] != null) widthSides = components[0].getPreferredSize().width;
if (components[2] != null) {
x = components[2].getPreferredSize().width;
if (x > widthSides) widthSides = x;
}
return new Dimension(2 * widthSides + widthMid, height);
}
public Dimension minimumLayoutSize(Container parent)
{
return preferredLayoutSize(parent);
}
public void addLayoutComponent(String where, Component comp) {}
public void removeLayoutComponent(Component comp) {}
/**
* Returns the current value of HGap, which determines the horizontal spacing between
* components
* @return the current value of HGap
*/
public int getHGap()
{
return HGap;
}
/**
* Sets the horizontal spacing between components to the given value
* @param gap the new value for HGap
*/
public void setHGap(int gap)
{
HGap = gap;
}
private int HGap = 0;
private int widthMid, widthSides, height;
}Jul 12th, 2008
Comments
No comments yet! Feel free to post some using the form below.
If you wish to add code to your comment you can use code tags, like this: <code class="php">yourCodeHere</code>.
Quite a large number of languages are supported, although I can't guarantee it'll be pretty. Inside the code tags you can use any characters except for the string "</code>".