Level: Intermediate Joe Winchester (joewin@us.ibm.com), Software developer, IBM Renee Schwartz (rsch@us.ibm.com), Software designer, IBM
01 Oct 2001 Face it, software designers and developers rarely see eye to eye. Nowhere is this more evident than in the GUI development process, where designers take the lead, but often don't provide the type of information developers really need to bring their visions to life. Authors Renee Schwartz and Joe Winchester explain how Java layout managers can bring congruency to your GUI design and development process, by providing a common framework for both sides of the team to work with. Not only will this ease the friction between you, it could lead to better overall GUI design and a more robust final product.
The process of creating a graphical user interface (GUI) for an application requires one or more developers and designers to work closely together to meet a set goal. Even in the most well-knit production teams, this process is often hampered by the lack of a shared vocabulary, which can result in misunderstanding, reiteration of fundamentally simple tasks, and poor results. In this article we'll look at how designers and developers alike may benefit from incorporating Java layout managers in the development of a GUI. The Swing class library includes several layout managers, ranging from the very simple FlowLayout manager to the more complex and flexible GridBagLayout manager. It is our experience that working with a layout manager helps to bring a congruency to the design and development process, because each layout manager presents a unique and defined set of design possibilities, which are easily implemented by a development team. We'll work with an example GUI, taking it from its most simple form, in which no layout manager is used in the GUI construction, to its most complex form, in which an advanced layout manager is used to manipulate controls, columns, and the allocation of extra space within the GUI window. At the end of the article, you should have a good understanding of how a layout manager can positively impact GUI design, and thus the GUI development process. The design and development process
In a typical GUI design and development process, the designer is responsible for creating a series of images that represent each screen of the GUI. These images are generally
produced on a whiteboard or piece of paper, then mocked up in a paint tool such as Photoshop or Visual Basic. The designer works with the fonts, colors, and layout of controls in the interface until he or she is happy with the results. This mocked-up GUI then becomes part of the specification that is given to the developer or development team whose task it is to implement the desired GUI. In many cases, the designer's mockup presents a number of problems to the developer or development team, most of which are grounded in a series of mistaken assumptions. We'll first discuss these common assumptions, then look at how Java layout managers can help to resolve them. Common assumptions that lead to flawed designs
First, the designer may have assumed consistency in the size of the string literals contained in controls such as buttons and labels. If the application is to be deployed to users in different
countries, however, then the user-visible strings will likely be translated into the language of that country. If, as is very likely, the translated strings are of a different length than the length specified by the designer, then the larger strings will be clipped by the controls and the smaller strings will be surrounded by excess whitespace. Second, the designer may have assumed control of the size of the overall window. Users often expect to be able to resize their windows, to make them larger so they have more area to work with, or smaller so they can work with more visible application windows at a time. In both cases, the position and size of the GUI controls should be adjusted to make the best use of the new space. While users will likely not expect control buttons (which contain fixed, literal strings) to grow when a window is expanded, they might expect an element such as a list or table to grow, thus showing more rows and wider columns. Third, the designer may have assumed that the appearance of the controls on the prototype machine will extend to every end user's machine. One of the promises of the Java language is that of cross-platform compatibility. To achieve this, the Java Foundation Classes (JFC) provide a set of GUI controls that can run on different platforms and operating systems. For basic controls such as buttons and text boxes, developers may rely on the Abstract Window Toolkit (AWT) class library. The AWT enables the Java language to use the native (or heavyweight) widgets on each system, but it provides only a basic user interface experience. For applications that will use tree lists, tables, toolbars, buttons with graphics,
and other sophisticated controls, developers may turn to the JFC and to the Swing class library. Swing achieves portability by creating a canvas and actually building each control with low-level paint and mouse API calls. Because native widgets aren't used, this is known as a lightweight or emulated widget toolkit. The actual drawing of the each control is
deferred to an object known as a look and feel. The look and feel tries to emulate as much as possible the appearance and characteristics of the native control, so the user experiences little change when switching from a native application to a Java one. The different look and feels position and paint controls quite differently, so that a GUI that has one appearance in the Windows look and feel will have a very different appearance running with the Motif or Macintosh look and feel. A GUI designed and developed without taking this into account may look great in your Windows prototype but could look quite poor when run on another operating system such as Linux or Macintosh. Working around these assumptions
The first three assumptions can be avoided by delivering an application in a single language with non-resizeable windows and a fixed look and feel. The display settings present another problem entirely, however. These settings may be configured differently on the machines on which the application will be deployed than they were on the prototype machine. For example, fonts and font sizes could be set system-wide by the user to suit his or her accessibility preferences. In this case, the size of the user-visible strings, as well as the controls that accept input (such as text boxes), will be different from those at design time. Controls, windows, and fonts change from system to system, thus your GUI should be built with these variables in mind, and should adjust gracefully as they arise. The most important concept Web application designers and developers must work with is fluidity. Designing your Java GUI based on a static model is self-defeating; rather, you must work within a framework where the position and size of controls is governed by a number of shifting variables. In the sections that follow, we'll work with a simple GUI consisting
of a window and a number of controls. We'll start with a look at a GUI
design that does not make use of a layout manager.
Manipulating controls without a layout manager
A GUI consists of a top-level window that has a number of controls on it. Each control is an instance of a Java class that is a subclass of java.awt.Component. Examples of controls are text boxes, labels, buttons, and lists. The outermost control, the one with the title bar and the Minimize and Maximize buttons, is a subclass of java.awt.Window. Every control is positioned by being given a rectangle that contains its x and y positions, as well as its width and height. Each control has a parent control, with the exception of the Window itself. Figure 1 shows the initial form of the GUI we'll work with throughout this article. Figure 1. A simple GUI

