Brought to you by EarthWeb
IT Library Logo

This is the Default Image
This is the Default Image

nav

EarthWeb Direct

EarthWeb Direct

EarthWeb sites: other sites

Day 13

Creating User Interfaces with the awt

by Laura Lemay


CONTENTS

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:

  • A full set of user interface (UI) widgets and other components, including windows, menus, buttons, check boxes, text fields, scrollbars, and scrolling lists
  • Support for UI containers, which can contain other embedded containers or UI widgets
  • An event system for managing system and user events among parts of the awt
  • Mechanisms for laying out components in a way that enables platform-
    independent UI design

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.

An awt Overview

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.

Figure 13.1 : awt components.

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:

  • Containers. Containers are generic awt components that can contain other components, including other containers. The most common form of container is the panel, which represents a container that can be displayed onscreen. Applets are a form of panel (in fact, the Applet class is a subclass of the Panel class).
  • Canvases. A canvas is a simple drawing surface. Although you can draw on panels (as you've been doing all along), canvases are good for painting images or performing other graphics operations.
  • UI components. These can include buttons, lists, simple pop-up menus, check boxes, test fields, and other typical elements of a user interface.
  • Window construction components. These include windows, frames, menu bars, and dialog boxes. They are listed separately from the other UI components because you'll use these less often-particularly in applets. In applets, the browser provides the main window and menu bar, so you don't have to use these. Your applet may create a new window, however, or you may want to write your own Java application that uses these components. (You'll learn about these tomorrow.)

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 Basic User Interface Components

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!

Labels

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

  • You don't have to redraw labels yourself. Labels are an awt element, and the awt keeps track of drawing them.
  • Labels follow the layout of the panel in which they're contained and can be aligned with other UI components. Panel layout is determined by the layout manager, which you'll learn about later, in the section "Panels and Layout."

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:

  • Label() creates an empty label, with its text aligned left.
  • Label(String) creates a label with the given text string, also aligned left.
  • Label(String, int) creates a label with the given text string and the given alignment. The available alignment numbers are stored in class variables in Label, making them easier to remember: Label.RIGHT, Label.LEFT, and Label.CENTER.

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.

Table 13.1. Label methods.

MethodAction
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
1    is Label.CENTER
2    is Label.RIGHT
setAlignment(int) Changes the alignment of this label to the given integer-use the class variables listed in the getAlignment() method

Buttons

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:

  • Button() creates an empty button with no label.
  • Button(String) creates a button with the given string as a label.

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

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:

  • Nonexclusive: Given a series of check boxes, any of them can be selected.
  • Exclusive: Given a series, only one check box can be selected at a time.

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:

  • Checkbox() creates an empty check box, unselected.
  • Checkbox(String) creates a check box with the given string as a label.
  • Checkbox(String, null, boolean) creates a check box that is either selected or deselected based on whether the boolean argument is true or false, respectively. (The null is used as a placeholder for a group argument. Only radio buttons have groups, as you'll learn in the next section.)

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.

Table 13.2. Check box methods.

MethodAction
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

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.

Choice Menus

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):

Figure 13.7 : A choice menu.

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.

Table 13.3. Choice menu methods.

MethodAction
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

Text Fields

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:

  • TextField() creates an empty TextField that is 0 characters wide (it will be resized by the current layout manager).
  • TextField(int) creates an empty text field. The integer argument indicates the minimum number of characters to display.
  • TextField(String) creates a text field initialized with the given string. The field will be automatically resized by the current layout manager.
  • TextField(String, int) creates a text field some number of characters wide (the integer argument) containing the given string. If the string is longer than the width, you can select and drag portions of the text within the field, and the box will scroll left or right.

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.

Table 13.4. Text field methods.

MethodAction
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.

Panels and Layout

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.

Layout Managers: An Overview

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

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

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

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

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

Grid Bag Layouts

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:

  • Creating a GridBagLayout object and defining it as the current layout manager, as you would any other layout manager
  • Creating a new instance of GridBagConstraints
  • Setting up the constraints for a component
  • Telling the layout manager about the component and its constraints
  • Adding the component to the panel

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.

Step One: Design the Grid

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.

Step Two: Create the Grid in Java

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.

Step Three: Determine the Proportions

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.

Step Four: Add and Arrange the Components

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:

  • GridBagConstraints.BOTH, which stretches the component to fill the cell in both directions.
  • GridBagConstraints.NONE, which causes the component to be displayed in its smallest size.
  • GridBagConstraints.HORIZONTAL, which stretches the component in the horizontal direction.
  • GridBagConstraints.VERTICAL, which stretches the component in the vertical direction.

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:

  • The labels will have no fill and will be aligned east (so they hug the right side of the cell)
  • The text fields will be filled horizontally (so they start one line high, but stretch to the width of the cell)
  • The button will have no fill and will be center aligned

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 made there.

Step Five: Futz with It

I added this step to the list because in my own experimentation with grid bag layouts, I found that even by following all the steps, usually the resulting layout wasn't quite right, and I needed to do a considerable amount of tinkering and playing with various values of the constraints in order to get it to come out right (that's what futzing means) There's nothing wrong with that; the goal of the previous three steps was to get things fairly close to their final positions, not to come out with a perfect layout each and every time.

The Code

Listing 13.1 shows the complete code for the panel layout we've been building up in this section. If you had trouble following the discussion up to this point, you might find it useful to go through this code line by line to make sure you understand the various bits.


Listing 13.1. The panel with the final grid bag layout.
 1:import java.awt.*;
 2:
 3:public class GridBagTestFinal extends java.applet.Applet {
 4:
 5:  void buildConstraints(GridBagConstraints gbc, int gx, int gy, 
 6:      int gw, int gh,
 7:      int wx, int wy) {
 8:      gbc.gridx = gx;
 9:      gbc.gridy = gy;
10:      gbc.gridwidth = gw;
11:      gbc.gridheight = gh;
12:      gbc.weightx = wx;
13:      gbc.weighty = wy;
14:  }
15:
16:  public void init() {
17:      GridBagLayout gridbag = new GridBagLayout();
18:      GridBagConstraints constraints = new GridBagConstraints();
19:      setLayout(gridbag);
20:      
21:      // Name label
22:      buildConstraints(constraints, 0, 0, 1, 1, 10, 40);
23:      constraints.fill = GridBagConstraints.NONE;
24:      constraints.anchor = GridBagConstraints.EAST;
25:      Label label1 = new Label("Name:", Label.LEFT);
26:      gridbag.setConstraints(label1, constraints);
27:      add(label1);
28:
29:      // Name text field
30:      buildConstraints(constraints, 1, 0, 1, 1, 90, 0);
31:      constraints.fill = GridBagConstraints.HORIZONTAL;
32:      TextField tfname = new TextField();
33:      gridbag.setConstraints(tfname, constraints);
34:      add(tfname);
35:
36:      // password label
37:      buildConstraints(constraints, 0, 1, 1, 1, 0, 40);
38:      constraints.fill = GridBagConstraints.NONE;
39:      constraints.anchor = GridBagConstraints.EAST;
40:      Label label2 = new Label("Password:", Label.LEFT);
41:      gridbag.setConstraints(label2, constraints);
42:      add(label2);
43:
44:      // password text field
45:      buildConstraints(constraints, 1, 1, 1, 1, 0, 0);
46:      constraints.fill = GridBagConstraints.HORIZONTAL;
47:      TextField tfpass = new TextField();
48:      tfpass.setEchoCharacter('*');
49:      gridbag.setConstraints(tfpass, constraints);
50:      add(tfpass);
51:
52:      // OK Button
53:      buildConstraints(constraints, 0, 2, 2, 1, 0, 20);
54:      constraints.fill = GridBagConstraints.NONE;
55:      constraints.anchor = GridBagConstraints.CENTER;
56:      Button okb = new Button("OK");
57:      gridbag.setConstraints(okb, constraints);
58:      add(okb);
59:  }
60:}

ipadx and ipady

Before finishing up with grid bag layouts (isn't it over yet?), there are a two more constraints that deserve mentioning: ipadx and ipady. These two constraints control the padding-that is, the extra space around an individual component. By default, no components have extra space around them (which is easiest to see in components that fill their cells).

ipadx adds space to either side of the component, and ipady adds it above and below.

Insets

Horizontal and vertical gap, created when you create a new layout manager (using ipadx and ipady in grid bag layouts), are used to determine the amount of space between components in a panel. Insets, however, are used to determine the amount of space around the panel itself. The Insets class includes values for the top, bottom, left, and right insets, which are then used when the panel itself is drawn.

Insets determine the amount of space between the edges of a panel and that panel's components.

To include an inset, override the insets() method in your class (your Applet class or other class that serves as a panel). Inside the insets() method, create a new Insets object, where the constructor to the Insets class takes four integer values representing the insets on the top, left, bottom, and right of the panel. The insets() method should then return that Insets object. Here's some code to add insets for a grid layout, 10 to the top and bottom, and 30 to the left and right. (Figure 13.20 shows the inset):

Figure 13.20: A panel with insets of 10 pixels on the top and bottom and 30 pixels to the left and right.

public Insets insets() {
   return new Insets(10, 30, 10, 30);
}

The arguments to the Insets constructor provide pixel insets for the top, bottom, left, and right edges of the panel, respectively. This particular example provides an inset of 10 pixels on all four sides of the panel.

Handling UI Actions and Events

If you stopped reading today's lesson right now, you could go out and create an applet that had lots of little UI components, nicely laid out on the screen with the proper layout manager, gap, and insets. If you did stop right here, however, your applet would be really dull, because none of your UI components would actually do anything when they were pressed, typed into, or selected.

For your UI components to do something when they are activated, you need to hook up the UI's action with an operation. Actions are a form of event, and testing for an action by a UI component involves event management. Everything you learned yesterday about events will come in handy here.

UI actions are events that occur when a UI component is activated-pressed, selected, typed into, and so on.

To intercept an action event generated by any UI component, you define an action() method in your applet or class:

public boolean action(Event evt, Object arg) {
    ...
}

The action() method should look similar to the basic mouse and keyboard event methods. Like those methods, it gets passed the event object that represents this event. It also gets an extra object (in this code, the parameter arg), which can be of any class type.

What kind of object that second argument to the action method is depends on the UI component that's generating the action. The basic definition is that it's any arbitrary argument-when a component generates an event, it can pass along any extra information that might be useful for you to use in processing that action.

All the basic UI components (except for labels, which have no action) have different actions and arguments:

  • Buttons create actions when they are pressed and released with the mouse, and a button's extra argument is the label string of that button.
  • Check boxes, both exclusive and nonexclusive, generate actions when a box is checked. The extra argument is always true.
  • Choice menus generate an action when a menu item is selected, and the extra argument is the label string of that item.
  • Text fields create actions when the user presses Return or Enter inside that text field. Note that if the user tabs to a different text field or uses the mouse to change the input focus, an action is not generated. Pressing Return or Enter is the only thing that triggers the action.

Note that with actions, unlike with ordinary events, you can have many different kinds of objects generating the action event, as opposed to a single movement (a mouse press) generating a single event (such as a mouseDown). To deal with those different UI components and the actions they generate, you have to test for the type of object that sent/created the event in the first place inside the body of your action() method. That object is stored in the event's target instance variable, and you can use the instanceof operator to find out what kind of UI component sent it:

public boolean action(Event evt, Object arg) {
    if (evt.target instanceof TextField)
        return handleText(evt.target);
    else if (evt.target instanceof Choice)
        return handleChoice(arg);
...
}

Although you can handle UI actions in the body of the action() method, it's much more common simply to define a special method in your action() method and call that method instead. Here, there are two special methods: one to handle the action on the text field (handleText()) and one to handle the action on the choice menu (handleChoice()). Depending on the action you want to handle, you may also want to pass on the argument from the action, the UI component that sent it, or any other information that the event might contain.

As with the other event methods, action() returns a boolean value. As with all the event methods, you should return true if action() itself deals with the method, or false if it passes the method on somewhere else (or ignores it).

Listing 13.2 shows a simple applet that has five buttons labeled with colors. The action() method tests for a button action and then passes control to a method called changeColor(), which changes the background color of the applet based on which button was pressed (see Figure 13.21 to see the applet in action).

Figure 13.21: The ButtonAction applet.


Listing 13.2. The ButtonActionsTest applet.
 1:import java.awt.*;
 2:
 3:public class ButtonActionsTest extends java.applet.Applet {
 4:
 5:  public void init() {
 6:    setBackground(Color.white);
 7:
 8:    add(new Button("Red"));
 9:    add(new Button("Blue"));
10:    add(new Button("Green"));
11:    add(new Button("White"));
12:    add(new Button("Black"));
13:  }
14:
15:  public boolean action(Event evt, Object arg) {
16:    if (evt.target instanceof Button) {
17:      changeColor((String)arg);
18:      return true;
19:    } else return false;
20:  }
21:
22:  void changeColor(String bname) {
23:    if (bname.equals("Red")) setBackground(Color.red);
24:    else if (bname.equals("Blue")) setBackground(Color.blue);
25:    else if (bname.equals("Green")) setBackground(Color.green);
26:    else if (bname.equals("White")) setBackground(Color.white);
27:    else setBackground(Color.black);
28:
29:    repaint();
30:  }
31:}

As with most awt-based applets, this one starts with an init() method that initializes the applet's state and creates and adds components to the layout. The init() method defined in lines 8 through 13 here sets the applet's background color to white and creates five new buttons with color labels. Here we'll use the default layout manager, which is a FlowLayout. The buttons will appear all in a row at the top of the screen.

With the buttons in place, the second step is to attach actions to those buttons. The action() method, defined in lines 15 through 20, does this. The first thing to check is to make sure it's a button action that's been generated (line 16) and, if so, to pass the extra argument (cast to a string) to the changeColor() method, which will do all the work to change the color. If the event is indeed a button action, we'll return true to intercept that event. Otherwise, we'll return false and let some other component handle the event.

The changeColor() method is where all the work goes on. Here we test for each of the button labels in turn to see which button it was that was pressed and to set the background to the appropriate color. A final repaint at the end does the actual change (setting the background color does not automatically trigger a repaint; you'll have to do it yourself).

Nesting Panels and Components

Adding UI components to individual panels or applets is fun, but working with the awt begins to turn into lots of fun when you start working with nested panels. By nesting different panels inside your applet, and panels inside those panels, you can create different layouts for different parts of the overall applet area, isolate background and foreground colors and fonts to individual parts of an applet, and manage the design of your UI components individually and in distinct groups. The more complex the layout of your applet, the more likely you're going to want to use nested panels.

Nested Panels

Panels, as you've already learned, are components that can be actually displayed onscreen; Panel's superclass Container provides the generic behavior for holding other components inside it. The Applet class, from which your applets all inherit, is a subclass of Panel. To nest other panels inside an applet, you merely create a new panel and add it to the applet, just as you would add any other UI component:

setLayout(new GridLayout(1, 2, 10, 10));
Panel panel1 = new Panel();
Panel panel2 = new Panel();
add(panel1);
add(panel2);

You can then set up an independent layout for those subpanels and add awt components to them (including still more subpanels) by calling the add() method in the appropriate panel:

panel1.setLayout(new FlowLayout());
panel1.add(new Button("Up"));
panel1.add(new Button("Down"));

Although you can do all this in a single class, it's common in graphical applets and applications that make heavy use of subpanels to factor out the layout and behavior of the subpanels into separate classes and to communicate between the panels by using methods. You'll look at an extensive example of this later in today's lesson in the section "A Complete Example: RGB-to-HSB Converter."

Events and Nested Panels

When you create applets with nested panels, those panels form a hierarchy from the outermost panel (the applet, usually) to the innermost UI component. This hierarchy is important to how each component in the interface interacts with other components; for example, the component hierarchy determines the order in which those components are painted to the screen.

More importantly, however, the hierarchy also affects event handling, particularly for user-input events such as mouse and keyboard events.

Events are received by the innermost component in the component hierarchy and passed up the chain to the applet's panel (or to the root window in Java applications). Suppose, for example, that you have an applet with a subpanel that can handle mouse events (using the mouseDown() and mouseUp() methods), and that panel contains a button. Clicking the button means that the button receives the event before the panel does; if the button isn't interested in that mouseDown(), the event gets passed to the panel, which can then process it or pass it further up the hierarchy.

Remember the discussion about the basic event methods yesterday? You learned that the basic event methods all return boolean values. Those boolean values become important when you're talking about handling events or passing them on.

An event-handling method, whether it is the set of basic event methods or the more generic handleEvent(), can do one of three things, given any random event:

  • Ignore the event entirely, if the event doesn't match whatever criteria the event-handling method set-for example, the mouseDown wasn't in the right area, or the action wasn't a button action. If this is the case, the event handler should return false so the event is passed up the hierarchy until a component processes it (or it is ignored altogether).
  • Intercept the event, process it, and return true. In this case, the event stops with that event method.
  • Intercept the method, process it, and pass it on to another, more specific event handler-for example, as handleEvent passes events onto mouseDown().

More UI Components

After you master the basic UI components and how to add them to panels, organize their layout, and manage their events, you can add more UI components. In this section, you'll learn about text areas, scrolling lists, scrollbars, and canvases.

Note that most of the components in this section do not produce actions, so you can't use the action() method to handle their behavior. Instead, you have to use a generic handleEvent() method to test for specific events that these UI components generate. You'll learn more about this in the next section.

Text Areas

Text areas are like text fields, except they have more functionality for handling large amounts of text. Because text fields are limited in size and don't scroll, they are better for one-line responses and simple data entry; text areas can be any given width and height and have scrollbars by default, so you can deal with larger amounts of text more easily.

Text areas are larger, scrollable text-entry components. Whereas text fields only provide one line of text, text areas can hold any amount of editable text.

To create a text area, use one of the following constructors:

  • TextArea() creates an empty text area 0 rows long and 0 characters wide (the text area will be automatically resized based on the layout manager).
  • TextArea(int, int) creates an empty text area with the given number of rows and columns (characters).
  • TextArea(String) creates a text area displaying the given string, which will be sized according to the current layout manager.
  • TextArea(String, int, int) creates a text area displaying the given string and with the given dimensions.

Figure 13.22 shows a simple text area generated from the following code:

Figure 13.22: A text area.

import java.awt.*;

public class TextAreaTest extends java.applet.Applet {

  public void init() {
    String str = "Once upon a midnight dreary, while I pondered, weak and weary,\n" +
     "Over many a quaint and curious volume of forgotten lore,\n" +
     "While I nodded, nearly napping, suddenly there came a tapping,\n" +
     "As of some one gently rapping, rapping at my chamber door.\n" +
     "\"'Tis some visitor,\" I muttered, \"tapping at my chamber door-\n" +
     "Only this, and nothing more.\"\n\n";
     // more text deleted for space

     add(new TextArea(str,10,50));
  } 
}

Both text areas and text fields inherit from the TextComponent class, so a lot of the behavior for text fields (particularly getting and setting text and selections) is usable on text areas as well (refer to Table 13.4). Text areas also have a number of their own methods that you may find useful. Table 13.5 shows a sampling of those methods.

Table 13.5. Text area methods.

MethodAction
getColumns() Returns the width of the text area, in characters or columns
getRows() Returns the number of rows in the text area (not the number of rows of text that the text area contains)
insertText(String, int) Inserts the string at the given position in the text (text positions start at 0)
replaceText(String, int, int) Replaces the text between the given integer positions with the new string

Scrolling Lists

Remember the choice menu, with which you could choose one of several different options? A scrolling list is functionally similar to a choice menu in that it lets you pick several options from a list, but scrolling lists differ in two significant ways:

  • Scrolling lists are not pop-up menus. They're displayed as a list of items from which you can choose one or more items. If the number of items is larger than the list box, a scrollbar is automatically provided so that you can see the other items.
  • You can choose more than one item in the list (if the list has been defined to allow it).

Scrolling lists provide a menu of items that can be selected or deselected. Unlike choice menus, scrolling lists are not pop-up menus and can be defined to allow multiple selections.

To create a scrolling list, create an instance of the List class and then add individual items to that list. The List class has two constructors:

  • List() creates an empty scrolling list that enables only one selection at a time.
  • List(int, boolean) creates a scrolling list with the given number of visible lines on the screen (you're unlimited as to the number of actual items you can add to the list). The boolean argument indicates whether this list enables multiple selections (true) or not (false).

After creating a List object, add items to it using the addItem() method and then add the list itself to the panel that contains it. Here's an example that creates a list five items high that allows multiple selections (the result of this code is shown in Figure 13.23):

Figure 13.23: A scrolling list.

import java.awt.*;

public class ListsTest extends java.applet.Applet {

  public void init() {
    List lst = new List(5, true);

    lst.addItem("Hamlet");
    lst.addItem("Claudius");
    lst.addItem("Gertrude");
    lst.addItem("Polonius");
    lst.addItem("Horatio");
    lst.addItem("Laertes");
    lst.addItem("Ophelia");
    
    add(lst);
  }
}

Scrolling lists generate actions when the user double-clicks a list item (single-clicking generates a LIST_SELECT or LIST_DESELECT event ID; you'll learn more about these in the section "More UI Events"). A scrolling list action has the argument of the string of the item that was double-clicked.

Table 13.6 shows some of the methods available to scrolling lists. See the API documentation for a complete set.

Table 13.6. Scrolling list methods.

MethodAction
getItem(int) Returns the string item at the given position
countItems() Returns the number of items in the menu
getSelectedIndex() Returns the index position of the item that's selected (used for lists that allow only single selections)
getSelectedIndexes() Returns an array of index positions (used for lists that allow multiple selections)
getSelectedItem() Returns the currently selected item as a string
getSelectedItems() Returns an array of strings containing all the selected items
select(int) Selects the item at the given position
select(String) Selects the item with that string

Scrollbars and Sliders

Text areas and scrolling lists come with their own scrollbars, which are built into those UI components and enable you to manage both the body of the area or the list and its scrollbar as a single unit. You can also create individual scrollbars, or sliders, to manipulate a range of values.

Scrollbars are used to select a value between a maximum and a minimum value. To change the current value of that scrollbar, you can use three different parts of the scrollbar (seeFigure 13.24):

Figure 13.24: Scrollbar parts.

  • Arrows on either end, which increment or decrement the values by some small unit (1 by default).
  • A range in the middle, which increments or decrements the value by a larger amount (10 by default).
  • A box in the middle, often called an elevator or thumb, whose position shows where in the range of values the current value is located. Moving this box with the mouse causes an absolute change in the value, based on the position of the box within the scrollbar.

Choosing any of these visual elements causes a change in the scrollbar's value; you don't have to update anything or handle any events. All you have to do is give the scrollbar a maximum and minimum, and Java will handle the rest.

A scrollbar is a visual UI element that allows you to choose a value between some minimum and some maximum. Scrollbars are sometimes called sliders.

To create a scrollbar, you can use one of three constructors:

  • Scrollbar() creates a scrollbar with its initial maximum and minimum values both 0, in a vertical orientation.
  • Scrollbar(int) creates a scrollbar with its initial maximum and minimum values both 0. The argument represents an orientation, for which you can use the class variables Scrollbar.HORIZONTAL and Scrollbar.VERTICAL.
  • Scrollbar(int, int, int, int, int) creates a scrollbar with the following arguments (each one is an integer, and they must be presented in this order):
  • The first argument is the orientation of the scrollbar: Scrollbar.HORIZONTAL and Scrollbar.VERTICAL.
  • The second argument is the initial value of the scrollbar, which should be a value between the scrollbar's maximum and minimum values.
  • The third argument is the overall width (or height, depending on the orientation) of the scrollbar's box. In user-interface design, a larger box implies that a larger amount of the total range is currently showing (applies best to things such as windows and text areas).
  • The fourth and fifth arguments are the minimum and maximum values for the scrollbar.

Here's a simple example of a scrollbar that increments a single value (see Figure 13.25). The label to the left of the scrollbar is updated each time the scrollbar's value changes:

Figure 13.25: A scrollbar.

import java.awt.*;

public class SliderTest extends java.applet.Applet {
  Label l;

  public void init() {
    setLayout(new GridLayout(1,2));
    l = new Label("0", Label.CENTER);
    add(l);
    add(new Scrollbar(Scrollbar.HORIZONTAL,0,0,1,100));
  }

  public Insets insets() {
    return new Insets(15,15,15,15);
  }

  public boolean handleEvent(Event evt) {
    if (evt.target instanceof Scrollbar) {
      int v = ((Scrollbar)evt.target).getValue();
      l.setText(String.valueOf(v));
      repaint();
      return true;
    } else return false;
  }

}

The Scrollbar class provides several methods for managing the values within scrollbars. (See Table 13.7.)

Table 13.7. Scrollbar methods.

MethodAction
getMaximum() Returns the maximum value.
getMinimum() Returns the minimum value.
getOrientation() Returns the orientation of this scrollbar:

0 is Scrollbar.HORIZONTAL; 1 is Scrollbar.VERTICAL.

getValue() Returns the scrollbar's current value.
setValue(int) Sets the current value of the scrollbar.
setLineIncrement(int inc) Change the increment for how far to scroll when the endpoints of the scrollbar are selected. The default is 1.
getLineIncrement() Returns the increment for how far to scroll when the endpoints of the scrollbar are selected.
setPageIncrement(int inc) Change the increment for how far to scroll when the inside range of the scrollbar is selected. The default is 10.
getPageIncrement() Returns the increment for how far to scroll when the inside range of the scrollbar is selected.

Canvases

Although you can draw on most awt components such as panels using the graphics methods you learned about on Day 11, "More Animation, Images, and Sound," canvases do little except let you draw on them. They can't contain other components, but they can accept events, and you can create animation and display images on them. If you have a panel that doesn't need to do anything except display images or animation, a canvas would make a lighter-weight surface than a panel would.

A canvas is a component that you can draw on.

To create a canvas, use the Canvas class and add it to a panel as you would any other component:

Canvas can = new Canvas();
add(can);

More UI Events

Yesterday, you learned about some basic event types that are generated from user input to the mouse or the keyboard. These event types are stored in the Event object as the event ID, and can be tested for in the body of a handleEvent() method by using class variables defined in Event. For many basic events, such as mouseDown() and keyDown(), you can define methods for those events to handle the event directly. You learned a similar mechanism today for UI actions where creating an action() method handled a specific action generated by a UI component.

The most general way of managing events, however, continues to be the handleEvent() method. For events relating to scrollbars and scrolling lists, the only way to intercept these events is to override handleEvent().

To intercept a specific event, test for that event's ID. The available IDs are defined as class variables in the Event class, so you can test them by name. You learned about some of the basic events yesterday; Table 13.8 shows additional events that may be useful to you for the components you've learned about today (or that you might find useful in general).

Table 13.8. Additional events.

Event IDWhat It Represents
ACTION_EVENT Generated when a UI component action occurs
GOT_FOCUS Generated when the user clicks inside a text area
LOST_FOCUS Generated when the user clicks anywhere outside a text area (after being inside one)
LIST_DESELECT Generated when an item in a scrolling list is deselected
LIST_SELECT Generated when an item in a scrolling list is selected
SCROLL_ABSOLUTE Generated when a scrollbar's box has been moved
SCROLL_LINE_DOWN Generated when a scrollbar's bottom or left endpoint (button) is selected
SCROLL_LINE_UP Generated when a scrollbar's top or right endpoint (button) is selected
SCROLL_PAGE_DOWN Generated when the scrollbar's field below (or to the left of) the box is selected
SCROLL_PAGE_UP Generated when the scrollbar's field above (or to the right of) the box is selected

Fun with Components

The Component class is the root of all the awt objects: all the UI elements, panels, canvases, even applets. Just about everything you can display, lay out, change the color of, draw to, or interact with using events in the awt is a component.

Components have a set of methods that allow you to modify their appearance or change their behavior. You've seen the use of a few of these methods already (setBackground(), setFont, size()), applied specifically to applets. But the methods defined in Component can be used with any component, allowing you to modify the appearance or the behavior of just about any element in your program. You can also create custom components (classes that inherit from Panel or Canvas) to make your own special awt elements or user interface widgets.

Table 13.9 summarizes some of the methods you can use with individual components. For more methods, check out the Java API documentation for the class Component. The JDK 1.0.2 documentation is online at

http://java.sun.com:80/products/JDK/CurrentRelease/api/

Table 13.9. Component methods.

getBackground() Returns a Color object representing the component's background color.
setBackground(Color) Sets the component's background color.
getForeground() Returns a Color object representing the component's current foreground color.
setForeground(Color) Sets the component's foreground color
getFont() Returns a Font object representing the component's current font.
setFont(Font) Changes the component's current font.
size() Returns a Dimension object representing the component's current size. You can then get to the individual width and height using size().width() and size().height().
minimumSize() The component's smallest possible size as a Dimension object. minimumSize() is usually only used by layout managers to determine how small it can draw a component; if you create a custom component you'll want to override this method to return the minimum size of that component.
preferredSize() The component's preferred size (usually equal to or larger than the component's minimumSize()) as a Dimension object.
resize(Dimension) Changes the size of the applet to be the current size. For custom components you'll want to also call validate() after resizing the applet so that the layout can be redrawn.
inside(x, y) Returns true if the given x and y coordinates are inside the component.
hide() Hides the component. Hidden components do not show up onscreen.
show() Shows a component previously hidden.
isVisible() Returns true or false depending on whether this component is visible (not hidden).
disable() Disables the component-that is, stops generating events. Disabled components cannot be pressed, selected from, typed into, and so on.
enable() Enables a previously disabled object.
isEnabled() Returns true or false depending on whether the component is enabled.

A Complete Example: RGB-to-HSB Converter

Let's take a break here from theory and smaller examples to create a larger example that puts together much of what you've learned so far. The following applet example demonstrates layouts, nesting panels, creating user-interface components, and catching and handling actions, as well as using multiple classes to put together a single applet. In short, it's the most complex applet you've created so far.

Figure 13.26 shows the applet you'll be creating in this example. The ColorTest applet enables you to pick colors based on RGB (red, green, and blue) and HSB (hue, saturation, and brightness) values.

Figure 13.26: The ColorTest applet.

Note
A very quick summary in case you're not familiar with basic color theory: RGB color defines a color by its red, green, and blue values; some combination of these values can produce any color in the spectrum (red, green, and blue are called additive colors; that's how your monitor and your TV represent different colors).
HSB stands for hue, saturation, and brightness and is a different way of indicating color. Hue is the actual color in the spectrum you're representing (think of it as values along a color wheel). Saturation is the amount of that color; low saturation results in pastels; high-saturation colors are more vibrant and "colorful." Brightness, finally, is the lightness or darkness of the color. No brightness is black; full brightness is white.
A single color can be represented either by its RGB values or by its HSB values, and there are mathematical algorithms to convert between them. The ColorTest applet provides a graphical converter between the two.

The ColorTest applet has three main parts: a colored box on the left side and two groups of text fields on the right. The first group indicates RGB values; the right, HSB. By changing any of the values in any of the text boxes, the colored box is updated to the new color, as are the values in the other group of text boxes.

Note
If you try this applet, be aware that you have to press Enter or Return after changing a number for the updating to occur. Using the Tab key to move between text fields or clicking with the mouse will not cause the applet to update.

This applet uses two classes:

  • ColorTest, which inherits from Applet. This is the controlling class for the applet itself.
  • ColorControls, which inherits from Panel. You'll create this class to represent a group of three text fields and to handle actions from those text fields. Two instances of this class, one for the RGB values and one for the HSB ones, will be created and added to the applet.

Let's work through this step by step, because it's very complicated and can get confusing. All the code for this applet will be shown at the end of this section.

Designing and Creating the Applet Layout

The best way to start creating an applet that uses awt components is to worry about the layout first and then worry about the functionality. When dealing with the layout, you should start with the outermost panel first and work inward.

Making a sketch of your UI design can help you figure out how to organize the panels inside your applet or window to best take advantage of layout and space. Paper designs are helpful even when you're not using grid bag layouts, but doubly so when you are (we'll be using a simple grid layout for this applet).

Figure 13.27 shows the ColorTest applet with a grid drawn over it so that you can get an idea of how the panels and embedded panels work.

Figure 13.27: The ColorTest applet panels and components.

Let's start with the outermost panel-the applet itself. This panel has three parts: the color box on the left, the RGB text fields in the middle, and the HSB fields on the right.

Because the outermost panel is the applet itself, your ColorTest class will be the applet class and will inherit from Applet. You'll also import the awt classes here (note that because you use so many of them in this program, it's easiest to just import the entire package):

import java.awt.*;

public class ColorTest extends java.applet.Applet {
    ...
}

This applet has three main things to keep track of: the color box and the two subpanels. The two subpanels each refer to different things, but they're essentially the same panel and behave in the same ways. Rather than duplicate a lot of code here in this class, this is a perfect opportunity to create another class just for the subpanels, use instances of that class here in the applet, and communicate between everything using methods. In a bit we'll define that new class, called ColorControls.

For now, however, we know we need to keep a handle to all three parts of the applet so you can update them when they change. So let's create three instance variables: one of type Canvas for the color box, and the other two of type ColorControls for the control panels:

ColorControls RGBcontrols, HSBcontrols;
Canvas swatch;

Now we'll move onto the init() method, where all the basic initialization and layout of the applet takes place. There are three steps to initializing this applet:

  1. Create the layout for the big parts of the panel. Although a flow layout would work, a grid layout with one row and three columns is a much better idea.
  2. Create and initialize the three components of this applet: a canvas for the color box and two subpanels for the text fields.
  3. Add those components to the applet.

Step one is the layout. Let's use a grid layout and a gap of 10 points to separate each of the components:

setLayout(new GridLayout(1, 3, 5, 15));

Step two is creating the components-the canvas first. You have an instance variable to hold that one. Here we'll create the canvas and initialize its background to black:

swatch = new Canvas();
swatch.setBackground(Color.black);

You need to create two instances of your as-of-yet nonexistent ColorControls panels here as well, but because we haven't created the class yet we don't know what the constructors to that class will look like. Let's put in some placeholder constructors here; we'll fill in the details later:

RGBcontrols = new ColorControls(...)
HSBcontrols = new ColorControls(...);

Step three is adding all three components to the applet panel:

add(swatch);
add(RGBcontrols);
add(HSBcontrols);

While you're working on layout, let's add insets for the applet: 10 points along all the edges:

public Insets insets() {
    return new Insets(10, 10, 10, 10);
}

Got it so far? At this point you have three instance variables, an init() method with two incomplete constructors, and an insets() method in your ColorTest class. Let's move on now to creating the subpanel layout in the ColorControls class so we can fill in those constructors and finish up the layout.

Defining the Subpanels

The ColorControls class will have behavior for laying out and handling the subpanels that represent the RGB and HSB values for the color. ColorControls doesn't need to be a subclass of Applet because it isn't actually an applet; it's just a panel. Define it to inherit from Panel:

import java.awt.*

class ColorControls extends Panel {
    ...
}

Note
I've put the ColorControls source code into its own file, called ColorControls.java. However, you can put the ColorControls class in the same file as the ColorTest class. Up to this point, you've only defined one class per file, with the filename the same name as the class. In Java you can have multiple class definitions in a file as long as only one of those classes is declared public (and the name of the source file is the same as that public class). In this case, the ColorTest class is public (it's an applet, so it has to be), but the ColorControls class isn't public, so it can be in the same source file. When you compile the file, Java will create the appropriate multiple class files for each class definition. You'll learn more about public classes on Day 15, "Modifiers, Access Control, and Class Design," and Day 16, "Packages and Interfaces."
In general, however, I prefer to use separate source files for my classes. It makes it easier for me to find the source for a particular class because I don't have to remember which file I defined it in.

The ColorControls class will need a number of instance variables so that information from the panel can get back to the applet. The first of these instance variables is a hook back up to the applet class that contains this panel. Because it's the outer applet class that controls the updating of each panel, this panel will need a way to tell the applet that something has changed. And to call a method in that applet, you need a reference to that object. So, instance variable number one is a reference an instance of the class ColorTest:

ColorTest applet;

If you figure that the applet class is the one that's going to be updating everything, that class if going to be interested in the individual text fields in this subpanel. We'll create instance variables for those text fields:

TextField tfield1, tfield2, tfield3;

Now let's move on to the constructor for this class. Because this class isn't an applet, we won't use init() to initialize it; instead we'll use a constructor method.

Inside the constructor you'll do much of what you did inside init(): create the layout for the subpanel, create the text fields, and add them to the panel.

The goal here is to make the ColorControls class generic enough so that you can use it for both the RGB fields and the HSB fields. Those two panels differ in only one respect: the labels for the text. That's three values to get before you can create the object. You can pass those three values in through the constructors in ColorTest. You also need one more: that reference to the enclosing applet, which you can get from the constructor as well.

You now have four arguments to the basic constructor for the ColorControls class. Here's the signature for that constructor:

ColorControls(ColorTest parent,
        String l1, String l2, String l3) {
}

Let's start this constructor by first setting the value of parent to the applet instance variable:

applet = parent;

Next, create the layout for this panel. You can also use a grid layout for these subpanels, as you did for the applet panel, but this time the grid will have three rows (one for each of the text field and label pairs) and two columns (one for the labels and one for the fields). We'll also define a 10-point gap between the components in the grid:

setLayout(new GridLayout(3,2,10,10));

Now we can create and add the components to the panel. First, we'll create the text field objects (initialized to the string "0"), and assign them to the appropriate instance variables:

tfield1 = new TextField("0");
tfield2 = new TextField("0");  
tfield3 = new TextField("0");

Now we'll add those fields and the appropriate labels to the panel, using the remaining three parameters to the constructor as the text for the labels:

add(new Label(l1, Label.RIGHT));
add(tfield1);
add(new Label(l2, Label.RIGHT));
add(tfield2);
add(new Label(l3, Label.RIGHT));
add(tfield3);

That finishes up the constructor for the subpanel class ColorControls. Are we done with the layout? Not quite. We'll also add an inset around the subpanel-only on the top and bottom edges-to tinker the layout. Add the inset here as you did in the ColorTest class, using the insets() method:

public Insets insets() {
        return new Insets(10, 10, 0, 0);
 }

You're almost there. You have 98 percent of the basic structure in place and ready to go, but there's one step left: going back to ColorTest and fixing those placeholder constructors for the subpanel so they match the actual constructors for ColorControls.

The constructor for ColorControls that we just created now has four arguments: the ColorTest object and three labels (strings). Remember back to when we created the init() method for ColorTest: We added two placeholders for creating new ColorControls objects; we'll replace those placeholders with the correct versions now. Make sure you add the four arguments that constructor needs to work: the ColorTest object and three strings. To pass the ColorTest object to those constructors, we can use the this keyword:

RGBcontrols = new ColorControls(this, "Red", "Green", "Blue");
HSBcontrols = new ColorControls(this, "Hue", "Saturation", "Brightness");

Note
For the initial values of all the text fields in this example, I used the number 0 (actually, the string "0"). For the color black, both the RGB and the HSB values are 0, which is why I can make this assumption. If you wanted to initialize the applet to be some other color, you might want to rewrite the ColorControls class to use initializer values as well as to initialize labels. This way made for a shorter example.

Handling the Actions

With the layout done, its time to set up event handling and updating between the various components so that when the user interacts with the applet, the applet can respond.

The action of this applet occurs when the user changes a value in any of the text fields and presses Enter. By causing an action in a text field, the color changes, the color box updates to the new color, and the values of the fields in the opposite subpanel change to reflect the new color.

The ColorTest class is responsible for actually doing the updating because it keeps track of all the subpanels. Because the actual event occurs in the subpanel, however, you'll need to track and intercept those events in that subpanel using the action() method in the ColorControls class:

public boolean action(Event evt, Object arg) {
    if (evt.target instanceof TextField) {
      applet.update(this);
      return true;
    }
    else return false;
}

In the action() method, you test to make sure the action was indeed generated by a text field (because there are only text fields available, that's the only action you'll get, but it's a good idea to test for it anyhow). If there was indeed a text field action, we'll call a method to update all the subpanels. That method, which we'll call update(), is defined in the enclosing class, so we'll call it using the object stored in the applet instance variable (and pass along a reference to the panel so that the applet can get at our values). And, finally, we'll return either true or false so that other actions that might occur on this applet can be passed along to enclosing panels or components.

Updating the Result

Now comes the hard part: actually doing the updating based on the new values of whatever text field was changed. For this, you define the update() method in the ColorTest class. This update() method takes a single argument-the ColorControls instance that contains the changed value (you get that argument from the action() method in the ColorControls object).

Note
Won't this update() method interfere with the system's update() method? Nope. Remember, methods can have the same name, but different signatures and definitions. Because this update() has a single argument of type ColorControls, it doesn't interfere with the other version of update(). Normally, all methods called update() should mean basically the same thing; it's not true here, but it's only an example.

The update() method is responsible for updating all the panels in the applet. To know which panel to update, you need to know which panel changed. You can find out by testing to see whether the argument you got passed from the panel is the same as the subpanels you have stored in the RGBcontrols and HSBcontrols instance variables:

void update(ColorControls controlPanel) {

    if (controlPanel == RGBcontrols) {  // RGB has changed, update HSB
       ...
    } else {  // HSB has changed, update RGB
       ...
    }
}

This test is the heart of the update() method. Let's start with that first case-a number has been changed in the RGB text fields. So now, based on those new RGB values, you have to generate a new Color object and update the values on the HSB panel. To reduce some typing, you create a few local variables to hold some basic values. In particular, the values of the text fields are strings whose values you can get to using the getText() method defined in the TextField objects of the ColorControls object. Because most of the time in this method we'll want to deal with those values as integers, we'll get those string values, convert them to integers, and store them in local variables (value1, value2, value3). Here's the code to do this (it looks more complicated than it actually is):

int value1 = Integer.parseInt(controlPanel.tfield1.getText());
int value2 = Integer.parseInt(controlPanel.tfield2.getText());
int value3 = Integer.parseInt(controlPanel.tfield3.getText());

While we're here defining local variables, we'll also need one for the new Color object:

Color c;

OK. Let's assume one of the text fields in the RGB side of the applet has changed and add the code to the if part of the update() method. We'll need to create a new Color object and update the HSB side of the panel. That first part is easy; given the three RGB values, you can create a new Color object using those as arguments to the constructor:

c = new Color(value1, value2, value3);

Note
This part of the example isn't very robust; it assumes that the user has indeed entered integers from 0 to 255 into the text fields. A better version of this would test to make sure that no data-entry errors had occurred (I was trying to keep this example small).

Now we'll convert the RGB values to HSB. There are standard algorithms to convert an RGB-based color to an HSB color, but we don't have to go look them up. The Color class has a class method we can use called RGBtoHSB() that will do the work for us-or, at least, most of it. There are two problems with the RGBtoHSB() method, however:

  • The RGBtoHSB() method returns an array of the three HSB values, so we'll have to extract those values from the array.
  • The HSB values are measured in floating-point values from 0.0 to 1.0. I prefer to think of HSB values as integers, where the hue is a degree value around a color wheel (0 through 360), and saturation and brightness are percentages from 0 to 100.

Neither of these problems is insurmountable; it just means some extra lines of code. Let's start by calling RGBtoHSB() with the new RGB values we have. The return type of that method is an array of floats, so we'll create a local variable (HSB) to store the results of the RBGtoHSB() method. (Note that you'll also need to create and pass in an empty array of floats as the fourth argument to RGBtoHSB()):

float[] HSB = Color.RGBtoHSB(value1, value2, value3, (new float[3]));

Now we'll convert those floating-point values that range from 0.0 to 1.0 to values that range from 0 and 100 (for the saturation and brightness) and 0 to 360 for the hue by multiplying the appropriate numbers and reassigning the value back to the array:

HSB[0] *= 360;
HSB[1] *= 100;
HSB[2] *= 100;

Now we have the numbers we want. The last part of the update is to put those values back into the text fields. Of course, those values are still floating-point numbers, so we'll have to cast them to ints before turning them into strings and storing them:

HSBcontrols.tfield1.setText(String.valueOf((int)HSB[0]));
HSBcontrols.tfield2.setText(String.valueOf((int)HSB[1]));
HSBcontrols.tfield3.setText(String.valueOf((int)HSB[2]));

You're halfway there. The next part of the applet is that part that updates the RGB values where a text field on the HSB side has changed. This is the else in the big if-else that defines this method and determines what to update, given a change.

It's actually easier to generate values from HSB values than it is to do it the other way around. There's a class method in the Color class, called getHSBColor(), that creates a new Color object from three HSB values, and once you have a Color object you can easily pull the RGB values out of there. The catch, of course, is that getHSBColor takes three floating-point arguments, and the values we have are the integer values I prefer to use. So in the call to getHSBColor, we'll have to cast the integer values from the text fields to floats and divide them by the proper conversion factor. The result of getHSBColor is a Color object, so we can simply assign that object to our c local variable so we can use it again later:

c = Color.getHSBColor((float)value1 / 360, 
    (float)value2 / 100, (float)value3 / 100);

With the Color object all set, updating the RGB values involves extracting those values from that Color object. The getRed(), getGreen() and getBlue() methods, defined in the Color class, will do just that:

RGBcontrols.tfield1.setText(String.valueOf(c.getRed()));
RGBcontrols.tfield2.setText(String.valueOf(c.getGreen()));
RGBcontrols.tfield3.setText(String.valueOf(c.getBlue()));

And finally, regardless of whether the RGB or HSB value has changed, you'll need to update the color box on the left to reflect the new color. Because we have a new Color object stored in the variable c, we can use the setBackground method to change that color. Also note that setBackground doesn't automatically repaint the screen, so you'll want to fire off a repaint() as well:

swatch.setBackground(c);
swatch.repaint();

That's it! You're done. Compile both the ColorTest and ColorControls classes, create an HTML file to load the ColorTest applet, and check it out.

The Complete Source Code

Listing 13.3 shows the complete source code for the applet class ColorTest, and Listing 13.4 shows the source for the helper class ColorControls. Often it's easier to figure out what's going on in an applet when it's all in one place and you can follow the method calls and how values are passed back and forth. Start with the init() method in the ColorTest applet and go from there.


Listing 13.3. The ColorTest applet.
 1:import java.awt.*;
 2:
 3:public class ColorTest extends java.applet.Applet {
 4:  ColorControls RGBcontrols, HSBcontrols;
 5:  Canvas swatch;
 6:
 7:  public void init() {   
 8:    setLayout(new GridLayout(1,3,5,15));
 9:    
10:    // The color swatch
11:    swatch = new Canvas();
12:    swatch.setBackground(Color.black);
13:    
14:    // the subpanels for the controls
15:    RGBcontrols = new ColorControls(this, "Red", "Green", "Blue");
16:    HSBcontrols = new ColorControls(this, "Hue", "Saturation", "Brightness");
17:
18:    //add it all to the layout
19:    add(swatch);
20:    add(RGBcontrols);
21:    add(HSBcontrols);
22:  }
23:
24:  public Insets insets() {
25:    return new Insets(10,10,10,10);
26:  }
27:
28:  void update(ColorControls controlPanel) {
29:    Color c;
30:    // get string values from text fields, convert to ints
31:    int value1 = Integer.parseInt(controlPanel.tfield1.getText());
32:    int value2 = Integer.parseInt(controlPanel.tfield2.getText());
33:    int value3 = Integer.parseInt(controlPanel.tfield3.getText());
34:
35:    if (controlPanel == RGBcontrols) {  // RGB has changed, update HSB
36:      c = new Color(value1, value2, value3);
37:
38:      // convert RGB values to HSB values
39:      float[] HSB = Color.RGBtoHSB(value1, value2, value3, (new float[3]));
40:      HSB[0] *= 360;
41:      HSB[1] *= 100;
42:      HSB[2] *= 100;
43:
44:      // reset HSB fields
45:      HSBcontrols.tfield1.setText(String.valueOf((int)HSB[0]));
46:      HSBcontrols.tfield2.setText(String.valueOf((int)HSB[1]));
47:      HSBcontrols.tfield3.setText(String.valueOf((int)HSB[2]));
48:   
49:    } else {  // HSB has changed, update RGB
50:      c = Color.getHSBColor((float)value1 / 360, 
51:        (float)value2 / 100, (float)value3 / 100);
52:
53:      // reset RGB fields
54:      RGBcontrols.tfield1.setText(String.valueOf(c.getRed()));
55:      RGBcontrols.tfield2.setText(String.valueOf(c.getGreen()));
56:      RGBcontrols.tfield3.setText(String.valueOf(c.getBlue()));
57:    }
58:
59:    //update swatch
60:    swatch.setBackground(c);
61: swatch.repaint();
62:}
63:}


Listing 13.4. The ColorControls class.
 1:import java.awt.*;
 2:
 3:class ColorControls extends Panel {
 4:  TextField tfield1, tfield2, tfield3;
 5:  ColorTest applet;
 6:
 7:  ColorControls(ColorTest parent,
 8:        String l1, String l2, String l3) {
 9:
10:    // get hook to outer applet parent
11:    applet = parent;
12:
13:    //do layouts
14:    setLayout(new GridLayout(3,2,10,10));
15:    
16:    tfield1 = new TextField("0");
17:    tfield2 = new TextField("0");    
18:    tfield3 = new TextField("0");    
19:
20:    add(new Label(l1, Label.RIGHT));
21:    add(tfield1);
22:    add(new Label(l2, Label.RIGHT));
23:    add(tfield2);
24:    add(new Label(l3, Label.RIGHT));
25:    add(tfield3);
26:  }
27: 
28: public Insets insets() {
29:    return new Insets(10,10,0,0);
30:  }
31:
32:  public boolean action(Event evt, Object arg) {
33:    if (evt.target instanceof TextField) {
34:      applet.update(this);
35:      return true;
36:    } else return false;
37:  }
38:}

Up and Coming in Java 1.1

Everything you've learned up to this point is available in the 1.0.2 Java API. Java 1.1, however, will add many more features to the awt, as well as improve performance and robustness across platforms. The goal for the awt is to move beyond the basics that 1.0.2 provided and make the awt more suitable for large-scale application development. Note, also, that the 1.1 API will be backward-compatible with the 1.0.2 features; none of the code you write after reading this chapter will be obsolete in 1.1.

Explicit details about the changes to the awt for 1.1 were not available at the time this book was being written. Sun has announced the following teasers, however for new features in 1.1:

  • New components for pop-up menus, buttons with images on top of them, and menu accelerators
  • Support for clipboard operations (copy and paste), drag and drop, and printing
  • The ability to set a cursor for each component (currently you can have only one cursor per window; you'll learn about this on Day 14, "Windows, Networking, and Other Tidbits")
  • A new set of graphics primitives as part of the new 2D graphics model; you'll learn more about this on Day 27, "The Standard Extension APIs"
  • A new event model that delegates event actions to other objects, as opposed to requiring special methods (mouseDown(), action(), handleEvent(), and so on) to be overridden in the component classes themselves. Those action objects are often called callbacks in other event-driven programming systems.
  • Performance enhancements: a complete rewrite for Windows 95 and NT, improvements in how components are laid out and painted, better scrolling of components, and a "number of bug fixes."

For more information about the Java 1.1 changes to the awt, check out the 1.1 preview page at http://java.sun.com/products/JDK/1.1/designspecs/.

Summary

The Java awt, or Abstract Windowing Toolkit, is a package of Java classes and interfaces for creating full-fledged access to a window-based graphical user interface system, with mechanisms for graphics display, event management, text and graphics primitives, user-interface components, and cross-platform layout. Applets are also an integral part of the awt.

Today has been a big day; the lesson has brought together everything you've learned up to this point about simple applet management and added a lot more about creating applets, panels, and user-interface components and managing the interactions between all of them. With the information you got today and the few bits you'll learn tomorrow, you can create cross-platform Java applications that do just about anything you want.

Q&A

Q:
I really dislike working with layout managers; they're either too simplistic or too complicated (grid bag layout). Even with a whole lot of tinkering, I can never get my applets to look like I want them to. All I want to do is define the sizes of my components and put them at an x and y position on the screen. Can I do this?
A:
I'm going to tell you how to do this, but not without a lecture.
Java applications and the awt were designed such that the same graphical user interface could run equally well on different platforms and with different resolutions, different fonts, different screen sizes, and so on. Relying on pixel coordinates in this case is a really bad idea; variations from one platform to another or even from one Java environment to another on the same platform can mess up your careful layouts such that you can easily have components overlapping or obscuring each other, the edges of your applet cut off, or other layout disasters. Just as an example-I found significant differences in the layout of the same applet running in the JDK's appletviewer and in Netscape, both on Windows 95, side by side. Can you guarantee that your applet will always be run in precisely the same environment as the one in which you designed it? Layout managers, by dynamically placing elements on the screen, get around these problems. This does mean that your applet may end up looking not quite right on any platform-but at least it's usable on any platform. New versions of the awt promise to offer better layout and UI design controls.
Still not convinced? Well, then. To make a component a specific size and to place it at a particular position, use a null layout manager and the reshape() method:

setLayout(null);
Button myButton (new Button("OK");
mybutton.reshape(10, 10, 30, 15);

You can find out more about reshape() in the Component class.

Q:
I was exploring the awt classes, and I saw this subpackage called peer. There are also references to the peer classes sprinkled throughout the API documentation. What do peers do?
A:
Peers are responsible for the platform-specific parts of the awt. For example, when you create a Java awt window, you have an instance of the Window class that provides generic window behavior, and then you have an instance of a class implementing WindowPeer that creates the very specific window for that platform-a motif window under X Window, a Macintosh-style window under the Macintosh, or a Windows 95 window under Windows 95. These "peer" classes also handle communication between the window system and the Java window itself. By separating the generic component behavior (the awt classes) from the actual system implementation and appearance (the peer classes), you can focus on providing behavior in your Java application and let the Java implementation deal with the platform-specific details.
Q:
There's a whole lot of functionality in the awt that you haven't talked about here. Why?
A:
Given that even a basic introduction took this long, I figured that if I put in even more detail than I already have, this book would turn into Teach Yourself Java in 21 Days Plus a Few Extra for the awt Stuff.
 
As it is, I've left windows, menus, and dialog boxes until tomorrow, so you'll have to wait for those. But you can find out about a lot of the other features of awt merely by exploring the API documentation. Start with the Applet class and examine the sorts of methods you can call. Then look at Panel, from which Applet inherits-you have all that class's functionality as well. The superclass of Panel is Container, which provides still more interesting detail. Component comes next. Explore the API and see what you can do with it. You might find something interesting.
Q:
I have a new button class I defined to look different from the standard awt button objects. I'd like to implement callbacks on this button (that is, to execute an arbitrary function when the button is pressed), but I can't figure out how to get Java to execute an arbitrary method. In C++ I'd just have a pointer to a function. In Smalltalk I'd use perform:. How can I do this in Java?
A:
You can't; Java doesn't have this facility. This is why normal button actions are executed from the generic action() method rather than using a mechanism for actions attached to the button itself (which would be more object-oriented, easier to extend, and wouldn't require a whole lot of if...elses inside action()).


footer nav
Use of this site is subject certain Terms & Conditions.
Copyright (c) 1996-1999 EarthWeb, Inc.. All rights reserved. Reproduction in whole or in part in any form or medium without express written permission of EarthWeb is prohibited. Please read our privacy policy for details.