My last column covered Components, Containers and Events. The material discussed Java's different kinds of Components, the class structure, how Components generate events and how those events can be handled. We discussed the differences between Components and Containers. To recap, Component is the base class for all user interface widgets. A Container is a descendant of Component whose sole purpose is to hold other Components. For example, if you were to create a user interface and you wanted to add several buttons and text areas, you would create instances of the appropriate classes (which all descend from Component) and add them to some kind of Container (like a Panel).
If you have experimented with the AWT classes (or if you have been doing your assignments) you have probably noticed Java's distinct lack of a precise method to place Components. Basically, the programmer is offered a method similar to add(Component). There are no arguments indicating coordinates or sizes; nothing in the method call indicates where the Component may wind up. All you know is that it will be added, somewhere. Most new Java programmers have spent a lot of time wondering, "Where will the Component be added after this method is executed?" Even after experimenting with the add(Component) method, most programmers get unpredictable results. Either the Component is added in some awkward place, or it resizes funny, or it never shows up on the screen at all. If this has happened to you, don't despair. These are common side effects of using one of Java's layout managers.
A LayoutManager is an interface whose implementation provides rules for Component placement. Containers use LayoutManagers to indicate where Components will appear when they are added (using the add(Component) method). LayoutManagers also provide a mechanism to describe how Components will resize if their parent Container grows or shrinks. For example, a LayoutManager might indicate, and a certain one does, that the parent Container is to be broken up into a grid of a predetermined number of rows and columns. Components are placed into equally sized grid cells, starting at the upper left and proceeding to the lower right. When the Container resizes, new cell dimensions are calculated and the Components placed in those cells assume the corresponding size.
LayoutManagers can often be awkward to use even if the rules are very simple, as in the brief GridLayout example above. User interface requirements are often very complicated, and a simple set of rules usually does not accommodate the many exceptions needed to put a screen together aesthetically. It is possible to use many Containers, each of which uses a different LayoutManager, to create a nice looking screen, but to the developer this usually seems like a clumsy approach. There always seems to be one button or text field that has to grow differently, or needs to be placed in an odd place.
So why does Java promote the use of LayoutManagers? Why shouldn't Components to be placed by coordinate values? If the developer wants a button at an exact location, why do they have to go through the headache of creating a LayoutManager, adding the component and often settling for whatever happens? Well, it turns out that the programmer's investment in LayoutManagers pays off pretty well. Java is a cross-platform language. Machines that run Java programs can have a wide variety of screen resolutions. If Java programs hard-coded Component placement with pixel coordinates, they might be unusable on certain systems.
Imagine yourself in a few years using your favorite Java-based personal organization software package. While on the road, you would probably use the software to keep appointments and such, and you will probably run the program on your tiny little Java-enabled watch-computer. When you come home, you might like to run the same program on your-wall sized flat panel Java-enabled television. If the writers of the software package only had your watch in mind when hard coding the coordinates of Components, the whole program might take up about a square inch of the upper left corner of your wall-sized television. But since they used LayoutManagers to design the screen, the application could grow to fit the whole size of your television.
In today's terms, you cannot be sure of the monitor resolution of end user machines. A program developed with Components hard coded to fit on an 800X600 screen will be clipped when running on machines in 640X480. Java uses LayoutManagers to ensure that your programs look similar on all platforms.
A LayoutManager is actually an object that implements one of two interfaces: LayoutManager or LayoutManager2. The interfaces are defined in the java.awt package. LayoutManager is an artifact from Java 1.0X and is used by all of the LayoutManager implementations included in java.awt. The LayoutManager2 interface is new to Java 1.1. The methods defined in this interface allow for extra information (constraint information) to be stored with each Component added to a Container. The constraints may hold some useful data indicating how a particular Component sizes when more space becomes available, or rules describing how much distance one Component must be from another.
The methods defined in these interfaces rarely get called by the programmer. Containers call the methods when they have to resize the screen. Programmers only have to work with specific methods when creating their own LayoutManager implementations.
The steps involved in getting a Container to work with a LayoutManager are fairly straightforward. First, you need to create an instance of a LayoutManager. Next, you will want to tell a Container to use this new LayoutManager instance. Finally, you add your Components to the Container. The Container will rely on the logic defined in the LayoutManager to determine where the Components will be placed. See the example code in Listing 1.
As I mentioned earlier, Java comes with some LayoutManagers. Though some of them are crude, and others are a little tricky to use, they are all that a programmer really needs to create a decent user interface.
Java has five LayoutManagers. This article will discuss four. The fifth is a complicated beast called GridBagLayout which I will discuss in my next column. The following are descriptions of the others.
FlowLayout is the default LayoutManager for all Panels. This means that if you don't explicitly assign a LayoutManager to a Panel, you get FlowLayout. FlowLayout can be very frustrating for new programmers to use because it does not resize Components. Components controlled by a FlowLayout are added to the Container one row at a time, much the same way that letters are typed into a document. When there is no more space available in the current row, FlowLayout will 'carriage-return' to the next row and start adding Components there until the end of the row is reached. FlowLayout defines several constants to indicate whether Components will be added with a 'left justification', 'right justification' or 'center justification.' The names of the constants are LEFT, RIGHT and CENTER.
As mentioned earlier, Components added to a FlowLayout do not resize. The LayoutManager gives each Component its preferredSize. A very common mishap occurs when the underlining Container does not have enough space to fit all of the Components that are added to it. Under these circumstances, a Component will appear clipped or will not be drawn at all. It is very common for Java programmers to spend hours wondering why the Component they just added does not show up on the screen, even though everything compiles fine and the code has been examined several times. If you are using FlowLayout, chances are that the Container is not big enough!
BorderLayout is the default LayoutManager for Windows. BorderLayout divides the screen into five regions: North, South, East, West and Center. Components are added to a region, using the add(String name, Component c) method, and expand to fill the entire area. The Name argument indicates the region in which to add the Component. Components added to the North or South fill the entire width of the Container and assume their preferred height. If the Container is not big enough to accommodate the sum of the two preferred heights, the Component added first will be drawn on top of the Component added second. Components added to the East and West regions have whatever height is left after the North and South Components take their share. The East and West Components will contend for width the same way the North and South Components contend for height. The Center Component gets whatever is left over.
Programmers often get into trouble when using BorderLayout. One common mistake is to use the wrong add method. If you call add(Component) and do not specify a region to add the Component to, your code will compile fine but the Component will not show up on the screen! Also, North, South, East, West and Center are caps-sensitive. So if your code makes a call with the wrong capitalization (i.e., add("south", b1);), you will not see b1 appear in the center region of the Container and you will not receive any compiler errors! Finally, if you try to add two Components to the same region, only the second one will appear on the screen.
GridLayout is the simplest, most predictable LayoutManager. GridLayout divides the Container into equally sized rows and columns. Components are added to each cell starting from the upper left and going from row to row until finishing up in the lower right cell of the grid. Components added resize themselves to fill up an entire cell.
GridLayout's Constructor describes the number of rows and columns the Layout will contain. For example, GridLayout gl = new GridLayout(10,9); divides the Container into a grid of 90 cells. The grid has 10 rows and 9 columns.
A common 'feature' encountered by programmers when using GridLayout occurs when the developer has not taken the time to count exactly how many Components will be added to their Container. In many cases, GridLayout's grid will often reshape itself to better hold its Components. For example, if you specify a 3X3 grid in GridLayout's Constructor, but only add four Components instead of nine, the grid may actually appear to be 2X2 in size.
CardLayout is a peculiar LayoutManager used to organize Containers. For example, if you had five Panels, each of which had some Buttons, TextAreas, etc. and you wanted to display these five Panels within a particular Container, one at a time, you would use CardLayout. Using this, you could change which Panel is displayed and which are hidden. Probably the most popular example of CardLayout is the tabbed dialog. A tabbed dialog has a single display area (Container) and some tabs running along the top. When a tab is pressed, a different screen (Panel) appears in the display area.
About the Author
John V. Tabbone is a lecturer at New York University's Information Technologies Institute, where he teaches two Java programming courses and advises on curriculum development. He has been a professional Java programmer since early 1996, and continues to consult on and develop systems for a variety of New York based businesses.
Panel p = new Panel();
Button b1 = new Button( “Button1” );
Button b2 = new Button( “Button2” );
Button b3 = new Button( “Button3” );
GridLayout myLayout = new GridLayout( 3,1 ); // First, create an instance of the
// LayoutManager . In this example we use
p.setLayout( myLayout ); // Next, tell the Container to use the
p.add( b1 );
p.add( b2 );
p.add( b3 ); // Finally, add the Components. When the Components get added to the
// Container, the LayoutManager determines where they will be positioned on
// the screen. In this case, the Panel is divided into three rows and one column,
// each of which will contain one Button.