The code to create the window illustrated in Figure 1 is shown in Listing 1. The first line shows the creation of the Frame, which is the class used for the outermost window. The next line sets the frame's layout manager to null. Because we're not using a layout manager, the controls must position themselves on the frame. Next, two buttons are created and added to the frame, and given locations and dimensions. The locations here are the x and y 10,30 and 70,30. The x coordinates go from left to right and the y coordinates go from top to bottom.
Frame frame = new Frame();
frame.setLayout(null);
frame.setBounds(100,100,150,70);
Button button1 = new Button("Next");
frame.add(button1);
button1.setBounds(10,30,50,25);
Button button2 = new Button("Previous");
frame.add(button2);
button2.setBounds(70,30,50,25);
frame.setVisible(true);
|
The problem with fixed coordinates
The problem with code that positions controls based on their absolute coordinates (known as absolute positioning) is that the strings within the labels may be translated into another language, or the user could resize the window. In both cases the buttons will remain fixed at their original location. The results are shown in Figure 2, where you can see what happens when the GUI is translated from English to French, as well as what happens when the window is resized by the user. Figure 2. The results of employing fixed
coordinates in the simple GUI

To work around this problem, we'll utilize the simplest of layout
managers, the flow layout manager.
A simple layout manager
A control such as a window, which can have a child control placed within it, is a subclass of java.awt.Container. Every container has a layout manager, and each layout manager is responsible for positioning the controls for its container. The most simple layout manager, FlowLayout lays out the controls in a left-to-right fashion. The code for this is shown in Listing 2, where the frame's layout is set to be an
instance of FlowLayout and the two buttons are added to the frame. Note that the lines that set the bounds of the two buttons to position and size them have been removed. When a layout manager is used, controls are no longer individually positioned
through setting their bounds. It is the job of the layout manager to calculate the location and size of all of its child controls.
Frame frame = new Frame();
frame.setLayout(new FlowLayout());
frame.setBounds(100,100,160,70);
Button button1 = new Button("Suivante");
frame.add(button1);
Button button2 = new Button("Precedente");
frame.add(button2);
frame.setVisible(true);
|
As shown in Figure 3, the flow layout manager positions the two buttons in the center of the available space. Each button is allocated enough space for its label, leaving just the right amount of whitespace on either side. Figure 3. FlowLayout lays out the
controls from left to right

Listing 2 contains no code to set the size of the buttons, which may lead you to wonder how the flow layout manager gauges the appropriate width for each button. This is done with the method getPreferredSize() on the Component
class. The preferred size is an instance of a Dimension that
contains a width and a height. The Button class is
written to look at its label and the metrics of its font and calculate the width
required to show the entire label. If the label grows, the preferred size will change as well, and the layout manager will use the new preferred size. Figure 4 illustrates a GUI that has been translated from English to French, and the button size has grown to accommodate the new string. Figure 4. FlowLayout adjusts the button's preferred size

