[Table of contents][Sun's homepage][Next story]

[Java Developer column by Rinaldo DiGiorgio]

Laying it all out with Java

The manner in which you organize buttons and other
GUI elements is an important aspect of application design


Abstract
Laying out the components of your GUI application may seem a tad mundane, but the task is crucial to any good system design. The Java JDK comes with five basic layout managers. Read on to discover a sixth. Also, one or two gratuitous applets demonstrate Java's graphic ability. Thanks to Henry Wong for his programmatic contributions. (3,500 words)

This month we will explore two topics. First, a highly interactive graphical financial application that includes a digital caliper. Second, the various layout managers included with Java as well as a guide to writing a layout manager. In addition, we'll present an actual implementation of the PackerLayout manager. This month's article is based heavily on the work of Henry Wong, a systems engineer in the tactical engineering group at Sun, as well as several applets from Arthur Van Hoff and other members of the Java team.

The applets included here were tested with Netscape 2.0 Beta 4 and worked fine. You may remember that the DNA sequencing program discussed last month did not work properly. Beta 4 clears up the two major problems with bitblits (drawImage) and mouse coordinate values.

Graphing historical volume and prices of various stocks
The HiLo applet demonstrates the power of Java to abstract input streams from servers or URLs, implement mouse control, handle large datasets, and use subclassing to make a problem easier to understand. Historical data is available for companies traded under the symbols CCI, COMP, DEC, DJIA, HWP, IBM, IFMX, INTC, MSFT, NSCP, ORCL, SGI, SUNW, SYBS, T, and TWX.

[You need the JDK appletviewer or Netscape 2.0 Beta 4 or better to view this applet.]
(Click here to view the source.)

This applet displays a volume and price chart based on historical data supplied via a URL on the server. The applet can also work with live feeds, but due to security restrictions this ability cannot be demonstrated here.

The html is parsed to determine if the data source is a server over the network or a URL containing the data. The applet creates a canvas on which to draw the volume chart. Users then enter a symbol. If the source is a server, a command is sent requesting data for the symbol or to read the data from an appropriate URL. Java supports the same methods on either stream. The data is then parsed, normalized, and graphed. Users can select a region of interest by dragging the mouse over the chart. The region is then expanded on the local machine with no interaction required from the server. Clicking on the chart after a region has been selected causes the display to revert back to the entire dataset.

Gratuitous scrolling New York City
[You need the JDK appletviewer or Netscape 2.0 Beta 4 or better to view this applet.]
(Click here to view the source.)

Layout managers
The five standard layout managers shipped with Java Beta 1 or 2 are:

We will start with an example that does not use a layout manager and proceed to a series of short examples and descriptions of the five standard managers. In order to understand how to write a layout manager, we should review some Java AWT (Abstract Windowing Toolkit) layout basics. Here is a diagram showing the functional structure of the AWT:

In order to display a button in a browser we need to create it and place it on the screen. If we're not using a layout manager, the placement must be done by providing the direct screen address. The following applet places all components explicitly:

[You need the JDK appletviewer or Netscape 2.0 Beta 4 or better to view this applet.]
(Click here to view the source.)

Performing this function on many components would be very time consuming and error prone. Also, the information available in each of the objects is not used. Layout managers implement placement rules that attempt to best fit the components into the available space. Not all layout managers are equal. Some may make approximations or decisions about placements that are difficult to understand. The following applet demonstrates a number of different layouts:

[You need the JDK appletviewer or Netscape 2.0 Beta 4 or better to view this applet.]
(Click here to view the source.)

You would not want to lay this out with world or device coordinates.

Now let's look at the standard layout managers supplied with Java Beta 2 and close with the PackerLayout manager.

BorderLayout
Here is the BorderLayout:

[You need the JDK appletviewer or Netscape 2.0 Beta 4 or better to view this applet.]
(Click here to view the source.)

CardLayout
The source for this program is rather involved, be careful not to misread the createPanel method(). [You need the JDK appletviewer or Netscape 2.0 Beta 4 or better to view this applet.]
(Click here to view the source.)

FlowLayout (default for panel/applet)
[You need the JDK appletviewer or Netscape 2.0 Beta 4 or better to view this applet.]
(Click here to view the source.)

GridLayout
[You need the JDK appletviewer or Netscape 2.0 Beta 4 or better to view this applet.]
(Click here to view the source.)

GridBagLayout
GridBagLayout provides more control in laying out grids. You can position items explicitly and all the members do not have to be the same size. Layout information (constraints) is supplied in a constraint class and set with a method call for each element of the grid. This is a rather complicated layout manager and the best approach may be to read this article, the html documentation from the JDK, and practice with a few examples.

[You need the JDK appletviewer or Netscape 2.0 Beta 4 or better to view this applet.]
(Click here to view the source.)

You must know the relative size of the widgets before specifying the layout. Here are some aspects of the GridBagConstraints class:

PackerLayout
The PackerLayout provides a geometry manager that arranges components by packing them around the edges of the parent. PackerLayout processes an input list in order, packing one component in each iteration. Components are positioned in three steps:

More details on the original packer on which the PackerLayout is modeled can be obtained from the packer manual page for Tcl/Tk.

The PackerLayout supplied below does not implement all of the features found in the packer used in Tcl/Tk. Packer is actually the most commonly used layout for the Tk system. The following three files comprise the packer:

Henry Wong has contributed the full source and the technical documentation for a new Java layout manager called the PackerLayout.

[You need the JDK appletviewer or Netscape 2.0 Beta 4 or better to view this applet.]
(Click here to view the source.)

The layout manager interface is called when components need to be laid out. For example, when show is called, objects in the frame need to be laid out and given actual coordinates. There are five methods declared to be abstract you must implement if you're writing a layout manager. If you're using a layout manager these functions are called for you at the appropriate times by other methods in the Java runtime engine. One can see the coordinates of these components before and after layout by using the list() function. This function is very useful for debugging. The layout manager is called with components to add and some other ancillary data that provide information on how to place the component. Layout managers do not return errors, they simply try to place the components as best they can.

Methods you must supply for a new layout manager
The following methods are called to inform the layout manager that components are being added or removed. PackerLayout does not use these methods but does implement them. Other layout managers, such as BorderLayout, use these methods to add the component with additional attribute information.

The next two methods are called by the container to find the minimum or preferred size. It is the job of the manager to call the minimum or preferred size routines of the widgets, and calculate an overall size.

The last of the abstract methods is called by the container to layout the widgets. The manager should call either move, resize, or reshape routines of the individual widgets in the container.

When calculating the various sizes it is important to realize that all containers have insets (borders around the panel), and layout manager must take this into consideration during size calculation and layout. Programmers can arbitrarily set these values by supplying a method that overrides the default insets() method.

Container support routines
A container can have many components. A number of methods are required to provide information back to the caller during layout requests. Get the number of components in a container and the component at the specified index:

Get the components contained in a container:

Get the inset (border) around this container:

Get the layout manager assigned to the container:

Component support routines
Request particular size requests from a component:

Position and size a component:

Request the location and size of a component:

Using the PackerLayout manager
The PackerLayout manager is implemented in two classes. The first class we will discuss is the PackerInfo class which contains information for each component. The programmer must fill in the desired attributes. The information is used by PackerLayout.

This class is used by both the programmer and the PackerLayout manager. To specify the layout the programmer supplies values and calls the add method. When a layout request is received by the layout manager all of the entries in the vector of PackerInfo objects are processed. Here is some example code:

public class PackerInfo implements Cloneable {
        //	Specify which edge to use for placement
        public static final int TOP = 0;
        public static final int BOTTOM = 1;
        public static final int LEFT = 2;
        public static final int RIGHT = 3;

        public int side;
        //	A padding factor to supply additional space
        public int padx;
        public int pady;
        //	Fill in the x and y directions
        public boolean fillx;
        public boolean filly;
	//	The component that this applies to
        public Component widget;
	//	Constructor with default values
        public PackerInfo() {
                side = PackerInfo.TOP;
                padx = pady = 0;
                fillx = filly = false;
                widget = null;
        }
        public Object clone() {
                try {
                        return super.clone();
                } catch (CloneNotSupportedException e) {
                        // this shouldn't happen, since we are Cloneable
                        throw new InternalError();
                }
        }
}

Here are some examples using PackerLayout with varying attributes.

Fillx and filly are false and pad is zero:

[You need the JDK appletviewer or Netscape 2.0 Beta 4 or better to view this applet.]

Fillx and filly are true and pad is 20:

[You need the JDK appletviewer or Netscape 2.0 Beta 4 or better to view this applet.]

Fillx is true and filly is false and pad is 20:

[You need the JDK appletviewer or Netscape 2.0 Beta 4 or better to view this applet.]

Filly is true and fillx is false and pad is 20:

[You need the JDK appletviewer or Netscape 2.0 Beta 4 or better to view this applet.]
(Click here to view the source.)

PackerLayout class (setup/support routines)
The following routines are from PackerLayout. These methods implement the five abstract methods we discussed earlier. Important points are:

        /**
         *      Creates the packer layout
         */
        public PackerLayout() {
                if ( debug )
                        System.out.println("PackerLayout() called");
                defaultPackerInfo = new PackerInfo();
                infoTable = new Vector();
        }
        /**
         *      Layout info added sequentially
         *      @param info the Packerinfo for the component
         */
        public synchronized void addLayoutInfo(PackerInfo info) {
                if ( debug )
                        System.out.println("addLayoutInfo() called");
                infoTable.addElement(info.clone());
        }
        /**
         *      Remove the layout info for the given component
         */
        public synchronized void removeLayoutInfo(Component comp) {
                if ( debug )
                        System.out.println("removeLayoutInfo() called");
                for (int i = 0; i < infoTable.size(); i++) {
                        if (((PackerInfo)infoTable.elementAt(i)).widget == comp)
                                infoTable.removeElementAt(i);
                }
        }
        /**
         *      Return the packer info for the given component
         *      @param comp the component
         */
        public synchronized PackerInfo getLayoutInfo(Component comp) {
                if ( debug )
                        System.out.println("getLayoutInfo() called");
                for (int i = 0; i < infoTable.size(); i++) {
                        if (((PackerInfo)infoTable.elementAt(i)).widget == comp)
                                return (PackerInfo)infoTable.elementAt(i);
                }
                return defaultPackerInfo;
        }

PackerLayout class (adding/removing widgets)

        /**
         * Adds the specified component with the specified name to the layout.
         * @param name the name of the component
         * @param comp the component to be added
         * Does not apply
         */
        public void addLayoutComponent(String name, Component comp) {
                if ( debug )
                        System.out.println("addLayoutComponent() called");
        }
         /**
         * Adds the specified component with the specified name to the layout.
         * @param comp the component to be removed
         * Does not apply
         */
        public void removeLayoutComponent(Component comp) {
                if ( debug )
                        System.out.println("removeLayoutComponent() called");
        }

PackerLayout class (preferred/minimum size)
The following code fragment demonstrates the basic structure for a method that is to return the preferred size.

public Dimension preferredLayoutSize(Container parent) {
	Dimension result = new Dimension(0,0);

	Insets ins = parent.insets();
	result.width = ins.left + ins.right;
	result.height = ins.top + ins.bottom;

	int ncomponents = parent.countComponents();
	for (int i = 0; i < ncomponents; i++) {
		Component c = parent.getComponent(i);
		if (c.isVisible() == false) continue;
		PackerInfo winfo = getLayoutInfo(c);
		Dimension widget = c.preferredSize();

		result = SomeFunc(result, winfo, widget, ...);
	}
	return result;
}

public Dimension minimumLayoutSize(Container parent) {
	...
}

PackerLayout class (preferred/minimum size)
This method is from the PackerLayout. It is called when the size of a container is needed.

        private Dimension getSizeContainer(Container parent, boolean preferred) {
            Dimension dim = new Dimension(0, 0);
            Dimension max = new Dimension(0, 0);
            int ncomponents = parent.countComponents();
                if ( debug )
                        System.out.println("getSizeContainer() called");

            Insets ins = parent.insets();
            dim.width = ins.left + ins.right; max.width = dim.width;
            dim.height = ins.top + ins.bottom; max.height = dim.height;

            for (int i = 0; i < ncomponents; i++) {
                Component c = parent.getComponent(i);
                if (c.isVisible() == false) continue;
                PackerInfo winfo = getLayoutInfo(c);
                Dimension widget = getSizeComponent(c, preferred);

                if ((winfo.side == PackerInfo.TOP) ||
                    (winfo.side == PackerInfo.BOTTOM)) {
                        dim.height += widget.height+(2*winfo.pady);
                        max.width =
                            Math.max(widget.width+dim.width+(2*winfo.padx),max.width);
                } else {
                        dim.width += widget.width+(2*winfo.padx);
                        max.height =
                            Math.max(widget.height+dim.height+(2*winfo.pady),max.height);
                }
            }

            dim.height = Math.max(dim.height, max.height);
            dim.width = Math.max(dim.width, max.width);
            return dim;
        }

        /**
         * Returns the preferred dimensions for this layout given the components
         * int the specified panel.
         * @param parent the component which needs to be laid out
         * @see #minimumSize
         */

        public Dimension preferredLayoutSize(Container parent) {
                if ( debug )
                        System.out.println("preferredLayoutSize() called");
            return getSizeContainer(parent, true);
        }

PackerLayoutClass (layout panel) template
This template provides a guide for understanding the implementation of the layoutContainer method. The important points are:

	Dimension dim = parent.size();
	boolean isPreferred = SomeFunc2(dim, ...);

	Rectangle freeSpace = new Rectangle(dim);
	Inset ins = parent.insets();
	freeSpace.x += ins.left; freeSpace.y += ins.top;
	freeSpace.width -= ins.left + ins.right;
	freeSpace.height -= ins.top + ins.bottom;

	int ncomponents = parent.countComponents();
	for (int i = 0; i < ncomponents; i++) {
		Component c = parent.getComponent(i);
		if (c.isVisible() == false) continue;
		PackerInfo winfo = getLayoutInfo(c);
		Dimension widget = (isPreferred)?
					c.preferredSize() : c.minimumSize();

		Rectangle s = SomeFunc3(freeSpace, winfo, widget);
		c.reshape(s.x, s.y, s.width, s.height);
		freeSpace = SomeFunc4(freeSpace, s, ...);
	}
}

