Friday, July 17, 2009

Writing Custom Java Layout Managers

Why Write Custom Layout Managers?
A Swing application depends on layout managers to intelligently position a collection of GUI components in a window. Swing provides a number of general purpose layout managers for this purpose.
But sometimes it is hard to shoehorn a general purpose layout manager for a specific purpose and the result is often less than satisfactory. Third-party layout managers are available that try to address some of the perceived shortcomings of Swing's own offerings. In addition, GUI layout editors are available that visually assist inprogramming a GUI layout.
Nonetheless, it is often more efficient and more effective to write a custom layout manager designed for a specific layout requirement rather than struggle with the standard layout managers. And it's easier than one may imagine.
LayoutManager and LayoutManager2 API
Java's LayoutManager interface declares the five methods that have to be implemented in a valid layout manager:
  • addLayoutComponent, specifying name
  • removeLayoutComponent
  • layoutContainer
  • minimumLayoutSize
  • preferredLayoutSize
AddLayoutComponent and removeLayoutComponent are called to inform the LayoutManager about Components added to a Container using it. LayoutContainer implements the layout strategy, changing the x and y pixel position and the dimension of the Components as necessary. MinimumLayoutSize and preferredLayoutSize let the Container find out the minimum required and preferred layout size given the Components to be laid out in it.
LayoutManager2 extends LayoutManager with five more methods:
  • addLayoutComponent, specifying constraints object
  • getLayoutAlignmentX
  • getLayoutAlignmentY
  • invalidateLayout
  • maximumLayoutSize
The new addLayoutComponent allows you to pass an object specifying properties to constrain the Component in the layout. GetLayoutAlignmentX and getLayoutAlignmentY specifies the alignment of the Component within the Container. InvalidateLayout tells the LayoutManager to discard cached information about the layout and recalculate. MaximumLayoutSize specifies maximum dimension of the Container.

Example: Grid Layout with Positionable Components
Let's say we need to draw components on a grid, for example, but need to specify the positions of the components arbitrarily at any time. On the first take we'd probably choose Java's GridLayout as the layout manager - that is, until we discover that it will only position components in sequence. Afterwards, the programmer has no control over their positions. The alternative would be to use GridBagLayout, but that's way too complicated and difficult to control for such a simple task. The solution is to write our own LayoutManager.

We call our grid layout with positionable Components the PositionableGridLayout class. The minimumLayoutSize for this case should be the extent of the layout embodying the outermost component. That would be our preferredLayoutSize as well. In other words, minimumLayoutSize is the x grid position of the easternmost Component times the width of each grid and the y grid position of the southernmost Component times the height of each grid. LayoutContainer calculates the pixel location of each Component given its grid position, and relocates it with a call to the Component's setBounds method if necessary. Note that we did not implement addLayoutComponent and removeLayoutComponent because nothing special needs to be done when a Component is added to or removed from the Container.

An automatic LayoutManager - that is, one that would not allow the programmer to control the location and size of each Component - would normally be able to work alone. For a LayoutManager that allows the programmer to individually fix the location and size of the Components, however, an assisting class is required. Such is the case with Java's GridBagLayout, which requires GridBagConstraints to work. In our example we need to be able to position any Component at an arbitrary location at any time. Following the example of GridBagLayout, we implement an assisting class, the PositionableGridConstraints, which allows us to specify the grid position of an added component. The PositionableGridConstraints class is fairly simple to implement. It mainly stores away the grid position of a Component.

/**
* Constructs a PositionableGridConstraints.
*/
public PositionableGridConstraints( int gridx, int gridy, ... )
{

this.gridx = gridx;
this.gridy = gridy;
:
}

We need to assign the grid constraints to the Component, of course. Again, following the example set by Java's GridBagLayout, we implement a setConstraints method in PositionableGridLayout whose purpose is to associate the grid constraints to a Component in a hashtable using the Component as the key.

Example: Row Layout with Proportionally Sized Components
RowLayout lets the programmer specify the space a Component is allowed to occupy horizontally relative to the width of the entire Container. But if the component has a preferred size, its aspect ratio is preserved. RowLayout implements LayoutManager2 so that we can specify the proportion as a constraint.
Again, minimumLayoutSize and also maximumLayoutSize calls preferredLayoutSize, but the calculation of preferred size is slightly more involved. Its width is the width of the Container. Its height is the height of the tallest Component.
AddLayoutComponent and removeLayoutComponent now has a function. They are used to add and remove the proportionality constraints to a component.
Download
Source code for PositionableGridLayout, PositionableGridConstraints and RowLayout.
References
  1. Daniel Dee, Implementing a Grid Layout Manager With Positionable Components, Java Developer's Journal, 1997.
  2. Using Layout Managers, Java Tutorials, Sun.
  3. Patrick Niemeyer and Jonathan Knudsen, Learning Java, 3rd ed., O'Reilly (2005), Chapter 19.


No comments:

Post a Comment