After asking each control for its preferred size, the layout manager looks at the available space and sets the location of each control. Controls are positioned so that they don't overlap one another. When the window gets larger FlowLayout repositions the controls at the center of the available space. If there isn't enough room to lay the controls side by side, then the layout manager will place one beneath the other, as shown in Figure 5. Figure 5. FlowLayout positions one control beneath the other

Dynamically sizing the GUI window
In Listings 1 and 2 above, the actual size of the window has been hardcoded to have a width of 160 and a height of 70. This is problematic because the size and position of the controls themselves can grow or shrink based on the text contained within them. If a control grows too large for the window, it will be clipped; if it grows too small, there will be excess space around it. The results are shown in Figure 6, where the strings have become too large for the window. Figure 6. The results of using a static window size

The Window component offers the method pack() to resolve this problem. When a window is sent pack() it will size itself so there is just enough room to
display all of the controls contained within its frame. Listing 3 shows the simple
GUI with the pack() method written in.
Frame frame = new Frame();
frame.setLayout(new FlowLayout());
Button button1 = new Button("Advance Forward");
frame.add(button1);
Button button2 = new Button(" Revert to Previous");
frame.add(button2);
frame.pack();
frame.setVisible(true);
|
When pack() is used, the Window will
size itself so that no controls are clipped. Using methods to calculate preferred size
As should be clear from the above examples, it is dangerous to ever specify a fixed size when designing a GUI screen. Furthermore, the positions of controls should not be referred to in
terms of x and y positions or widths and heights. Each control contains methods for dynamically calculating its preferred size, and these methods should be deployed to give your GUI maximum fluidity. Table 1 shows some examples of methods for calculating control size.
Table 1. Methods for calculating control size
| Control | Description | Method | | Button | Show the current label text | setLabel(String) or setText(String) | | Text | Show a number of characters | setColumns(int) | | Label | Show the text of the label | setLabel(String) or setText(String) | | Text area | Show a number of rows and columns of characters | setRows(int) and setColumns(int) | | List | Show a number of rows | Calculated based on the number of rows added |
Listing 4 shows the construction of each of the above methods for the
simple GUI.
Frame frame = new Frame();
frame.setLayout(new FlowLayout());
Button button1 = new Button("Next");
frame.add(button1);
TextField text1 = new TextField();
text1.setColumns(10);
frame.add(text1);
Label label1 = new Label("First Name:");
frame.add(label1);
TextArea textArea1 = new TextArea("This is some text in a text area");
textArea1.setRows(2);
textArea1.setColumns(10);
frame.add(textArea1);
List list1 = new List();
list1.add("FirstItem");
list1.add("SecondItem");
list1.add("ThirdItem");
list1.add("FourthItem");
list1.add("FifthItem");
frame.add(list1);
frame.pack();
frame.setVisible(true);
|
Figure 7 shows the impact of the above methods on our GUI. Figure 7. Results of using preferred-size methods

An advanced layout manager
Up until now we have dealt with only the most simple of GUIs. We've shown you how a GUI can be constructed using no layout manager, where each control receives its fixed location and size through the setBounds(Rectangle) method. And we've shown you how to use the simplest layout manager, FlowLayout, which deploys the method getPreferredSize() and individual control methods to size and position each control. With FlowLayout, you control only the order in which the components are laid out. For more control, you'll have to use what is known as a constrained layout manager. A constrained layout manager is one that has a constraint object associated with it. Whenever a component is added to a container it is done so with a constraint object, which can be thought of as a layout hint. An example of a constrained layout manager is GridBagLayout, which has the constraint object GridBagConstraints. GridBagLayout lets you
break the screen into a series of logical rows and columns. Listing 5 shows how we would add a GridBagLayout to
each of the five controls that were used in the FlowLayout example.
The GridBagConstraints object is the second argument to the add(Component,Object) method that adds each component to the frame.
Frame frame = new Frame();
frame.setLayout(new GridBagLayout());
GridBagConstraints constraints = new GridBagConstraints();
Button button1 = new Button("Next");
frame.add(button1 , constraints);
TextField text1 = new TextField();
text1.setColumns(10);
constraints.gridx = 1;
frame.add(text1 , constraints);
Label label1 = new Label("First Name:");
constraints.gridx = 2;
frame.add(label1 , constraints );
TextArea textArea1 = new TextArea("This is some text in a text area");
textArea1.setRows(2);
textArea1.setColumns(10);
constraints.gridx = 0;
constraints.gridy = 1;
frame.add(textArea1 , constraints );
List list1 = new List();
list1.add("FirstItem");
list1.add("SecondItem");
list1.add("ThirdItem");
list1.add("FourthItem");
list1.add("FifthItem");
constraints.gridx = 1;
frame.add(list1 , constraints );
frame.pack();
frame.setVisible(true);
List list1 = new List();
list1.add("FirstItem");
list1.add("SecondItem");
list1.add("ThirdItem");
list1.add("FourthItem");
list1.add("FifthItem");
constraints.gridx = 1;
frame.add(list1 , constraints );
frame.pack();
frame.setVisible(true);
|
Figure 8 shows how using GridBagLayout results in
the separation of the controls into columns on the GUI screen. Figure 8. Results of using GridBagLayout for controls

