by Laura Lemay
For the past five days you've concentrated on creating applets that do very simple things: display text, play an animation or a sound, or interact with the user. When you get past that point, however, you may want to start creating more complex applets that behave like real applications embedded in a Web page-applets that start to look like real GUI applications with buttons, menus, text fields, and other elements.
It's this sort of real work in Java applets and applications for which Java's Abstract Windowing Toolkit, or awt, was designed. You've actually been using the awt all along, as you might have guessed from the classes you've been importing. The Applet class and most of the classes you've been using this week are all integral parts of the awt.
The awt provides the following:
Today you'll learn about how to use all these things in your Java applets. Tomorrow you'll learn about creating windows, menus, and dialog boxes, which enable you to pop up separate windows from the browser window. In addition, you can use the awt in standalone applications, so everything you've learned so far this week can still be used. If you find the framework of the Web browser too limiting, you can take your awt background and start writing full-fledged Java applications.
Today, however, you'll continue focusing on applets.
| Note |
This is by far the most complex lesson so far, and it's a long chapter as well. There's a lot to cover and a lot of code to go through today, so if it starts becoming overwhelming, you might want to take two days (or more) for this one. |
The basic idea behind the awt is that a graphical Java program is a set of nested components, starting from the outermost window all the way down to the smallest UI component. Components can include things you can actually see on the screen, such as windows, menu bars, buttons, and text fields, and they can also include containers, which in turn can contain other components. Figure 13.1 shows how a sample page in a Java browser might include several different components, all of which are managed through the awt.
This nesting of components within containers within other components creates a hierarchy of components, from the smallest check box inside an applet to the overall window on the screen. The hierarchy of components determines the arrangement of items on the screen and inside other items, the order in which they are painted, and how events are passed from one component to another.
These are the major components you can work with in the awt:
The classes inside the java.awt package are written and organized to mirror the abstract structure of containers, components, and individual UI components. Figure 13.2 shows some of the class hierarchy that makes up the main classes in the awt. The root of most of the awt components is the class Component, which provides basic display and event-handling features. The classes Container, Canvas, TextComponent, and many of the other UI components inherit from Component. Inheriting from the Container class are objects that can contain other awt components-the Panel and Window classes, in particular. Note that the java.applet.Applet class, even though it lives in its own package, inherits from Panel, so your applets are an integral part of the hierarchy of components in the awt system.
Figure 13.2 : A Partial awt class Hierarchy.
A graphical user interface-based application that you write by using the awt can be as complex as you like, with dozens of nested containers and components inside each other. The awt was designed so that each component can play its part in the overall awt system without needing to duplicate or keep track of the behavior of other parts in the system.
In addition to the components themselves, the awt also includes a set of layout managers. Layout managers determine how the various components are arranged when they are displayed onscreen, and their various sizes relative to each other. Because Java applets and applications that use the awt can run on different systems with different displays, different fonts, and different resolutions, you cannot just stick a particular component at a particular spot on the window. Layout managers help you create UI layouts that are dynamically arranged and can be displayed anywhere the applet or application might be run.
The simplest form of awt component is the basic UI component. You can create and add these to your applet without needing to know anything about creating containers or panels-your applet, even before you start painting and drawing and handling events, is already an awt container. Because an applet is a container, you can put other awt components-such as UI components or other containers-into it.
In this section, you'll learn about the basic UI components: labels, buttons, check boxes, choice menus, and text fields. In each case, the procedure for creating the component is the same-you first create the component and then add it to the panel that holds it, at which point it is displayed on the screen. To add a component to a panel (such as your applet, for example), use the add() method:
public void init() {
Button b = new Button("OK");
add(b);
}
Here the add() method refers to the current applet-in other words, it means "add this element to me." You can also add elements to other containers, as you'll learn later.
Note that where the component appears in the panel depends on the layout manager that panel is defined to have. In these examples I've used both flow layouts and grid layouts, depending on which makes the applet look better. You'll learn more about panels and layouts in the next section.
Note also that each of these components has an action associated with it-that is, something that component does when it's activated. Actions generally trigger events or other activities in your applet (they are often called callbacks in other window toolkits). In this section, you'll focus on creating the components themselves; you'll learn about adding actions to them later in today's lesson.
On to the components!
The simplest form of UI component is the label, which is, effectively, a text string that you can use to label other UI components. Labels are not editable; they just label other components on the screen.
The advantages that a label has over an ordinary text string (that you'd draw using drawString() in the paint() method) are
A label is an uneditable text string that acts as a description for other awt components.
To create a label, use one of the following constructors:
You can change the label's font with the setFont() method, either called on the label itself to change the individual label, or on the enclosing component to change all the labels. Here's some simple code to create a few labels in Helvetica Bold (Figure 13.3 shows how this looks onscreen):
Figure 13.3 : Three labels with various alignments.
| Note |
This code uses the setLayout method to create a new layout manager. Don't worry about that line right now; you'll learn more about layout managers in the next section. |
import java.awt.*;
public class LabelTest extends java.applet.Applet {
public void init() {
setFont(new Font ("Helvetica", Font.BOLD, 14));
setLayout(new GridLayout(3,1));
add(new Label("aligned left", Label.LEFT));
add(new Label("aligned center", Label.CENTER));
add(new Label("aligned right", Label.RIGHT));
}
}
When you have a Label object,
you can use methods defined in the Label
class to get and set the values of the text, as shown in Table
13.1.
| Method | Action |
| getText() | Returns a string containing this label's text |
| setText(String) | Changes the text of this label |
| getAlignment() | Returns an integer representing the alignment of this label:0 is Label.LEFT |
| setAlignment(int) | Changes the alignment of this label to the given integer-use the class variables listed in the getAlignment() method |
The second user interface component to explore is the button. Buttons are simple UI components that trigger some action in your interface when they are pressed. For example, a calculator applet might have buttons for each number and operator, or a dialog box might have buttons for OK and Cancel.
A button is a UI component that, when "pressed" (selected) with the mouse, triggers some action.
To create a button, use one of the following constructors:
Once you have a Button object, you can get the value of the button's label by using the getLabel() method and set the label using the setLabel(String) method.
Figure 13.4 shows some simple buttons, created using the following code:
Figure 13.4 : Four buttons in Netscape.
public class ButtonTest extends java.applet.Applet {
public void init() {
add(new Button("Rewind"));
add(new Button("Play"));
add(new Button("Fast Forward"));
add(new Button("Stop"));
}
}
Check boxes are user-interface components that have two states: on and off (or checked and unchecked, selected and unselected, true and false, and so on). Unlike buttons, check boxes usually don't trigger direct actions in a UI, but instead are used to indicate optional features of some other action.
Check boxes can be used in two ways:
The latter kind of check boxes are called radio buttons or check box groups, and are described in the next section.
Check boxes are UI components that can be selected or deselected (checked or unchecked) to provide options. Nonexclusive check boxes can be checked or unchecked independently of other check boxes.
Exclusive check boxes, sometimes called radio buttons, exist in groups; only one in the group can be checked at one time.
Nonexclusive check boxes can be created by using the Checkbox class. You can create a check box using one of the following constructors:
Figure 13.5 shows a few simple check boxes (only Underwear is selected) generated using the following code:
Figure 13.5 : Five check boxes, one selected.
import java.awt.*;
public class CheckboxTest extends java.applet.Applet {
public void init() {
setLayout(new FlowLayout(FlowLayout.LEFT));
add(new Checkbox("Shoes"));
add(new Checkbox("Socks"));
add(new Checkbox("Pants"));
add(new Checkbox("Underwear", null, true));
add(new Checkbox("Shirt"));
}
}
Table 13.2 lists some of the check box methods.
| Method | Action |
| getLabel() | Returns a string containing this check box's label |
| setLabel(String) | Changes the text of the check box's label |
| getState() | Returns true or false, based on whether the check box is selected |
| setState(boolean) | Changes the check box's state to selected (true) or unselected (false) |
Radio buttons have the same appearance as check boxes, but only one in a series can be selected at a time. To create a series of radio buttons, first create an instance of CheckboxGroup:
CheckboxGroup cbg = new CheckboxGroup();
Then create and add the individual check boxes using the constructor with three arguments (the first is the label, the second is the group, and the third is whether that check box is selected). Note that because radio buttons, by definition, have only one in the group selected at a time, the last true to be added will be the one selected by default:
add(new Checkbox("Yes", cbg, true);
add(new Checkbox("No", cbg, false);
Here's a simple example (the results of which are shown in Figure 13.6):
Figure 13.6 : Six radio buttons (exclusive check boxes), one selected.
import java.awt.*;
public class CheckboxGroupTest extends java.applet.Applet {
public void init() {
setLayout(new FlowLayout(FlowLayout.LEFT));
CheckboxGroup cbg = new CheckboxGroup();
add(new Checkbox("Red", cbg, false));
add(new Checkbox("Blue", cbg, false));
add(new Checkbox("Yellow", cbg, false));
add(new Checkbox("Green", cbg, true));
add(new Checkbox("Orange", cbg, false));
add(new Checkbox("Purple", cbg, false));
}
}
All the check box methods shown in Table 13.2 in the previous
section can be used with
the check boxes in the group. In addition, you can use the getCheckboxGroup()
and setCheckboxGroup() methods
(defined in the Checkbox()
class) to access and change the group of any given check box.
Finally, the getCurrent() and setCurrent(Checkbox) methods, defined in CheckboxGroup, can be used to get or set the currently selected check box.
The choice menu is a more complex UI component than labels, buttons, or check boxes. Choice menus are pop-up (or pull-down) menus from which you can select an item. The menu then displays that choice on the screen. The function of a choice menu is the same across platforms, but its actual appearance may vary from platform to platform.
Note that choice menus can have only one item selected at a time. If you want to be able to choose multiple items from the menu, use a scrolling list instead (you'll learn more about scrolling lists later today, in the section "More UI Components").
Choice menus are pop-up menus of items from which you can choose one item.
To create a choice menu, create an instance of the Choice class and then use the addItem() method to add individual items to it in the order in which they should appear. Finally, add the entire choice menu to the panel in the usual way. Here's a simple program that builds a choice menu of fruits; Figure 13.7 shows the result (with the menu pulled down):
import java.awt.*;
public class ChoiceTest extends java.applet.Applet {
public void init() {
Choice c = new Choice();
c.addItem("Apples");
c.addItem("Oranges");
c.addItem("Strawberries");
c.addItem("Blueberries");
c.addItem("Bananas");
add(c);
}
}
Even after your choice menu has been added to a panel, you can
continue to add items to that menu with the addItem()
method. Table 13.3 shows some other methods that may be useful
in working with choice menus.
| Method | Action |
| getItem(int) | Returns the string item at the given position (items inside a choice begin at 0, just like arrays) |
| countItems() | Returns the number of items in the menu |
| getSelectedIndex() | Returns the index position of the item that's selected |
| getSelectedItem() | Returns the currently selected item as a string |
| select(int) | Selects the item at the given position |
| select(String) | Selects the item with the given string |
Unlike the UI components up to this point, which only enable you to select among several options to perform an action, text fields allow you to enter and edit text. Text fields are generally only a single line and do not have scrollbars; text areas, which you'll learn about later today, are better for larger amounts of text.
Text fields are different from labels in that they can be edited; labels are good for just displaying text, text fields for getting text input from the user.
Text fields provide an area where you can enter and edit a single line of text.
To create a text field, use one of the following constructors:
For example, the following line creates a text field 30 characters wide with the string "Enter Your Name" as its initial contents:
TextField tf = new TextField("Enter Your Name", 30);
add(tf);
| Tip |
Text fields include only the editable field itself. You usually need to include a label with a text field to indicate what belongs in that text field. |
You can also create a text field that obscures the characters typed into it-for example, for password fields. To do this, first create the text field itself; then use the setEchoCharacter() method to set the character that is echoed on the screen. Here is an example:
TextField tf = new TextField(30);
tf.setEchoCharacter('*');
Figure 13.8 shows three text boxes (and labels) that were created using the following code:
Figure 13.8 : Three text fields to allow input from the user.
add(new Label("Enter your Name"));
add(new TextField("your name here", 45));
add(new Label("Enter your phone number"));
add(new TextField(12));
add(new Label("Enter your password"));
TextField t = new TextField(20);
t.setEchoCharacter('*');
add(t);
The text in the first field (your name here) was initialized in the code; I typed the text in the remaining two boxes just before taking the snapshot.
Text fields inherit from the class TextComponent
and have a whole suite of methods, both inherited from that class
and defined in their own class, that may be useful to you in your
Java programs. Table 13.4 shows a selection of those methods.
| Method | Action |
| getText() | Returns the text this text field contains (as a string) |
| setText(String) | Puts the given text string into the field |
| getColumns() | Returns the width of this text field |
| select(int, int) | Selects the text between the two integer positions (positions start from 0) |
| selectAll() | Selects all the text in the field |
| isEditable() | Returns true or false based on whether the text is editable |
| setEditable(boolean) | true (the default) enables text to be edited; false freezes the text |
| getEchoChar() | Returns the character used for masking input |
| echoCharIsSet() | Returns true or false based on whether the field has a masking character |
| Note |
The descriptions of the getEchoChar() and echoCharIsSet() methods refer to masking user input. User input masking is a technique of limiting user input to a specific type, such as a number. Other types of user input masking include dates and phone numbers, where there are a specific number of numeric digits arranged in a constant format. |
awt panels can contain UI components or other panels. The question now is how those components are actually arranged and displayed onscreen.
In other windowing systems, UI components are often arranged using hard-coded pixel measurements-put a text field at the position 10,30, for example-the same way you used the graphics operations to paint squares and ovals on the screen. In the awt, your UI design may be displayed on many different window systems on many different screens and with many different kinds of fonts with different font metrics. Therefore, you need a more flexible method of arranging components on the screen so that a layout that looks nice on one platform isn't a jumbled, unusable mess on another.
For just this purpose, Java has layout managers, insets, and hints that each component can provide to help dynamically lay out the screen.
Note that the nice thing about awt components and user-interface items is that you don't have to paint them-the awt system manages all that for you. If you have graphical components or images, or you want to create animation inside panels, you still have to do that by hand, but for most of the basic components, all you have to do is put them on the screen and Java will handle the rest.
The actual appearance of the awt components on the screen is usually determined by two things: how those components are added to the panel that holds them (either the order or through arguments to add()) and the layout manager that panel is currently using to lay out the screen. The layout manager determines how portions of the screen will be sectioned and how components within that panel will be placed.
The layout manager determines how awt components are dynamically arranged on the screen.
Each panel on the screen can have its own layout manager. By nesting panels within panels, and using the appropriate layout manager for each one, you can often arrange your UI to group and arrange components in a way that is functionally useful and that looks good on a variety of platforms and windowing systems. You'll learn about nesting panels in a later section.
The awt provides five basic layout managers: FlowLayout, GridLayout, BorderLayout, CardLayout, and GridBagLayout. To create a layout manager for a given panel, create an instance of that layout manager and then use the setLayout() method for that panel. This example sets the layout manager of the entire enclosing applet panel:
public void init() {
setLayout(new FlowLayout());
}
Setting the default layout manager, like creating user-interface components, is best done during the applet's initialization, which is why it's included here.
After the layout manager is set, you can start adding components to the panel. The order in which components are added or the arguments you use to add those components is often significant, depending on which layout manager is currently active. Read on for information about the specific layout managers and how they present components within the panel to which they apply.
The following sections describe the five basic Java awt layout managers.
The FlowLayout class is the most basic of layouts. Using flow layout, components are added to the panel one at a time, row by row. If a component doesn't fit onto a row, it's wrapped onto the next row. The flow layout also has an alignment, which determines the alignment of each row. By default, each row is centered.
Flow layout arranges components from left to right in rows. The rows are aligned left, right, or centered.
To create a basic flow layout with a centered alignment, use the following line of code in your panel's initialization (because this is the default pane layout, you don't need to include this line if that is your intent):
setLayout(new FlowLayout());
With the layout set, the order in which you add elements to the layout determines their position. The following code creates a simple row of six buttons in a centered flow layout (Figure 13.9 shows the result):
Figure 13.9 : Six buttons, arranged using a flow layout manager.
import java.awt.*;
public class FlowLayoutTest extends java.applet.Applet {
public void init() {
setLayout(new FlowLayout());
add(new Button("One"));
add(new Button("Two"));
add(new Button("Three"));
add(new Button("Four"));
add(new Button("Five"));
add(new Button("Six"));
}
}
To create a flow layout with an alignment other than centered, add the FlowLayout.RIGHT or FlowLayout.LEFT class variable as an argument:
setLayout(new FlowLayout(FlowLayout.LEFT));
You can also set horizontal and vertical gap values by using flow layouts. The gap is the number of pixels between components in a panel; by default, the horizontal and vertical gap values are three pixels, which can be very close indeed. Horizontal gap spreads out components to the left and to the right; vertical gap spreads them to the top and bottom of each component. Add integer arguments to the flow layout constructor to increase the gap. Figure 13.10 shows the result of adding a gap of 30 points in the horizontal and 10 in the vertical directions, like this:
Figure 13.10: Flow layout with a gap of 10 points.
setLayout(new FlowLayout(FlowLayout.LEFT, 30, 10));
Grid layouts offer more control over the placement of components inside a panel. Using a grid layout, you portion off the display area of the panel into rows and columns. Each component you then add to the panel is placed in a cell of the grid, starting from the top row and progressing through each row from left to right (here's where the order of calls to the add() method are very relevant to how the screen is laid out).
To create a grid layout, indicate the number of rows and columns you want the grid to have when you create a new instance of the GridLayout class. Here's a grid layout with three rows and two columns (Figure 13.11 shows the result):
Figure 13.11: Six buttons displayed using a grid layout of three rows and two columns.
import java.awt.*;
public class GridLayoutTest extends java.applet.Applet {
public void init() {
setLayout(new GridLayout(3,2);
add(new Button("One"));
add(new Button("Two"));
add(new Button("Three"));
add(new Button("Four"));
add(new Button("Five"));
add(new Button("Six"));
}
}
Grid layouts can also have a horizontal and vertical gap between components. To create gaps, add those pixel values:
setLayout(new GridLayout(3, 3, 10, 30));
Figure 13.12 shows a grid layout with a 10-pixel horizontal gap and a 30-pixel vertical gap.
Figure 13.12: A grid layout with horizontal and vertical gaps.
Border layouts behave differently from flow and grid layouts. When you add a component to a panel that uses a border layout, you indicate its placement as a geographic direction: north, south, east, west, or center. (See Figure 13.13.) The components around all the edges are laid out with as much size as they need; the component in the center, if any, gets any space left over.
Figure 13.13: Where components go in a border layout.
To use a border layout, you create it as you do the other layouts; then you add the individual components with a special add() method that has two arguments. The first argument is a string indicating the position of the component within the layout, and the second is the component to add:
add("North", new TextField("Title", 50));
You can also use this form of add() for the other layout managers; the string argument will just be ignored if it's not needed.
Here's the code to generate the border layout shown in Figure 13.13:
import java.awt.*;
public class BorderLayoutTest extends java.applet.Applet {
public void init() {
setLayout(new BorderLayout());
add("North", new Button("One"));
add("East", new Button("Two"));
add("South", new Button("Three"));
add("West", new Button("Four"));
add("Center", new Button("Five"));
add(new Button("Six"));
}
}
Border layouts can also have horizontal and vertical gaps. Note that the north and south components extend all the way to the edge of the panel, so the gap will result in less vertical space for the east, right, and center components. To add gaps to a border layout, include those pixel values in the constructor as with the other layout managers:
setLayout(new BorderLayout(10, 10));
Card layouts behave much differently from the other layouts. When you add components to one of the other layout managers, all those components appear on the screen at once. Card layouts are used to produce slide shows of components, one at a time. If you've ever used the HyperCard program on the Macintosh, or seen dialog boxes on windows with several different tabbed pages, you've worked with the same basic idea.
When you create a card layout, the components you add to the outer panel will be other container components-usually other panels. You can then use different layouts for those individual cards so that each screen has its own look.
Cards, in a card layout, are different panels added one at a time and displayed one at a time. If you think of a card file, you'll get the idea; only one card can be displayed at once, but you can switch between cards.
When you add each card to the panel, you can give it a name. Then, to flip between the container cards, you can use methods defined in the CardLayout class to move to a named card, move forward or back, or move to the first card or to the last card. Typically you'll have a set of buttons that call these methods to make navigating the card layout easier.
Here's a simple snippet of code that creates a card layout containing three cards:
setLayout(new CardLayout());
//add the cards
Panel one = new Panel()
add("first", one);
Panel two = new Panel()
add("second", two);
Panel three = new Panel()
add("third", three);
// move around
show(this, "second"); //go to the card named "second"
show(this, "third"); //go to the card named "third"
previous(this); //go back to the second card
first(this); // got to the first card
I've saved grid bag layouts for last because although they are the most powerful way of managing awt layout, they are also extremely complicated.
Using one of the other four layout managers, it can sometimes be difficult to get the exact layout you want without doing a lot of nesting of panels within panels. Grid bags provide a more general-purpose solution. Like grid layouts, grid bag layouts allow you to arrange your components in a grid-like layout. However, grid bag layouts also allow you to control the span of individual cells in the grid, the proportions between the rows and columns, and the arrangement of components inside cells in the grid.
To create a grid bag layout, you actually use two classes: GridBagLayout, which provides the overall layout manager, and GridBagConstraints, which defines the properties of each component in the grid-its placement, dimensions, alignment, and so on. It's the relationship between the grid bag, the constraints, and each component that defines the overall layout.
In its most general form, creating a grid bag layout involves the following steps:
Here's some simple code that sets up the layout and then creates constraints for a single button (don't worry about the various values for the constraints; I'll cover these later on in this section):
// set up layout
GridBagLayout gridbag = new GridBagLayout();
GridBagConstraints constraints = new GridBagConstraints();
setLayout(gridbag);
// define constraints for the button
Button b = new Button("Save");
constraints.gridx = 0;
constraints.gridy = 0;
constraints.gridwidth = 1;
constraints.gridheight = 1;
constraints.weightx = 30;
constraints.weighty = 30;
constraints.fill = GridBagConstraints.NONE;
constraints.anchor = GridBagConstraints.CENTER;
// attach constraints to layout, add button
gridbag.setConstraints(b, constraints);
add(b);
By far, the most tedious part of this process is setting up the constraints for each component (as you can see from this example, you have to set all those constraints for every component you want to add to the panel). In addition to the tedium, constraints aren't all that easy to understand; they have many different values, many of which are interrelated, which means that changing one may have strange effects on others.
Given the numerous constraints, it helps to have a plan and to deal with each kind of constraint one at a time. There are four steps I like to follow in this process. Let's walk through each of them.
The first place to start in the grid bag layout is on paper. Sketching out your UI design beforehand-before you write even a single line of code-will help enormously in the long run with trying to figure out where everything goes. So put your editor aside for a second, pick up a piece of paper and a pencil, and let's build the grid.
Figure 13.14 shows the panel layout we'll be building in this example. Figure 13.15 shows the same layout with a grid imposed on top of it. Your layout will have a grid similar to this one, with rows and columns forming individual cells.
Figure 13.14: A grid bag layout.
Figure 13.15: The grid bag layout from Figure 13.14, with grid imposed.
Keep in mind as you draw your grid that each component must have its own cell. You cannot put more than one component into the same cell. The reverse is not true, however; one component can span multiple cells in the x or y directions (as in the OK button in the bottom row, which spans two columns). Note in Figure 13.15 that the labels and text fields have their own grids and that the button spans two column cells.
While you're still working on paper, something that will help you later is to label the cells with their x and y coordinates. These aren't pixel coordinates; rather, they're cell coordinates. The top-left cell is 0,0. The next cell to the right of that in the top row is 1,0. The cell to the right of that is 2,0. Moving to the next row, the leftmost cell is 1,0, the next cell in the row is 1,1, and so on. Label your cells on the paper with these numbers; you'll need them later when we do the code for this example. Figure 13.16 shows the numbers for each of the cells in this example.
Figure 13.16: The grid bag layout from Figure 13.14, with cell coordinates.
Let's go back to Java and start implementing the layout you've just drawn on paper. Initially we're going to focus exclusively on the layout-getting the grid and the proportions right. For that, it helps to not work with actual UI elements. I like to use buttons as placeholders for the actual elements in the layout until I can get everything set up right, and then change the buttons to the right elements.
To cut down on the amount of typing we have to do to set up all those constraints, I'm going to start by defining a helper method that takes several values and sets the constraints for those values. buildConstraints() takes seven arguments: a GridBagConstraints object and six integers representing the GridBagConstraints instance variables gridx, gridy, gridwidth, gridheight, weightx, and weighty. You'll learn what these actually do soon; for now, here's the code to the helper method that we'll use further on in this example:
void buildConstraints(GridBagConstraints gbc, int gx, int gy,
int gw, int gh, int wx, int wy) {
gbc.gridx = gx;
gbc.gridy = gy;
gbc.gridwidth = gw;
gbc.gridheight = gh;
gbc.weightx = wx;
gbc.weighty = wy;
}
Now let's move on to the init() method, where all the layout actually occurs. Here's the basic method definition, where we'll define the GridBagLayout to be the initial layout manager and create a constraints object (an instance of GridBagConstraints):
public void init() {
GridBagLayout gridbag = new GridBagLayout();
GridBagConstraints constraints = new GridBagConstraints();
setLayout(gridbag);
constraints.fill = GridBagConstraints.BOTH;
}
One more small note of explanation: That last line, which sets the value of constraints.fill, will be removed (and explained) later. It's there so that the components will fill the entire cell in which they're contained, which makes it easier to see what's going on. Add it for now and you'll get a clearer idea of what it's for later.
Now we'll add the button placeholders to the layout (remember, we're focusing on basic grid organization at the moment, so we'll use buttons as placeholders for the actual UI elements you'll add later). Let's start with a single button so you can get a feel for setting its constraints. This code will go into the init() method just after the setLayout line:
// Name label
buildConstraints(constraints, 0, 0, 1, 1, 100, 100);
Button label1 = new Button("Name:");
gridbag.setConstraints(label1, constraints);
add(label1);
These four lines set up the constraints for an object, create a new button, attach those constraints to that button, and then add it to the panel. Note that constraints for a component are stored in the GridBagConstraints object, so the component doesn't even have to exist to set up its constraints.
Now let's get down to details: Just what are the values for the constraints that we've plugged into the helper method buildConstraints?
The first two integer arguments are the gridx and gridy values of the constraints. These are the cell coordinates of the cell that contains this component. Remember how you wrote these down on the paper in step one? With the cells nearly numbered on paper, all you have to do is plug in the right values. Note that if you have a component that spans multiple cells, the cell coordinates are those of the cell in the top-left corner.
Here this button is in the top-left corner, so its gridx and gridy (the first two arguments to buildConstraints()) are 0 and 0, respectively.
The second two integer arguments are the gridwidth and gridheight. These are not the pixel widths and heights of the cells; rather, they are the number of cells this component spans: gridwidth for the columns and gridheight for the rows. Here this component spans only one cell, so the values for both are 1.
The last two integer arguments are for weightx and weighty. These are used to set up the proportions of the rows and columns-that is, how wide or deep they will be. Weights can become very confusing, so for now just set both values to 100. You'll deal with weights in step three.
After the constraints have been built, you can attach them to an object using the setConstraints() method. setConstraints90, which is a method defined in GridBagLayout, takes two arguments: the component (here a button) and the constraints for that button. Finally, you can add the button to the panel.
After you've set and assigned the constraints to one component, you can reuse that GridBagConstraints object to set up the constraints for the next object. This effectively means duplicating those four lines for each component in the grid, with different values for the buildConstraints() method. To save space, I'm just going to show you the buildConstraints() methods for the last four cells.
The second cell we'll add is the one that will hold the text box for the name. The cell coordinates for this one are 1,0 (second column, first row); it too spans only one cell, and the weights (for now) are also both 100:
buildConstraints(constraints, 1, 0, 1, 1, 100, 100);
The next two components, which will be a label and a text field, are nearly exactly the same as the previous two; the only difference is in their cell coordinates. The password label is at 0,1 (first column, second row), and the password text field is at 1,1 (second column, second row):
buildConstraints(constraints, 0, 1, 1, 1, 100, 100); buildConstraints(constraints, 1, 1, 1, 1, 100, 100);
And, finally, there is the OK button, which is a component that spans two cells in the bottom row of the panel. Here the cell coordinates are the left and topmost cell where the span starts (0,2). Here, unlike the previous components, we'll set gridwidth and gridheight to be something other than 1 because this cell spans multiple columns. The gridweight is 2 (it spans two cells), and the gridheight is 1 (it spans only one row):
buildConstraints(constraints, 0, 2, 2, 1, 100, 100);
Got it? Those are the placement constraints for all the components that you'll add to the grid layout. You will also need to assign each component's constraints to the layout manager and then add each component to the panel. Figure 13.17 shows the result so far. Note that you're not concerned about exact proportions here, or about making sure everything lines up. What you should keep track of at this point is making sure the grid is working, that there are the right number of rows and columns, that the spans are correct, and that nothing strange is going on (cells in the wrong place, cells overlapping, that kind of thing).
Figure 13.17: Grid bag layout, first pass.
The next step is to determine the proportions of the rows and columns in relation to other rows and columns. For example, in this case you'll want the labels (name and password) to take up less space than the text boxes. And you might want the OK button at the bottom to be only half the height of the two text boxes above it. You arrange the proportions of the cells within your layout using the weightx and weighty constraints.
The easiest way to think of weightx
and weighty is that their
values are either percentages of the total width and height of
the panel, or 0 if the weight
or height has been set by some other cell. The values of weightx
and weighty for all your
components, therefore, should sum to 100.
| Technical Note |
Actually, the weightx and weighty values are not percentages; they're simply proportions-they can have any value whatsoever. When the proportions are calculated, all the values in a direction are summed so that each individual value is in proportion to that total (in other words, divided into the total to actually get a percentage). Because this is incredibly non-intuitive, I find it far easier to look at the weights as percentages and to make sure they all sum up to 100 to make sure it's all coming out right. |
So which cells get values and which cells get 0? Cells that span multiple rows or columns should always be 0 in the direction they span. Beyond that, it's simply a question of picking a cell to have a value, and then all the other cells in that row or columns should be 0.
Let's look at the five calls to buildConstraints() we made in the last step:
buildConstraints(constraints, 0, 0, 1, 1, 100, 100); //name buildConstraints(constraints, 1, 0, 1, 1, 100, 100); //name text buildConstraints(constraints, 0, 1, 1, 1, 100, 100); //password buildConstraints(constraints, 1, 1, 1, 1, 100, 100); //password text buildConstraints(constraints, 0, 2, 2, 1, 100, 100); //OK button
We'll be changing those last two arguments in each call to buildConstraints to be either a value or 0. Let's start with the x direction (the proportions of the columns), which is the second-to-last argument in that list.
If you look back to Figure 13.15 (the picture of the panel with the grid imposed), you'll note that the second column is much larger than the first. If you were going to pick theoretical percentages for those columns, you might say that the first is 10 percent and the second is 90 percent (I'm making a guess here; that's all you need to do as well). With those two guesses, let's assign them to cells. We don't want to assign any values to the cell with the OK button because that cell spans both columns, and percentages there wouldn't work. So let's add them to the first two cells, the name label and the name text field:
buildConstraints(constraints, 0, 0, 1, 1, 10, 100); //name buildConstraints(constraints, 1, 0, 1, 1, 90, 100); //name text
And what about the values of the remaining two cells, the password label and text field? Because the proportions of the columns have already been set up by the name label and field, we don't have to reset them here. We'll give both of these cells and the one for the OK box 0 values:
buildConstraints(constraints, 0, 1, 1, 1, 0, 100); //password buildConstraints(constraints, 1, 1, 1, 1, 0, 100); //password text buildConstraints(constraints, 0, 2, 2, 1, 0, 100); //OK button
Note here that a 0 value does not mean that the cell has 0 width. These are proportions, not pixel values. A 0 simply means that the proportion has been set somewhere else; all 0 says is "stretch it to fit."
Now that the totals of all the weightx constraints are 100, let's move onto the weightys. Here there are three rows; glancing over the grid we drew, it looks like the button has about 20 percent and the text fields have the rest (40 percent each). As with the x values, we only have to set the value of one cell per row (the two labels and the button), with all the other cells having a weightx of 0.
Here are the final five calls to buildConstraints() with the weights in place:
buildConstraints(constraints, 0, 0, 1, 1, 10, 40); //name buildConstraints(constraints, 1, 0, 1, 1, 90, 0); //name text buildConstraints(constraints, 0, 1, 1, 1, 0, 40); //password buildConstraints(constraints, 1, 1, 1, 1, 0, 0); //password text buildConstraints(constraints, 0, 2, 2, 1, 0, 20); //OK button
Figure 13.18 shows the result with the correct proportions.
Figure 13.18: Grid bag layout second pass.
At this step, the goal here is to try to come up with some basic proportions for how the rows and cells will be spaced on the screen. You can make some basic estimates based on how big you expect the various components to be, but chances are you're going to use a lot of trial and error in this part of the process.
With the layout and the proportions in place, now you can replace the button placeholders with actual labels and text fields. And because you set everything up already, it should all work perfectly, right? Well, almost. Figure 13.19 shows what you get if you use the same constraints as before and replace the buttons with actual components.
Figure 13.19: Grid bag layout, almost there.
It's close, but it's weird. The text boxes are too tall, and the OK button stretches the width of the cell.
What's missing are the constraints that arrange the components inside the cell. There are two of them: fill and anchor.
The fill constraint determines, for components that can stretch in either direction (like text boxes and buttons), in which direction to stretch. fill can have one of four values, defined as class variables in the GridBagConstraints class:
| Note |
Keep in mind that this is dynamic layout. You're not going to set up the actual pixel dimensions of any components; rather, you're telling these elements in which direction they can grow given a panel that can be of any size. |
By default, the fill constraint for all components is NONE. So why are those text fields and labels filling the cells? If you remember way back to the start of the code for this example, I added this line to the init() method:
constraints.fill = GridBagConstraints.BOTH;
Now you know what it does. For the final version of this applet, you'll want to remove that line and add fill values for each independent component.
The second constraint that affects how a component appears in the cell is anchor. This constraint applies only to components that aren't filling the whole cell, and it tells the awt where inside the cell to place the component. The possible values for the anchor constraint are GridBagConstraints.CENTER, which aligns the component both vertically and horizontally inside the cell, or one of eight direction values: GridBagConstraints.NORTH, GridBagConstraints.NORTHEAST, GridBagConstraints.EAST, GridBagConstraints.SOUTHEAST, GridBagConstraints.SOUTH, GridBagConstraints.SOUTHWEST, GridBagConstraints.WEST, or GridBagConstraints.NORTHWEST. The default value of anchor is GridBagConstraints.CENTER.
You set these constraints in the same way you did all the other ones: by changing instance variables in the GridBagConstraints object. Here you can change the definition of buildConstraints() to take two more arguments (they're ints), or you could just set them in the body of the init() method. I prefer the latter way.
Be careful with defaults. Keep in mind that because you're reusing the same GridBagConstraints object for each component, there may be some values left over after you're done with one component. On the other hand, if a fill or anchor from one object is the same as the one before it, you don't have to reset that object.
For this example, I'm going to make three changes to the fills and anchors of the components:
I'm not going to show you all the code for this here; the full
code for the example is at the end of this section. You can see
the changes I've mad† Ú�kñÓ¦fÌø§©YKç½÷T€ºï“….q‹=ÅþÞXë\ìüK€ ñMá�
endstream
endobj
507 0 obj
<<
/Type /Page
/Parent 2904 0 R
/Resources 508 0 R
/Contents 509 0 R
/MediaBox [ 0 0 531 657 ]
/CropBox [ 0 0 531 657 ]
/Rotate 0
/Thumb 2334 0 R
>>
endobj
508 0 obj
<<
/ProcSet [ /PDF /Text /ImageB ]
/Font << /F5 1455 0 R /F7 1634 0 R /F8 1622 0 R /F10 1631 0 R /F12 1628 0 R >>
/ExtGState << /GS1 1621 0 R /GS2 1620 0 R >>
>>
endobj
509 0 obj
<< /Length 2735 /Filter /FlateDecode >>
stream
H‰„Wës×ÿö¸3ýbR[ÚÕÓî·ò&�6Á"ˆ@šYkWÒÆû»+Ëš6�¤¦!��†¶I?ô9Äi›„$“Â$˜qÒ–†@l
´ÆoË„ø!¿´Z÷Ü{w¥•ŽÙ»>÷üÎï<î9±ð�CO1,zž^„çâX´~žEãßÕÍ¡”ÁpHBL¨ÓFá®R˜P}’™nfkŒñïƒf,Ép,EdQ8„"l b
ãüõb¼ ÆcQ,Á´q\hKìEfGŒaá`ÕÌ�¥hô‰)òì1V·ˆ`6ˆ�¸Ð¬��Dþn-«#l!àFº@'¶�i‹:&][G™@§¯+ŠÔp9_(ŒA_J(ÌÖ=Œÿ
t1þÝ dü[ŸÞ†:ÿ¶näß.öI q—Îç™=Ûц½±á~Õíje½Rµ[«óþüáxµµ°2Ò—Ðÿeµ®}¥½Ðûyk¡]ºöÇ·Ïζ†-ßÿÓ»së- UŠÅ‘ÓïU[»b‹Ã§Î�ж,«Ú[*Þ:}¦jÛ«WÞ<õºÕ$\›ýÕïªvµøÁ™ßÿxµ1XVinìô/-Ûšü裿[i.Ü/�¿uܲ+…Ï>þàÓõFáâƒù©·^®Úë…±B¹Úhsq~iú�¼!^[µì&¶K‹åÂI�À‘¯m{„奕ÂÒŠ“½ÕéáÑ;õ`-—WfN&œ~–{å³j£ð…ûN‚®÷¥ŽþÝòÀ.OŸüÁ.ßxYÿá—uáÒÂÒÔ‰#ãDh-ÍÞ[¶=~.L?|ƒ×Ëç׫u¶ós¥Ñ_