|
SYS-CON.TV Webcasts
Comments
Did you read today's front page stories & breaking news?
SYS-CON.TV
|
Top Links You Must Click On
General Java Creating a Custom Layout Manager
Creating a Custom Layout Manager
By: Joseph Cozad
Oct. 1, 1998 12:00 AM
A layout manager is an object that positions and resizes components within a display area according to a specific algorithm. The Java 1.2 AWT package provides 10 layout manager classes that can be used to accomplish this task. Each has a defined set of behaviors that organize components in a container. Each Java container instance is associated with an instance of one of these layout managers. By nesting one container/layout manager combination within another one, complex screen layouts can be implemented. Sometimes the layout managers don't individually meet all the layout requirements needed to implement a particular GUI design. And in the case of complex layout designs, nesting container/layout manager combinations can produce code that is hard to understand. This article discusses what a layout manager is and how it is implemented in Java. Through a real-world example it demonstrates how a layout manager can be created to address any GUI layout situation without the need to nest container/layout manager combinations.
Layout Requirements To accommodate a component larger than an individual cell, two or more cells could be combined horizontally or vertically (or both), creating a cell area addressed by the identity of the first cell in the upper-left corner of the area. This functionality is similar in design to the colspan and rowspan attributes in HTML tables.
Component locations within a cell area need to be specified in one of three ways: In addition, components within the container could not lose their positions relative to the edges of the container when the container's size changed. Components needed to maintain their size relative to the size of the container so that as the container enlarged or shrank, the components enlarged or shrank proportionally. Also, the specified initial size of the component could not be altered to fill a cell that was larger than the component; that is, component size integrity had to be maintained. The layout manager was required to provide a way to calculate the size of a component based on cell area size to assist in setting a component's size relative to the size of the container. No limitation was placed on the number of components that could be added to a cell area. This meant that components could be overlapped and that overlapping should be done in a back-to-front manner. That is, component B added to the same container cell area as, and after, component A would lie on top of component A.
Design Choices The GridLayout organizes components by dividing the display area into a grid of equal-sized cells numbered 0 to n from left to right. Each cell is filled in succession with only one component, thus providing no component overlap functionality. The GridLayout manager doesn't provide a way to address a specific cell in the grid in which to add a component. In addition, component sizes are normalized to fill the cell so that each cell maintains an equal size, regardless of the preset component size. Similarly, the GridBagLayout organizes components into cells like the GridLayout, but provides greater flexibility in how they are displayed within each cell area by associating a separate constraints object (GridBagConstraints) with each component. Through the value manipulation of the GridBagConstraints object, each component can occupy more than one cell at a time. This is not a simple process, however, because it relies on a coordinate system rather than a single-cell ID number. For example, to add a component to a panel that implements a GridBagLayout manager, the programmer sets the instance variables, gridx and gridy, of the associated GridBagConstraints Object. Alhough this manager provides a mechanism for a component to span more than one cell, it doesn't provide component overlap or a simple solution for defining where in a cell area the component should be located. Also, the GridBagLayout resizes components based on a combination of its associated constraints object, the minimum size of the component and the preferred size of the container. Setting a specific size for a button object can be overridden by the size calculated by the layout manager using the constraints object. Java 1.2 introduces a new layout manager class called an OverlayLayout, which lays out a component based on the component's specified alignment value. While this class allows components to overlie each other, the programmer must set each component's alignmentX and alignmentY values so that the manager can calculate the component's size and location relative to the size of the enclosing container. For this example, creating a custom layout manager solution centering around the general functionality of these three layout managers and incorporating the simplicity of HTML tables seemed appropriate. So how is this done? To begin designing a custom layout manager, it is first necessary to understand how it works and when.
Layout Manager Functionality
In Java, an object becomes a layout manager by implementing the methods declared in the java.awt.LayoutManager or java.awt.LayoutManager2 interfaces or both. The latter interface is new to Java 1.1, and extends the LayoutManager; no changes were made to these interfaces in Java 1.2. These interfaces describe 10 method signatures (behaviors) necessary for the implementing object to arrange components within a container. These behaviors can be grouped into six types: The first three types are the minimum behaviors needed to create a layout manager's functionality. In designing the add and remove behaviors, the most important consideration is that the layout manager needs to maintain and associate layout information on each component object maintained by the container object. The following describes each group, what the associated methods do and when they are called by a container object. I assume the reader is familiar with how a layout manager is used by a container object, and knows that a Java applet or application never calls any of these implemented interface methods directly, but leaves these calls to be implemented by the container object itself.
Adding Components These methods define how the layout manager will track the components that are added to the associated container:
public void addLayoutComponent(String position, Component comp); The layout manager should maintain the information passed by the string or constraints object to use when the container needs to be laid out (see "Container Layout"). This could be done with an array, vector or hash table. The first method definition takes a String object that acts as a positional modifier indicating how or where the component should be laid out. For example, when a container object implements a BorderLayout object as its layout manager and a component is added to the center region of the container, the method Container.add("Center", comp); uses the string "Center" as a positional modifier. The other method definition supports constraint-based layout management, which assumes that each component added to the container is associated with a separate constraint object that specifies how the component will be laid out. An example of this is seen in the GridBagLayout and the associated GridBagConstraints classes.
Removing Components public void removeLayoutComponent(Component comp); The container also removes the component object from the list of components it maintains. After this method has been called, the container calls the layout manager's invalidateLayout(); method (see "Container Invalidation").
Container Layout public void layoutContainer(Container parent);
The algorithm designed to determine these values varies depending on what is required by the design of the layout manager. However, regardless of how the algorithm is designed, the following processes should happen: After the container calls the layout manager's layoutContainer(); method, the container draws the components on the screen. The container does this by cycling through the container's array of components beginning at 0 and ending at n-1. When trying to facilitate overlapping, it is important to add the component to the beginning of the array, that is, index location 0. This can be accomplished by calling one of the container's add methods that includes an integer index value as one of its arguments, such as add(Component comp, Object obj, int index);. By specifying a different index value, the component can be inserted between two overlapping components.
Container Invalidation public void invalidateLayout(Container target);
Defining Size These methods can optionally define what the container's size should be after laying out all the objects and taking into consideration the container's insets:
public Dimension minimumLayoutSize(Container parent); There's no guarantee that any one of these methods will be called before the layoutContainer(); method (see "Container Layout").
Defining Location
public float getLayoutAlignmentX(Container target); At minimum, a default return value of 0.5f for centered should be supplied.
Designing the RelationalGridLayout Manager Unlike the GridBagConstraints class that gives direct exposure to its instance variables, the CellConstraints class contains four constructors that initialize a set combination of instance variables. This design allows for only four possible ways to define how the component should be laid out. Each constructor takes an integer that indicates the cell number that contains the upper-left corner of the component. Three of the constructors take two integers, each representing the number of cells the component will span horizontally and vertically. These two values are used to represent the cell area in which the component will be placed. One of the constructors takes a java.awt.Point object that indicates the x and y coordinates of the component's upper-left corner within the cell area, and one of the constructors takes an integer that represents a predefined location within the cell area using one of several LocationManager values. The following paragraphs discuss key points in the RelationalGridManager's implementation (see code listing).
The Constructor
The Layout Manager's Behaviors After the method variables have been assigned, the point values for the component's location are calculated (lines 94-111). If a point within the cell area is specified, the calcCellInset(); method is called (lines 185-192). This method takes the point, the cell area size, and the component's size to calculate the distances of the component's edges from the cell's edges. If, instead, a LocationManager value is specified, the calcInsets(); method is called (lines 203-278) to determine these same values based on the LocationManager value supplied. Once the component's insets are calculated for the cell area defined by the column and row values, the x,y coordinate or point value within the container's dimensions is calculated using the cell ID number and the component's insets (lines 98, 100 and 108). Next, in lines 115-118, the method takes the calculated point and the component's size and converts this information into proportional ratios. These ratios are stored in an ItemInfo object and added to the instance's hash table keyed on a reference to the component (line 121).
Removing Components
Laying Out Components Next, in line 142, a reference to the container's array of components is retrieved. Then the method loops through the array retrieving the component's modifier information from its associated ItemInfo object, converts it from ratios to actual pixels based on the current size of the container and then sends the x,y width and height information to the component using its setBounds(); method (lines 143-153).
The rest of the RelationalGridLayout implementation contains helper methods that assist in performing some of the calculations needed by the behaviors described.
Two useful methods to note are:
Using the RelationalGridLayout First, a sketch of the screen was created (see Figure 2). Then a grid pattern was derived by identifying the smallest element in the sketch; in this case the shape, size, color, cell number labels and their associated input fields were used to identify the size of the smallest cell. From this, an 8 x 8 grid pattern was created. The createPanel(); method in the AddCompPanel class provides the source defining the representation of the Add Component Screen (see AddCompPanel.createPanel Code). Much of the code is taken up by creating the individual components and specifying their characteristics. Line 6 creates the RelationalGridLayout object supplying the size of the panel and the number of columns and rows in the grid pattern. Notice that a method variable is created to hold the reference to the layout manager, as opposed to creating the reference directly in the setLayout(); method call in line 7. This was done so that we can use the layout manager's calcArea(); method to specify and preset component sizes. Lines 10-104 create the instances of each component and set their characteristic values. Each component's size is set directly because the layout manager doesn't calculate the size, but calculates the component's proportional size (proportional to the container). Lines 17, 25, 28 and 96 use the layout manager's calcArea(); method to set these sizes relative to the sizes of their intended cell areas. For the shape, size, color and cell number labels; their fields; and the add button, a method variable (line 28) is assigned with the size calculated by the layout manager's calcArea(); method as two cells wide, one cell high, with a 10-pixel inset, just as the sketch shows. Once created, the components are added to the container using the panel's add(Component comp, Object obj, int indx); method (lines 105-126). With each add a CellConstraints object is created, passing the cell number, the number of columns the component will span horizontally, the number of rows the component will span vertically and a location within the cell area. Some components have a value of 1 for either the column or row spans. The RelationalGridLayout does not assign a valid default value for these arguments. Also, the index value passed to the add method is 0, thus placing each component at the beginning of the container's component array.
Conclusion Reader Feedback: Page 1 of 1
Enterprise Open Source Magazine Latest Stories . . .
Subscribe to the World's Most Powerful Newsletters
Subscribe to Our Rss Feeds & Get Your SYS-CON News Live!
|
SYS-CON Featured Whitepapers
Most Read This Week |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||