Control placement and the sizing of
columns
The button Next has been placed in the top-left cell with a gridx of 0 and a gridy of 0. The gridx constraint for the text field is set to 1, so the text field is placed in the column next to the button. The label, with a gridx of 2, is placed in the next (or third) column over. Next we see that the text area has been placed in the 0 column, which also contains the button. GridBagLayout uses the preferred size of the largest control in each column to set that column's size. This ensures that no control will be clipped by
being in a column that isn't wide enough or a row that isn't high enough to accommodate it. In the case of the button and the text area, both are in the same column and the text area is the larger of the two. The column will thus be sized to accommodate the text area, which means there will be extra space around the button. In the previous example, this is handled by placing the smaller control in the center of the column. A more desirable effect might be to place the button on the left side of the column. Likewise, the text box could be anchored on the left or right wall of the column. Anchoring a control to a wall of the column ensures that the control will always grow as the column does to make that space available to the user. You anchor a control by setting the anchor field of the GridBagConstraints object. The value is the compass point of the edge of the column that you want to anchor the control to. For example, to make the button anchor on the left side, you would use:
constraints.anchor = GridBagConstraints.WEST;
|
This command will anchor the left edge of the control to the left edge of the
column. If the preferred height of all the controls in the same row as the anchored control is larger than that control, then by default it will be centered vertically as well. If you want to fix a control to a particular corner, combinations of the compass points can be used, as in
the following example:
constraints.anchor = GridBagConstraints.NORTHWEST
|
To make a control anchor on the left but fill to take up all available space, use the fill property, as follows:
constraints.fill = GridBagConstraints.HORIZONTAL;
|
If you want the fill to be both horizontal and vertical, then the
Both value should be used:
constraints.fill = GridBagConstraints.Both
|
Another thing we might want to do using the same GUI example is to use the column beneath the label for the list box. This will enable the list to span two columns. The parameter to create this effect is the Gridwidth field on the GridBagConstraints, as shown here:
constraints.gridwidth = 2
|
As well as occupying more than one column, you can specify the use of more than one row, using the gridheight field. The result of the inclusion of all these fields in our code is shown in Figure 9. Figure 9. Results of adding GridBagConstraints to controls

And here is the code with all the new fields included.
Frame frame = new Frame();
frame.setLayout(new GridBagLayout());
GridBagConstraints constraints = new GridBagConstraints();
Button button1 = new Button("Next");
constraints.anchor = GridBagConstraints.WEST;
frame.add(button1 , constraints);
TextField text1 = new TextField();
text1.setColumns(10);
constraints.gridx = 1;
constraints.fill = GridBagConstraints.HORIZONTAL;
frame.add(text1 , constraints);
Label label1 = new Label("First Name:");
constraints.gridx = 2;
frame.add(label1 , constraints );
TextArea textArea1 = new TextArea("This is some text in a text area");
textArea1.setRows(2);
textArea1.setColumns(10);
constraints.gridx = 0;
constraints.gridy = 1;
frame.add(textArea1 , constraints );
List list1 = new List();
list1.add("FirstItem");
list1.add("SecondItem");
list1.add("ThirdItem");
list1.add("FourthItem");
list1.add("FifthItem");
constraints.gridx = 1;
constraints.gridwidth = 2;
frame.add(list1 , constraints );
frame.pack();
frame.setVisible(true);
|
Working with extra space in the window
Thus far we've looked at methods to work with controls and columns; another element we want to look at is the overall space in the window and how this space is used in the design and development of the GUI. To illustrate just how important consideration of space is in GUI design, Figure 10 shows how our simple GUI looks with the controls positioned by default (that is, centered) and the fluidity of window size unaccounted for. Figure 10. A GUI with poorly utilized space

