Introduction
Design Model
Required Classes
Example Layout Manager
Layout managers in Java are used by container objects to manage the location of the child components within the real estate of the container. Layout managers are objects that do all the work for the containers. These objects, however, were designed in the early days of Java and thus do not conform to the JavaBeans specification. Specifying which layout managers will be used and how they will work is, therefore, problematic.
Within the IDE we will have to find out from the user which layout manager is to be used and then how to layout each component within the container. Since this process requires some up front work on the part of the developer, we will also need a way to register layout managers with the IDE so that the up front work can be saved. The Layout Manager Dialog is used to select the layout manager that the container will use to layout components. The Constraints Editor Dialog is used to specify the constraints for each contained component.
The programmer needs to be able to provide as much information as possible to allow us to correctly generate code that will create and use the layout managers. In addition, the programmer should have as much information as is possible at his disposal to make it easy to connect layout managers to containers and components to the layout managers. Complex layout managers such as the GridbagLayout make this hard by requiring a constraint object that is a collection of arbitrary numeric or character fields. Many layout managers also hide the information that is required to manage the layout managers. The ease with which complex constraints can be specified relies on the amount of information that was supplied when the layout manager was registered. If this information is left out then the programmer will have a hard time adding complex constraints.
Registration of a layout manager is a two step process. The first step involves the creation of helper classes. These classes provide customizers for the layout managers and the constraints that these managers use. In addition information is provided to webAF to allow it to generate code that reflects the layout managers and the constraints.
The programmer will use the information supplied when the layout manager was registered to, first, select a layout manager and, second, to specify the constraints for each contained component. To select a layout manager, the programmer will be presented with a list of all registered layout managers. To modify the constraints on a component, the programmer will be presented with a constraint customizer to edit the constraint variables and their possible values.
The programmer will have to provide classes that implement the following interfaces. Note that these are design time only classes and can not ship with an applet or application. The interfaces are com.sas.ide.beans.LayoutInfo, com.sas.ide.beans.LayoutCustomizer, com.sas.ide.beans.ConstraintCustomizerInterface, and com.sas.ide.beans.LayoutConstructorInfo.
/**
* com.sas.ide.beans.LayoutInfo is the interface to allow the layout manager
developer
* to provide additional DesignTime help to webAF. The implementing class is
* searched for first in the package of the layout manager and then in the
* com.sas.ide.beans package. The implementing class must be named
<LayoutManagerName>LayoutInfo
**/
public interface LayoutInfo
{
/**
* getLayoutCustomizer returns the class of a component that will be placed
* in a dialog by webAF. This class is used to edit the properties of the
* Layout Manager itself. It must implement the LayoutCustomizer interface.
* This method should return null. If it returns null then webAF will edit the public
fields of the Layout Manager.
**/
public Class getLayoutCustomizer();
/**
* getLayoutConstraintCustomizer returns the class of a component that will be
* placed in a dialog by webAF. This class is used to edit the constraints of a
* component in a container that is using this layout manager. The class returned
* must implement the com.sas.ide.beans.ConstraintCustomizerInterface. This method
* can return null only if there is no constraint.
**/
public Class getLayoutConstraintCustomizer();
/**
* getLayoutConstraint returns the constraint object used by the layout manager
* for the specified component. This is used primarily by webAF to add the
* component back to the top level container at runtime and subsequent edits
* of the frame. This method may have to have native components to get to
* private fields in the layout managers
**/
public Object getLayoutConstraint(java.awt.Component comp);
}
/**
* com.sas.ide.beans.LayoutCustomizer is the interface to allow the layout manager
customizer
* to communicate with webAF. The implementing class is defined by the LayoutInfo
implementer
* of the layout manager class. This interface only needs to be implemented if the
layout manager is very complicated.
**/
public interface LayoutCustomizer
{
/**
* setLayoutManager provides the customizer with the layout manager object.
* This is the object that will be modified.
**/
public void setLayoutManager(java.awt.LayoutManager l);
/**
* setPropertyEditor provides the property sheet property editor to the
* layout customizer to allow it to track property changes within the layout manager
object.
**/
public void setPropertyEditor(com.sas.ide.beans.LayoutManagerEditor e);
}
/**
* com.sas.ide.beans.ConstraintCustomizerInterface allows the constraintCustomizer to
* be called before the component is actually added to the container. In that case
* updateComponent needs to perform the add directly.
**/
public interface ConstraintCustomizerInterface
{
/**
* setComponent allows the customizer to query the component and
* its container for information about the constraints associated
* with this component. Note that some (most) layout managers do
* not provide sufficient support for this to work. In that case
* the only recourse is native methods.
**/
public void setComponent(java.awt.Component c, java.awt.Container container);
/**
* updateComponent allows the customizer to update the constraints in the
* container of component. If the component does not have a parent yet
* then it should be added to the passed in container.
* This allows webAF to display a customizer when the component is dropped
* into a container before it is actually added to the container
**/
public void updateComponent(java.awt.Component c, java.awt.Container container);
}
/**
* com.sas.ide.beans.LayoutConstructorInfo is the interface to allow the layout manager developer
* to provide the IDE a way to create an instance of the layout if the layout does not have a default
* constructor or if an instance besides the default is desired. In the creation of the layout, a
* dialog can also be displayed to provide the user with additional customization of the constructor.
* This interface only needs to be implemented if the layout manager does not have a default
* constructor or if behavior besides the default constructor is desired.
* The implementing class must be named <LayoutManagerName>LayoutInfo as it extends from the
* com.sas.ide.beans.LayoutInfo interface.
**/
public interface LayoutConstructorInfo extends LayoutInfo
{
/**
* Creates an instance of the layout if a non default constructor is wanted to be used or
* if a non default constructor is present. Should return null if the default constructor
* is desired, otherwise, it should return an instance of the layout. The object passed into the
* method is the container or target that the layout manager is being set on.
**/
public LayoutManager createLayoutInstance(Object target);
}
What follows are an example third party layout manager and the classes that are required to register the layout manager with webAF.
/*
=====================================================================
CastleLayout.java
Created by Claude Duguay
Copyright (c) 1998
=====================================================================
*/
package com.sas.layoutTest;
import java.util.Hashtable;
import java.io.Serializable;
import java.awt.LayoutManager2;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Container;
import java.awt.Insets;
/**
* CastleLayout allows a component to occupy a central area
* with surrounding members named "North", "South",
"East",
* "West", "NorthEast", "NorthWest",
"SouthEast" & "SouthWest".
*
* The "North", "South", "East", "West",
"NorthEast", NorthWest",
* "SouthEast" and "SouthWest" components get laid out according
* to the larger of their preferred sizes and the constraints of
* the container's size. The "Center" component will get any
* space remaining.
*
* @version 1.0
* @author Claude Duguay
**/
public class CastleLayout extends AbstractLayout
implements LayoutManager2, Serializable
{
public static final String EAST = "East";
public static final String WEST = "West";
public static final String NORTH = "North";
public static final String SOUTH = "South";
public static final String CENTER = "Center";
public static final String NORTHEAST = "NorthEast";
public static final String NORTHWEST = "NorthWest";
public static final String SOUTHEAST = "SouthEast";
public static final String SOUTHWEST = "SouthWest";
Hashtable table = new Hashtable(); // NOTE: this variable is package scope for layout
constraint customizers
private static final int PREFERRED = 1;
private static final int MINIMUM = 0;
/**
* Constructs a CastleLayout with no gaps between components.
**/
public CastleLayout()
{
super();
}
/**
* Constructs a CastleLayout with the specified gaps.
* @param hgap The horizontal gap
* @param vgap The vertical gap
*/
public CastleLayout(int hgap, int vgap)
{
super(hgap, vgap);
}
// ----------------------
// Private helper methods
// ----------------------
private Component getComponent(String name)
{
if (!table.containsKey(name)) name = CENTER;
return (Component)table.get(name);
}
private boolean isVisible(String name)
{
if (!table.containsKey(name)) return false;
return getComponent(name).isVisible();
}
private void setBounds(String name, int x, int y, int w, int h)
{
if (!isVisible(name)) return;
getComponent(name).setBounds(x, y, w, h);
}
private Dimension getSize(int type, String name)
{
if (!isVisible(name)) return new Dimension(0,
0);
if (type == PREFERRED)
return
getComponent(name).getPreferredSize();
if (type == MINIMUM)
return
getComponent(name).getMinimumSize();
return new Dimension(0, 0);
}
/**
* Adds the specified component to the layout, using the
* specified constraint object.
* @param comp The component to be added
* @param name The name of the component position
**/
public void addLayoutComponent(Component comp, Object name)
{
if (!(name instanceof String))
{
throw new
IllegalArgumentException(
"constraint object must be a valid name");
}
// Assume a null name is the same as
"Center"
if (name == null) name = CENTER;
table.put((String)name, comp);
}
/**
* Removes the specified component from the layout.
* @param comp The component to be removed
*/
public void removeLayoutComponent(Component comp)
{
table.remove(comp);
}
/**
* Returns an Insets object indicating sized edge for
* the minimum and preferred layouts.
* @param type The type, either PREFERRED or MINIMUM
**/
private Insets calculateLayoutSize(int type)
{
Dimension size = new Dimension(0, 0);
int northHeight = 0;
int southHeight = 0;
int eastWidth = 0;
int westWidth = 0;
size = getSize(type, NORTH);
northHeight = Math.max(northHeight,
size.height);
size = getSize(type, SOUTH);
southHeight = Math.max(southHeight,
size.height);
size = getSize(type, EAST);
eastWidth = Math.max(eastWidth, size.width);
size = getSize(type, WEST);
westWidth = Math.max(westWidth, size.width);
size = getSize(type, NORTHWEST);
northHeight = Math.max(northHeight,
size.height);
westWidth = Math.max(westWidth, size.width);
size = getSize(type, SOUTHWEST);
southHeight = Math.max(southHeight,
size.height);
westWidth = Math.max(westWidth,size.width);
size = getSize(type, NORTHEAST);
northHeight = Math.max(northHeight,
size.height);
eastWidth = Math.max(eastWidth, size.width);
size = getSize(type, SOUTHEAST);
southHeight = Math.max(southHeight,
size.height);
eastWidth = Math.max(eastWidth, size.width);
return new Insets(northHeight,
westWidth, southHeight,
eastWidth);
}
/**
* Returns the minimum dimensions needed to layout the
* components contained in the specified target container.
* @param target The Container on which to do the layout
*/
public Dimension minimumLayoutSize(Container target)
{
Dimension size, dim = new Dimension(0, 0);
size = getSize(MINIMUM, CENTER);
dim.width += size.width;
dim.height += size.height;
Insets edge = calculateLayoutSize(MINIMUM);
dim.width += edge.right + hgap;
dim.width += edge.left + hgap;
dim.height += edge.top + hgap;
dim.height += edge.bottom + hgap;
Insets insets = target.getInsets();
dim.width += insets.left + insets.right;
dim.height += insets.top + insets.bottom;
return dim;
}
/**
* Returns the preferred dimensions for this layout given the
* components in the specified target container.
* @param target The component which needs to be laid out
*/
public Dimension preferredLayoutSize(Container target)
{
Dimension size, dim = new Dimension(0, 0);
size = getSize(PREFERRED, CENTER);
dim.width += size.width;
dim.height += size.height;
Insets edge = calculateLayoutSize(PREFERRED);
dim.width += edge.right + hgap;
dim.width += edge.left + hgap;
dim.height += edge.top + hgap;
dim.height += edge.bottom + hgap;
Insets insets = target.getInsets();
dim.width += insets.left + insets.right;
dim.height += insets.top + insets.bottom;
return dim;
}
/**
* Lays out the specified container. This method will actually
* reshape the components in the specified target container in
* order to satisfy the constraints of the layout object.
* @param target The component being laid out
*/
public void layoutContainer(Container target)
{
Insets insets = target.getInsets();
Insets edge = calculateLayoutSize(PREFERRED);
int top = insets.top;
int bottom = target.getSize().height -
insets.bottom;
int left = insets.left;
int right = target.getSize().width -
insets.right;
setBounds(NORTH,
left + edge.left +
hgap, top,
right - edge.left -
hgap - edge.right - hgap,
edge.top);
setBounds(SOUTH,
left + edge.left +
hgap, bottom - edge.bottom,
right - edge.left -
hgap - edge.right - hgap,
edge.bottom);
setBounds(EAST,
right - edge.right, top
+ edge.top + vgap, edge.right,
bottom - edge.top -
vgap - edge.bottom - vgap);
setBounds(WEST,
left, top + edge.top +
vgap, edge.left,
bottom - edge.top -
vgap - edge.bottom - vgap);
setBounds(NORTHWEST,
left, top, edge.left,
edge.top);
setBounds(SOUTHWEST,
left, bottom -
edge.bottom,
edge.left,
edge.bottom);
setBounds(NORTHEAST,
right - edge.right,
top,
edge.right, edge.top);
setBounds(SOUTHEAST,
right - edge.right,
bottom - edge.bottom,
edge.right,
edge.bottom);
// Center is what remains
top += edge.top + vgap;
bottom -= edge.bottom + vgap;
left += edge.left + hgap;
right -= edge.right + hgap;
setBounds(CENTER,
left, top, right -
left, bottom - top);
}
}
/*
=====================================================================
AbstractLayout.java
Created by Claude Duguay
Copyright (c) 1998
=====================================================================
*/
package com.sas.layoutTest;
import java.awt.Dimension;
import java.awt.Container;
import java.awt.Component;
import java.awt.LayoutManager2;
import java.io.Serializable;
public abstract class AbstractLayout
implements LayoutManager2, Serializable
{
protected int hgap;
protected int vgap;
public AbstractLayout()
{
this(0, 0);
}
public AbstractLayout(int hgap, int vgap)
{
setHgap(hgap);
setVgap(vgap);
}
/**
* Get the horizontal gap between components.
**/
public int getHgap()
{
return hgap;
}
/**
* Get the vertical gap between components.
**/
public int getVgap()
{
return vgap;
}
/**
* Set the horizontal gap between components.
* @param gap The horizontal gap to be set
**/
public void setHgap(int gap)
{
hgap = gap;
}
/**
* Set the vertical gap between components.
* @param gap The vertical gap to be set
**/
public void setVgap(int gap)
{
vgap = gap;
}
/**
* Returns the maximum dimensions for this layout given
* the component in the specified target container.
* @param target The component which needs to be laid out
**/
public Dimension maximumLayoutSize(Container target)
{
return new Dimension(Integer.MAX_VALUE,
Integer.MAX_VALUE);
}
/**
* Returns the alignment along the x axis. This specifies how
* the component would like to be aligned relative to other
* components. The value should be a number between 0 and 1
* where 0 represents alignment along the origin, 1 is aligned
* the furthest away from the origin, 0.5 is centered, etc.
**/
public float getLayoutAlignmentX(Container parent)
{
return 0.5f;
}
/**
* Returns the alignment along the y axis. This specifies how
* the component would like to be aligned relative to other
* components. The value should be a number between 0 and 1
* where 0 represents alignment along the origin, 1 is aligned
* the furthest away from the origin, 0.5 is centered, etc.
**/
public float getLayoutAlignmentY(Container parent)
{
return 0.5f;
}
/**
* Invalidates the layout, indicating that if the layout
* manager has cached information it should be discarded.
**/
public void invalidateLayout(Container target) {}
/**
* Adds the specified component with the specified name
* to the layout. By default, we call the more recent
* addLayoutComponent method with an object constraint
* argument. The name is passed through directly.
* @param name The name of the component
* @param comp The component to be added
**/
public void addLayoutComponent(String name, Component comp)
{
addLayoutComponent(comp, name);
}
/**
* Add the specified component from the layout.
* By default, we let the Container handle this directly.
* @param comp The component to be added
* @param constraints The constraints to apply when laying out.
**/
public void addLayoutComponent(Component comp, Object constraints) {}
/**
* Removes the specified component from the layout.
* By default, we let the Container handle this directly.
* @param comp the component to be removed
**/
public void removeLayoutComponent(Component comp) {}
/**
* Return a string representation of the layout manager
**/
public String toString()
{
return getClass().getName() +
"[hgap=" +
hgap + ",vgap=" + vgap + "]";
}
}
package com.sas.layoutTest;
import java.awt.Component;
public class CastleLayoutLayoutInfo implements com.sas.ide.beans.LayoutInfo
{
public Class getLayoutCustomizer()
{
return null;
}
public Class getLayoutConstraintCustomizer()
{
return CastleLayoutConstraintCustomizer.class;
}
public Object getLayoutConstraint(Component c)
{
String compKey = null;
if ((c.getParent() != null) &&
(c.getParent().getLayout() != null) &&
(c.getParent().getLayout() instanceof CastleLayout))
compKey =
CastleLayoutConstraintCustomizer.getBorder(c, (CastleLayout)c.getParent().getLayout());
return (Object)compKey;
}
}
package com.sas.layoutTest;
import java.awt.Label;
import java.awt.Choice;
import java.awt.Component;
import java.awt.Container;
import java.awt.LayoutManager2;
// This class presents the customizer for Border Layout constraints
public class CastleLayoutConstraintCustomizer extends java.awt.Panel implements
com.sas.ide.beans.ConstraintCustomizerInterface
{
Label label1;
Choice choice1;
Component comp;
public CastleLayoutConstraintCustomizer()
{
label1 = new Label("Set border
name:");
choice1 = new Choice();
add(label1);
add(choice1);
choice1.add("North");
choice1.add("South");
choice1.add("East");
choice1.add("West");
choice1.add("Center");
choice1.add("NorthEast");
choice1.add("NorthWest");
choice1.add("SouthEast");
choice1.add("SouthWest");
choice1.select("Center");
}
static String getBorder(Component c, CastleLayout b)
{
java.util.Enumeration keys = b.table.keys();
while(keys.hasMoreElements())
{
Object k = keys.nextElement();
Object tmp = b.table.get(k);
if (c.equals(tmp))
return (String)k;
}
return "Center";
}
public void setComponent(Component c, Container container)
{
comp = c;
CastleLayout b =
(CastleLayout)container.getLayout();
choice1.select(getBorder(c, b));
}
public void updateComponent(Component c, Container container)
{
// If container is not null then add c to
container with choice1's value
if (container == null)
{
System.out.println("Unable to modify constraints with no parent");
}
else
{
if (c.getParent() ==
null)
container.add(c, choice1.getSelectedItem());
else
{
LayoutManager2 l = (LayoutManager2)container.getLayout();
l.removeLayoutComponent(c);
l.addLayoutComponent(c, choice1.getSelectedItem());
container.invalidate();
container.validate();
}
}
}
}