PackerLayoutClass (layout panel) template
The following method is from PackerLayout. The method implements the functionality described without using additional methods.

public void layoutContainer(Container parent) {
        /** 
         * Lays out the container in the specified panel.
         * @param parent the specified component being laid out
         * @see Container
         */
	public void layoutContainer(Container parent) {
	    boolean preferred = false;
	    int ncomponents = parent.countComponents();
	    Dimension dim = parent.size();
	    Dimension chk = preferredLayoutSize(parent);
		if ( debug )
                        System.out.println("layoutContainer() called");
	    if ((dim.height >= chk.height) || (dim.width >= chk.width)) {
		preferred = true;
	    } else {
		chk = minimumLayoutSize(parent);
		dim.width = Math.max(dim.width, chk.width);
		dim.height = Math.max(dim.height, chk.height);
	    }

	    Rectangle frees = new Rectangle(dim);
	    Insets ins = parent.insets();
	    frees.x += ins.left; frees.y += ins.top;
	    frees.width -= ins.left + ins.right;
	    frees.height -= ins.top + ins.bottom;

	    for (int i = 0; i < ncomponents; i++) {
		Component c = parent.getComponent(i);
		if (c.isVisible() == false) continue;
		PackerInfo winfo = getLayoutInfo(c);
		Dimension widget = getSizeComponent(c, preferred);
		Rectangle shape = new Rectangle();

		// Calculate the Width and Height of the widget
		switch (winfo.side) {
		    case PackerInfo.TOP:
		    case PackerInfo.BOTTOM:
			if (winfo.fillx)
				shape.width = frees.width - (2*winfo.padx);
			else
				shape.width = widget.width;
			if ((winfo.filly) && ((i+1) == ncomponents))
				shape.height = frees.height - (2*winfo.pady);
			else
				shape.height = widget.height;
			break;
		    case PackerInfo.LEFT:
		    case PackerInfo.RIGHT:
			if ((winfo.fillx) && ((i+1) == ncomponents))
				shape.width = frees.width - (2*winfo.padx);
			else
				shape.width = widget.width;
			if (winfo.filly)
				shape.height = frees.height - (2*winfo.pady);
			else
				shape.height = widget.height;
			break;
		}
		// Calculate the X and Y of the widget
		switch (winfo.side) {
		    case PackerInfo.TOP:
			shape.x = frees.x + ((frees.width - shape.width)/2);
			shape.y = frees.y + winfo.pady;

			frees.y += shape.height + (2*winfo.pady);
			frees.height -= shape.height + (2*winfo.pady);
			break;
		    case PackerInfo.BOTTOM:
			shape.x = frees.x + ((frees.width - shape.width)/2);
			shape.y = frees.y + frees.height - shape.height - winfo.pady;

			frees.height -= shape.height + (2*winfo.pady);
			break;
		    case PackerInfo.LEFT:
			shape.x = frees.x + winfo.padx;
			shape.y = frees.y + ((frees.height - shape.height)/2);

			frees.x += shape.width + (2*winfo.padx);
			frees.width -= shape.width + (2*winfo.padx);
			break;
		    case PackerInfo.RIGHT:
			shape.x = frees.x + frees.width - shape.width - winfo.padx;
			shape.y = frees.y + ((frees.height - shape.height)/2);

			frees.width -= shape.width + (2*winfo.padx);
			break;
		}
		c.reshape(shape.x, shape.y, shape.width, shape.height);
	    }
	}

Documentation for the packer
A great new ability with the Beta 2 of the JDK is the electronic class indexing and the generation of names index. Check it out:

Next month's article
As December is a tumultuous time, I'm not quite sure what will be covered in next month's article. Likely candidates are a real Java success story from a bank in New York, or a test drive of the JDE from SunSoft. []

About the author
Rinaldo S. DiGiorgio works for Sun Microsystems as a Systems Engineer in the New York City office and provides frequent demonstrations of Java Technology. DiGiorgio is currently working on the integration of many technologies into HotJava/Java. Some of these technologies are database connectivity, portfolio management, low-cost video, and analytical applications in the financial and emerging genetics market. DiGiorgio has been using Unix-based operating systems since 1979, when he was deploying Unix solutions in paper mills. He sees HotJava/Java as the technology to minimize two great cost factors in the computer industry: distribution and code development. He can be reached at rinaldo.digiorgio@sunworld.com.

What did you think of this article?
-Excellent! -Okay -Poor ----- -Too long -Just right -Too short

[Feedback Form]
[Table of contents][Sun's homepage][Next story]


[Also this month...]

Sun's `Internet Toaster'
Sun maintains marketshare
Find what you need with sunWHERE


[(c) Copyright 1996 Web Publishing Inc.]

If you have problems with this magazine, contact webmaster@sunworld.com
URL: http://www.sunworld.com/swol-01-1996/swol-01-java.html
Last updated: 1 January 1996