Rather than simply wasting the excess room on whitespace, we could manipulate it to make the controls larger. By default controls do not grow with window size because this wouldn't make sense for controls such as buttons and labels. Text boxes and list boxes, however, would benefit from such growth. To specify growth in the event of an expansion of window size, we use the weightx and weighty fields on GridBagConstraints. These fields take values between 0 and 1.0, the default being 0. When there is excess horizontal space the GridBagLayout class asks each control for its weightx field and divides the excess space up between all controls in proportion to each one's weightx. Using the simple GUI example, we might give the TextField a weightx of 0.5 and the list box a weightx of 1. This would result in the excess horizontal
space being divided up across the text field and the list box by a ratio of 1:2. Giving the listbox a weighty of 1.0 would enable it to grow, additionally, to use all of the excess vertical space, as shown in Figure 11. Figure 11. The results of using weightx and weighty fields

When weightx and weighty are used, the excess space is not given to the control that it is specified to; rather, it is given to the column. Whether the control uses the excess space or not depends on how the control is anchored and filled. For the list box in Figure 9 to use the extra space allocated to the column, it had to be
specified as fill = BOTH so it filled both horizontally and
vertically to take up the excess space. The final code for our simple GUI is shown in Listing 7. This simple GUI is designed for portability and extensibility. It takes advantage of an advanced layout manager to dynamically position and size controls as essential variables such as string length and window size change.
Frame frame = new Frame();
frame.setLayout(new GridBagLayout());
GridBagConstraints constraints = new GridBagConstraints();
Button button1 = new Button("Next");
constraints.anchor = GridBagConstraints.WEST;
frame.add(button1 , constraints);
TextField text1 = new TextField();
text1.setColumns(10);
constraints.gridx = 1;
constraints.fill = GridBagConstraints.HORIZONTAL;
frame.add(text1 , constraints);
Label label1 = new Label("First Name:");
constraints.gridx = 2;
frame.add(label1 , constraints );
TextArea textArea1 = new TextArea("This is some text in a text area");
textArea1.setRows(2);
textArea1.setColumns(10);
constraints.gridx = 0;
constraints.gridy = 1;
constraints.weightx = 0.5;
frame.add(textArea1 , constraints );
List list1 = new List();
list1.add("FirstItem");
list1.add("SecondItem");
list1.add("ThirdItem");
list1.add("FourthItem");
list1.add("FifthItem");
constraints.gridx = 1;
constraints.gridwidth = 2;
constraints.weightx = 1.0;
constraints.weighty = 1.0;
constraints.fill = GridBagConstraints.BOTH;
frame.add(list1 , constraints );
frame.pack();
frame.setVisible(true);
|
 |
Conclusion
In this article, we've pinpointed the common mistakes and assumptions that can lead to poor GUI design and a frustrating development process. We've also shown you how Java layout managers can work to correct these mistakes and assumptions, simply by providing a common, yet flexible framework for GUI design and development. Using a layout manager as the foundation of the GUI design process allows designers to not only present the development team with a screen shot of the prototyped GUI, but also to specify more precisely the layout manager settings for each control. This precision frees the development team from having to second guess the designer's intentions for what the GUI should look like when, for example, the fonts change, the window size changes, or the application is run on a different operating system. Instead, designers and developers can work together to analyze and understand how their GUI will behave under different run-time conditions. Ultimately, using layout managers can lead to GUIs that more closely meet the original specifications set by the designer, while also more closely meeting the run-time requirements set by the development team.
Resources
About the authors  | |  |
Joe Winchester is a software developer at the IBM Research Triangle Park lab in North Carolina, working on development tools for WebSphere. He is currently working on builders that enable developers to create rich GUI applications by connecting together JavaBeans. Contact Joe at
joewin@us.ibm.com. |
 | |  |
Renee Schwartz is a software designer working at the IBM Research Triangle Park lab in North Carolina. She has worked extensively on user interface design for the IBM WebSphere software product family and is continually interested in combining design techniques with coding principles. Contact Renee at rsch@us.ibm.com.
|
Rate this page
|