Copyright ©1996, Que Corporation. All rights reserved. No part of this book may be used or reproduced in any form or by any means, or stored in a database or retrieval system without prior written permission of the publisher except in the case of brief quotations embodied in critical articles and reviews. Making copies of any part of this book for any purpose other than your own personal use is a violation of United States copyright laws. For information, address Que Corporation, 201 West 103rd Street, Indianapolis, IN 46290 or at support@mcp .com.

Notice: This material is excerpted from Special Edition Using Java, ISBN: 0-7897-0604-0. The electronic version of this material has not been through the final proof reading stage that the book goes through before being published in printed form. Some errors may exist here that are corrected before the book is published. This material is provided "as is" without any warranty of any kind.

Chapter 21 - AWT

by Mark Wutka

What is the AWT?

The Abstract Windowing Toolkit (AWT) provides an API for common User Interface components like buttons and menus.

One of the main goals of Java is to provide a platform-independent development environment. The area of Graphical User Interfaces has always been one of the stickiest parts of creating highly portable code. The Windows API is different from the OS/2 Presentation Manager API, which is different from the X-Windows API, which is different from the Mac API. The most common solution to this problem is to take a look at all the platforms you want to use, identify the components that are common to all of them (or would be easy to implement on all of them), and create a single API that you can use. On each different platform, the common API would interface with the platform's native API so that applications using the common API would have the same look and feel as applications using the native API.

The opposite of this approach is to create a single look and feel and then implement that look and feel on each different platform. For Java, Sun chose the common API approach, which allows Java applications to blend in smoothly with their surroundings. Sun called this common API the Abstract Windowing Toolkit, or AWT for short.

In this chapter, you will learn how to :

The AWT contains a number of familiar user interface elements. Figure 21.1 shows a Java applet with a sample of some of the components of the AWT:

Fig. 21.1
The AWT features a number of familiar components.

Figure 21.2 shows you a portion of the AWT's inheritance hierarchy.

Fig. 21.2
The AWT inherits all its user interface components  from Component.

Components

Components are the building blocks of the AWT. The end-user interacts directly with these components. The components provided by the AWT are:

Containers

You need more than just components to create a good user interface. The components need to be organized into manageable groups. That's where containers come in. Containers contain components. You cannot use a component in the AWT unless it is contained within a container. A component without a container is like a refrigerator magnet without a refrigerator. The containers defined in the AWT are:

Even if you don't create a container in your applet, you are still using one. The Applet class is a subclass of the Panel class.

Containers not only contain components, they are components themselves. This means that a container can contain other containers.

Layout Managers

Even though you have containers as a place to neatly store your UI components, you still need a way to organize the components within a container. That's where the layout managers come in. Each container is given a layout manager that decides where each component should be displayed. The Layout Managers in the AWT are:

Buttons

Buttons are a simple mechanism, but they are one of the workhorses of any graphical interface. You find buttons on toolbars, dialog boxes, windows, and even in other components such as scrollbars.

Creating Buttons

The only thing you have to decide when creating a button is whether or not you want the button to be labeled. There are no other options for buttons.

To create an unlabeled button, use this syntax:

Button myButton = new Button();

Creating a labeled button is an equally simple task:

Button myButton = new Button("Press Me");

Once you have created a button, you need to add it to a container. Since your applet is already a container, you can add a button directly to your applet:

Button myButton = new Button("Press Me");

add(myButton); 

To change the label of a button, use setLabel:

Button.setLabel("Hey!  Press me!");

To get the label for a button use getLabel:

String buttonLabel = Button.getLabel();

You may notice the lack of "image buttons"-that is, buttons that contain an image instead of text. These types of buttons are almost a necessity for creating toolbars. Unfortunately, they are not supported in the AWT. Hopefully, these will show up in a future version of the AWT; but for now, if you want an image button, you'll have to implement it yourself.

Using Buttons

Now that you are able to create a button and add it to your applet, it's time to learn how to make the button do something.

All the components within the AWT have an action method which is called when an action is taken on the component. In the case of the button, action is called when the button is pressed. The action method is similar to some of the event handling methods you may have come across already like keyDown or mouseDown.

When an action takes place in a component, the AWT calls the handleEvent method in that component with an event type of ACTION_EVENT. The default handleEvent method calls the action method in the component. By default, the component doesn't handle the action, which causes the event to be passed to the handleEvent method in the parent container where, by default, the parent's action method is called. This continues until the action is either handled or it is ignored by the top-most container. A component signals that it has handled the event by returning a value of true from the event- handling method (action, handleEvent, or whatever). A return value of false from an event-handling method indicates that the component has not handled the event and the event should be passed up to the parent.

The format of the action method in all components is:

public boolean action(Event event, Object whatAction)

where event is the Event that has occurred in the component, and whatAction indicates what has occurred.

For buttons, whatAction is the label of the button that has been pressed. The event parameter contains other information specific to the action, such as the component where the action occurred (event.target) and the time the action occurred (event.when).

You should always check the event.target variable using the instanceof operator to make sure that the action is for the object you expect. For instance, if you expect that the action is for a Button, then you need to check that (event.target instanceof Button) is true.

Now that you know how to create a button and check for an action, you can create a button applet. A very simple example is an applet with buttons that change its background color. One way to do this is by putting the name of the color in the button label. Then, in the action method, you look at the label of the button that was pressed and set the applet's background color based on that label. For example, the button to turn the background blue could be labeled "Blue." The action method would set the background to blue if the button's label was blue. The applet in listing 21.1 demonstrates how to do this:

Listing 21.1-filename Source code for Button1Applet.java
import java.applet.*;
import java.awt.*;

// Example 21.1 - Button1Applet
//
// This applet creates two buttons named "Red" and "Blue".  When a
// button is pressed, the background color of the applet is set to
// the color named by that button's label.
//

public class Button1Applet extends Applet
{
     public void init()
     {
          add(new Button("Red"));

          add(new Button("Blue"));

     }

     public boolean action(Event evt, Object whatAction)
     {
// Check to make sure this is a button action, if not,
// return false to indicate that the event has not been handled.
          if (!(evt.target instanceof Button)) 
          {
               return false;
          }
          String buttonLabel = (String) whatAction;

          if (buttonLabel == "Red")
          {
               setBackground(Color.red);
          }
          else if (buttonLabel == "Blue")
          {
               setBackground(Color.blue);
          }
          repaint();     // Make the change visible immediately
          return true;
     }
}

You will learn a better way to design this applet in the section titled "Object-Oriented Thinking" later in this chapter.

Figure 21.3 shows you the Button1Applet in operation.

Fig. 21.3
The buttons in Button1Applet change the applet's background color.

Labels

Labels are the simplest of the AWT components. They are text strings that are used only for decoration. Since they are "display-only," labels have no action method.

For you technical purists, labels do have an action method since they inherit from the Component class, and all components have an action method. A label's action method is never called.

Creating Labels

There are three different ways to create a label. The simplest is to create an empty label, such as:

Label emptyLabel = new Label();

Of course, an empty label isn't going to do you much good since there is nothing to see. A more useful label is one with some text, as in:

Label myLabel = new Label("This is a label");

Labels can be left-justified, right-justified, or centered. The variables Label.LEFT, Label.RIGHT, and Label.CENTER can be used to set the alignment of a label. Here is an example of how to create a right-justified label:

Label myLabel = new Label("This is a right-justified label", Label.RIGHT);

You can change the text of a label with setText:

myLabel.setText("This is the new label text");

You can also get the text of a label with getText:

String labelText = myLabel.getText();

You can change the alignment of a label with setAlignment:

myLabel.setAlignment(Label.CENTER);

You can also get the alignment of a label with getAlignment:

int labelAlignment = myLabel.getAlignment();

Figure 21.4 shows you a sample label.

Fig. 21.4
Labels are simply text strings.

Checkboxes and Radio Buttons

Checkboxes are similar to buttons except that they are used as "yes-no" or "on-off" switches. Every time you click a checkbox it changes from "off" to "on" or from "on" to "off." A close cousin to the checkbox is the radio button. Radio buttons are also "on-off" switches, but they are arranged in special mutually-exclusive groups where only one button in the group can be on at a time. Imagine what a radio would sound like if you could have more than one station on at a time!

Creating Checkboxes

A checkbox has two parts-a label and a state. The label is the text that is displayed next to the checkbox itself, while the state is a boolean variable that indicates whether or not the box is checked. By default, the state of a checkbox is false, or "off."

To create a checkbox with no label:

Checkbox myCheckbox = new Checkbox();

To create a checkbox with a label:

Checkbox myCheckbox = new Checkbox("Check me if you like Java");

You can also create a checkbox while setting its state:

Checkbox myCheckbox = new Checkbox("Check me if you like Java", null, true);

The null in the preceding code fragment refers to the CheckboxGroup to which the checkbox belongs. You use CheckboxGroup to create a set of radio buttons, for a normal checkbox, the CheckboxGroup will be null.

You may check to see if a checkbox has been checked with getState:

if (myCheckbox.getState()) {
     // The box has been checked
} else {
     // The box has not been checked
}

Creating Radio Buttons

Radio buttons are just a special case of a checkbox. There is no RadioButton class. Instead, you create a set of radio buttons by creating checkboxes and putting them in the same checkbox group. The constructor for CheckboxGroup takes no arguments:

CheckboxGroup myCheckboxGroup = new CheckboxGroup()

Once you have created the group, you create checkboxes that belong to this group by passing the group to the constructor. You can then add them to the applet:

add(new Checkbox("Favorite language is Java", myCheckboxGroup, true));
add(new Checkbox("Favorite language is Visual Cobol", myCheckboxGroup, false));
add(new Checkbox("Favorite language is Backtalk", myCheckboxGroup, false));

When you add checkboxes to a checkbox group, the last checkbox that was added as true is the box that is checked when the group is displayed.

You can find out which radio button is selected by either calling getState on each checkbox, or calling getCurrent on the CheckboxGroup. The getCurrent method returns the checkbox that is currently selected.

Using Checkboxes and Radio Buttons

The action method for a checkbox or a radio button is called whenever it is clicked. The whichAction parameter of the action method will be an instance of a Boolean class that is true if the checkbox was clicked on, or false if the checkbox was clicked off. If you create an action method for a radio button, you should not rely on the whichAction parameter to contain the correct value. If a radio button is clicked when it is already on, the whichAction contains a false value even though the button is still on. You are safer just using the getState method to check the state of the radio button or the checkbox. You can also use the getLabel method to determine which checkbox has been checked. The following code fragment shows an action method that responds to a box being checked and retrieves the current state of the box:

public boolean action(Event evt, Object whichAction)
{
if (evt.target instanceof Checkbox)  // make sure this is a checkbox
     {
            Checkbox currentCheckbox = (Checkbox)evt.target;
            boolean checkboxState = currentCheckbox.getState();
 
            if (currentCheckbox.getLabel() == "Check me if you like Java")
            {   
                  if (checkboxState)
                  {
                   // Code to handle "Check me if you like Java" being set to on
                  }
                  else
                  {
                  // Code to handle "Check me if you like Java" being set to off
                  }
                  return true;  // the event has been handled
            }
      }
      return false;  // the event has not been handled
}

Whenever you write an event-handling method like handleEvent or action, you should return true only in the cases where you actually handle the event. Notice that the example action method for checkboxes only returns true in the case where the event is a checkbox event. It returns false in all other cases. You may also have cases where you handle an event but you still want to allow other classes to handle the same event. In those cases, you also return false.

Figure 21.5 shows you some checkboxes and a group of three radio buttons.

Fig 21.5
Checkboxes are squared boxes with checks in them.  Radio buttons are rounded and are checked with dots.

Choices

The Choice class provides a pop-up menu of text string choices. The current choice is displayed as the menu title.

Creating Choices

To create a choice pop-up menu, you must first create an instance of the Choice class. Since there are no options for the choice constructor, the creation of a choice should always look something like this:

Choice myChoice = new Choice();

Once you have created the choice, you can add string items to it using the addItem method:

myChoice.addItem("Moe");
myChoice.addItem("Larry");
myChoice.addItem("Curly");

You may also change which item is currently selected either by name or by index. If you wanted Curly to be selected, for instance, you could select him by name:

myChoice.select("Curly");     // Make "Curly" become selected item

You could also select Curly by his position in the list. Since he was added third, and the choices are numbered starting at 0, Moe would be 0, Larry would be 1, and Curly would be 2:

myChoice.select(2);     // Make the third list entry become selected

The getSelectedIndex method will return the position of the selected item. Again, if Curly was selected, getSelectedIndex would return 2. Similarly, the getSelectedItem method returns the string name of the selected item, so if Curly was selected, getSelectedItem would return "Curly."

If you have an index value for an item and you want to find out the name of the item at that index, you can use getItem:

String selectedItem = myChoice.getItem(2);

Figure 21.6 shows a choice in its usual form, while figure 21.7 shows a choice with its menu of choices pulled down.

Fig. 21.6
The choice box displays its current selection.
Fig. 21.7
The button on the right of a choice pops up a menu of the possible choices.

Using Choices

The action method for a choice is called whenever a choice is made, even if it is the same choice. The whatAction parameter contains the name of the selected item. The following code fragment gives an example action method for a choice where the selection is stored in a String variable within the applet:

String currentStooge;

public boolean action(Event event, Object whatAction)
{
// Check to make sure this is a choice object, if not
// indicate that the event has not been handled.
     if (!(event.target instanceof Choice))
     {
          return false;
     }
     Choice whichChoice = (Choice) event.target;
// See if this is an action for myChoice
     if (whichChoice == myChoice)
     {
          currentStooge = (String) whatAction;
          return true; // the event has been handled
     }
     return false;  // it must have been a different Choice
}

Lists

The List class allows you to create a scrolling list of values that may be selected either individually, or many at a time.

Creating Lists

You have two options when creating a list. The default constructor for the List class allows you to create a list that does not allow multiple selections:

List myList = new List();

You may also set the number of list entries that are visible in the list window at any one time, as well as whether or not to allow multiple selections. The following code fragment creates a list with 10 visible entries and multiple selections turned on:

List myList = new List(10, true);   // True means allow multiple selections

Once you have created the list, you can add new entries to it with the addItem method:

myList.addItem("Moe");
myList.addItem("Larry");
myList.addItem("Curly");

You may also add an item at a specific position in the list. The list positions are numbered from 0, so if you add an item at position 0, it goes to the front of the list. If you try to add an item at position -1, or try to add an item at a position higher than the number of positions, the item will be added to the end of the list. The following code adds "Shemp" to the beginning of the list, and "Curly Joe" to the end:

myList.addItem("Shemp", 0);        // Add Shemp at position 0
myList.addItem("Curly Joe", -1);   // Add Curly Joe to the end of the list

List Features

The List class provides a number of different methods for changing the contents of the list. The replaceItem method will replace an item at a given position with a new item:

myList.replaceItem("Dr. Howard", 0);
         // Replace the first item in the list with "Dr. Howard"

You can delete an item in the list with deleteItem:

myList.deleteItem(1);
         // Delete the second item in the list (0 is the first)

The deleteItems method deletes a whole range of items from the list. The following code removes items from the list starting at position 2, up to and including position 5:

myList.deleteItems(2, 5);
         // Delete from position 2 up to and including position 5

You can delete all the items in the list with the clear method:

myList.clear();

The getSelectedIndex method returns the index number of the currently selected item, or -1 if no item is selected:

int currentSelection = myList.getSelectedIndex();

You can also get the selected item directly with getSelectedItem:

String selectItem = myList.getSelectedItem();

For lists with multiple selections turned on, you can get all the selections with getSelectedIndexes:

int currentSelections[];
currentSelections = myList.getSelectedIndexes();

The getSelectedItems returns all the selected items:

String selectedItems[];
selectItems = myList.getSelectItems();

You should only use getSelectedIndex and getSelectedItem on lists without multiple selections. If you allow multiple selections, you should always use getSelectedIndexes and getSelectedItems.

You may make any item become selected by calling the select method with the index of the item you want selected. If the list does not allow multiple selections, the previously selected item will be deselected:

myList.select(2);       // Select the third item in the list

You may deselect any item by calling the deselect method with the index of the item you want deselected:

myList.deselect(0);     // Deselect the first item in the list

The isSelected method will tell you whether or not the item at a particular index is selected:

if (myList.isSelection(0))
{
         // the first item in the list is selected
}

You may turn multiple selections on and off with the setMultipleSelections method:

myList.setMultipleSelections(true);
         // turn multi-select on, false turns it off

The allowsMultipleSelections method returns true if multiple selections are allowed:

if (myList.allowsMultipleSelections())
{
     // multiple selections are allowed
}

Sometimes you might make sure a particular item is visible in the list window. You can do just that by passing the index of the item you want to make visible to makeVisible. For example, suppose the list was positioned on item 0, but you wanted to make sure item 15 was showing in the window instead, you would call:

myList.makeVisible(15);          // Make item 15 in the list visible

Using Lists

Unlike the previous User Interface components you have encountered, the List class does not make use of the action method. Instead, you must use the handleEvent method to catch list selection and deselection events. The handleEvent method is called whenever you select or deselect an item in a list. The format of handleEvent is:

public boolean handleEvent(Event event)

When an item on a list is selected, event.id will be equal to Event.LIST_SELECT, and event.arg will be an instance of an integer whose value is the index of the selected item. The deselect event is identical to the select event except that event.id is Event.LIST_DESELECT. LIST_SELECT and LIST_DESELECT are declared in the Event class as static variables, as are all the other event types.

The applet in listing 21.2 sets up a List containing several values and uses a label to inform you whenever an item is selected or deselected:

Listing 21.2-Source code for ListApplet.java
// Example 21.2 - ListApplet
//
// This applet creates a scrolling list with several choices and
// informs you of selections and deselections using a label.
//

import java.applet.*;
import java.awt.*;

public class ListApplet extends Applet
{
     Label listStatus;
     List scrollingList;

     public void init()
     {

// First, create the List

          scrollingList = new List(3, true);

// Now add a few items to the list

          scrollingList.addItem("Moe");

          scrollingList.addItem("Larry");

          scrollingList.addItem("Curly");

          scrollingList.addItem("Shemp");

          scrollingList.addItem("Curly Joe");

// Set Shemp to be selected

          scrollingList.select(3);

// Finally, add the list to the applet

          add(scrollingList);

// Now create a label to show us the last event that occurred
          
          listStatus = new Label("You selected entry Shemp");
          add(listStatus);

     }

     public boolean handleEvent(Event evt)
     {
          String selectionString;
          Integer selection;

// Since we are handling events in the applet itself,
// we need to check to make sure the event is for the scrollingList.

          if (evt.target == scrollingList)
          {

// Check to see if this is a selection event

               if (evt.id == Event.LIST_SELECT)
               {
// selection is the index of the selected item
                    selection = (Integer) evt.arg;
// use getItem to get the actual item.
                    selectionString = "You selected entry "+
                         scrollingList.getItem(
                              selection.intValue());
// Update the label
                    listStatus.setText(selectionString);
               }
               else if (evt.id == Event.LIST_DESELECT)
               {
// If this is a deselection, get the deselected item
// selection is the index of the selected item
                    selection = (Integer) evt.arg;
// use getItem to get the actual item.
                    selectionString = "You deselected entry "+
                         scrollingList.getItem(
                              selection.intValue());
// Update the label
                    listStatus.setText(selectionString);
               }
          }
          return true;
     }
}

Figure 21.8 shows the output from ListApplet.

Fig. 21.8
The ListApplet program lets you select and deselect list items.

Text Fields and Text Areas

The AWT provides two different classes for entering text data-TextField and TextArea. The TextField class handles only a single line of text, while the TextArea handles multiple lines. Both of these classes share many similar methods, since they both are derived from a common class called TextComponent.

Creating Text Fields

The easiest way to create a text field is:

TextField myTextField = new TextField();

This will create an empty text field with an unspecified number of columns. If you want to control how many columns are in the text field, you can do so with:

TextField myTextField = new TextField(40);     // Create 40-column text field

Sometimes you may want to initialize the text field with some text when you create it:

TextField myTextField = new TextField("This is some initial text");

Rounding out these combinations is a method for creating a text field initialized with text, having a fixed number of columns:

TextField myTextField = new TextField("This is some initial text", 40);

Creating Text Areas

It should come as no surprise to you that the methods to create text areas are similar to those for text fields. In fact, they are identical, except that when giving a fixed size for a text area you must give both columns and rows. You can create an empty text area having an unspecified number of rows and columns with:

TextArea myTextArea = new TextArea();

If you want to initialize an area with some text:

TextArea myTextArea = new TextArea("Here is some initial text");

You can give a text area a fixed number of rows and columns with:

TextArea myTextArea = new TextArea(5, 40 );      // 5 rows, 40 columns

Finally, you can create a text area having some initial text and a fixed size with:

TextArea myTextArea = new TextArea(
      "Here is some initial text", 5, 40); // 5 rows, 40 cols

Common Text Component Features

The TextComponent abstract class implements a number of useful methods that may be used on either TextArea or TextField classes.

You will probably want to put text into the component at some point. You can do that with setText:

myTextField.setText("This is the text now in the field");

You will certainly want to find out what text is in the component. You can use getText to do that:

String textData = myTextArea.getText();

You can find out what text has been selected (highlighted with the mouse) by using getSelectedText:

String selectedStuff = myTextArea.getSelectedText();

You can also find out where the selection starts and where it ends. The getSelectionStart and getSelectionEnd methods return integers that indicate the position within the entire text where the selection starts and ends. For instance, if the selection started at the very beginning of the text, getSelectionStart would return 0:

int selectionStart, selectionEnd;
selectionStart = myTextField.getSelectionStart();
selectionEnd = myTextField.getSelectionEnd();

You can also cause text to be selected with the select method:

myTextField.select(0, 4);
         // Selects the characters from position 0 through 4

If you want to select the entire text, you can use selectAll as a shortcut:

myTextArea.selectAll();     // Selects all the text in the area

You can also use setEditable to control whether the text in the component can be edited (if not, it is read-only):

myTextField.setEditable(false);
         // Don't let anyone change this field

The isEditable method will return true if the component is editable, or false if it is not.

Text Field Features

Text fields have some features that text areas do not have. The TextField class allows you to set an echo character that is printed instead of the character that was typed. This is useful when making fields for entering passwords, where you might make '*' the echo character. Setting up an echo character is as easy as calling setEchoCharacter:

myTextField.setEchoCharacter('*'); // Print *s in place of what was typed 

You can find out the echo character for a field with getEchoChar:

char echoChar = myTextField.getEchoChar();

The echoCharIsSet method will return true if there is an echo character set for the field, or false if not.

Finally, you can find out how many columns are in the text field (how many visible columns, not how much text is there) by using the getColumns method:

int numColumns = myTextField.getColumns();

Text Area features

Text areas also have special features all their own. Text areas are usually used for editing text, so they contain some methods for inserting, appending, and replacing text. You can add text to the end of the text area with appendText:

myTextArea.appendText(
     "This will be added to the end of the text in the area");

You can also insert text at any point in the current text with insertText. For instance, if you add text at position 0, you will add it to the front of the area:

myTextArea.insertText(
     "This will be added to the front of the text in the area", 0);

You can also use replaceText to replace portions of the text. Here is an example that uses the getSelectionStart and getSelectionEnd functions from TextComponent to replace selected text in a TextArea with "[CENSORED]":

myTextArea.replaceText("[CENSORED]", myTextArea.getSelectionStart(), 
     myTextArea.getSelectionEnd());

Finally, you can find out the number of columns and the number of rows in a text area with getColumns and getRows.

Using Text Fields and Text Areas

Like the List class, the TextArea class does not use the action method. However, in this case, you probably do not need to use the handleEvent method, either. The events you would get for the TextArea would be keyboard and mouse events, and you want the TextArea class to handle those itself. What you should do instead is create a button for the user to press when they have finished editing the text. Then you can use getText to retrieve the edited text.

The TextField class does use the action method, but only in the case of the user pressing return. You may find this useful, but again, you could create a button for the user to signal that they have finished entering the text (especially if there are a number of text fields they must fill out).

Listing 21.3 creates two text fields, a text area with an echo character defined and a text area that displays the value of the text entered in one of the text fields:

Listing 21.3-Source code for TextApplet.java
import java.awt.*;
import java.applet.*;

// TextApplet
// This applet creates some text fields and a text area
// to demonstrate the features of each. 
//

public class TextApplet extends Applet
{
        protected TextField inputField;
        protected TextField passwordField;

        protected TextArea textArea;

        public void init()
        {
                inputField = new TextField();   // unspecified size
                add(inputField);

                passwordField = new TextField(10); // 10 columns
                passwordField.setEchoCharacter('*'); // print '*' for input
                add(passwordField);

                textArea = new TextArea(5, 40); // 5 rows, 40 cols
                textArea.appendText(
                        "This is some initial text for the text area.");
                textArea.select(5, 12); // select "is some"

                add(textArea);
        }

// The action method looks specifically for something entered in the
// password field and displays it in the textArea

        public boolean action(Event evt, Object whichAction)
        {
// Check to make sure this is an event for the passwordField
// if not, signal that the event hasn't been handled
                if (evt.target != passwordField)
                {
                        return false;  // Event not handled
                }

// Now, change the text in the textArea to "Your password is: "
// followed by the password entered in the passwordField

                textArea.setText("Your password is: "+
                        passwordField.getText());
                return true;    // Event has been handled
        }
}

Figure 21.9 shows the text fields and text area set up by the TextApplet example. Notice how small the first text field is because its size was left unspecified.

Fig. 21.9
Text fields and text areas allow the entry of text.

Scrollbars

The Scrollbar class provides a basic interface for scrolling that can be used in a variety of situations. The controls of the scrollbar manipulate a position value that indicates the scrollbar's current position. You can set the minimum and maximum values for the scrollbar's position as well as its current value. The scrollbar's controls update the position in three ways-"line," "page," and "absolute." The arrow buttons at either end of the scrollbar update the scrollbar position with a "line" update. You can tell the scrollbar how much to add to the position (or subtract from it) for a line update, the default is 1. A "page" update is performed whenever the mouse is clicked on the gap between the slider button and the scrolling arrows. You may also tell the scrollbar how much to add to the position for a page update. The "absolute" update is performed whenever the slider button is dragged in one direction or the other. You have no control over how the position value changes for an absolute update, except that you are able to control the minimum and maximum values.

An important aspect of the Scrollbar class is that it is only responsible for updating its own position. It is unable to cause any other component to scroll. If you want the scrollbar to scroll a canvas up and down, you have to add code to detect when the scrollbar changes and update the canvas as needed.

Creating Scrollbars

You can create a simple vertical scrollbar with:

Scrollbar myScrollbar = new Scrollbar();

You can also specify the orientation of the scrollbar as either Scrollbar.HORIZONTAL or Scrollbar.VERTICAL:

Scrollbar myScrollbar = new Scrollbar(Scrollbar.HORIZONTAL);

You can create a scrollbar with a pre-defined orientation, position, page increment, minimum value and maximum value. The following code creates a vertical scrollbar with a minimum value of 0, a maximum value of 100, a page size of 10, and a starting position of 50:

Scrollbar myScrollbar = new Scrollbar(Scrollbar.VERTICAL, 50, 10, 0, 100);

Scrollbar Features

You can set the scrollbar's line increment with setLineIncrement:

myScrollbar.setLineIncrement(2);
         // Arrow button increment/decrement by 2 each time

You can query the current line increment with getLineIncrement:

int lineIncrement = myScrollbar.getLineIncrement();

You can set the page increment with setPageIncrement:

myScrollbar.setPageIncrement(20);
         // Page update adds/subtracts 20 each time

You can also query the page increment with getPageIncrement.

int pageIncrement = myScrollbar.getPageIncrement();

You can find out the scrollbar's minimum and maximum position values with getMinimum and getMaximum:

int minimum = myScrollbar.getMinimum();
int maximum = myScrollbar.getMaximum();

The setValue method sets the scrollbar's current position:

myScrollbar.setValue(25);     // Make the current position 25

You can query the current position with getValue:

int currentPosition = myScrollbar.getValue();

The getOrientation method will return Scrollbar.VERTICAL if the scrollbar is vertical or Scrollbar.HORIZONTAL if it is horizontal:

if (myScrollbar.getOrientation() == Scrollbar.HORIZONTAL)
{
     // Code to handle a horizontal scrollbar
}
else
{
   // Code to handle a vertical scrollbar
}

You can also set the position, page increment, minimum value and maximum value with setValues. The following code sets the position to 75, the page increment to 25, the minimum value to 0, and the maximum to 500:

myScrollbar.setValues(75, 25, 0, 500);

Using Scrollbars

Like the List class, the Scrollbar class does not make use of the action method. You must use the handleEvent method to determine when a scrollbar has moved. The possible values of evt.id for events generated by the Scrollbar class are:

You may not care which of these events is received. In many cases, you may only need to know that the scrollbar position is changed and you would call the getValue method to find out the new position.

The IntScrollbar class introduced later in this chapter in the section titled "Using Observables" demonstrates how to create your own custom scrollbar.

Canvases

The Canvas class is a component with no special functionality. It is mainly used for creating custom graphic components. You create an instance of a Canvas with:

Canvas myCanvas = new Canvas();

However, you will almost always want to create your own special subclass of Canvas that does whatever special function you need. You should override the Canvas paint method to make your Canvas do something interesting. The Listing 21.4 creates a CircleCanvas class that draws a filled circle in a specific color:

Listing 21.4-Source code for CircleCanvas.java
import java.awt.*;

// Example 21.4 CircleCanvas class
//
// This class creates a canvas that draws a circle on itself.
// The circle color is given at creation time, and the size of
// the circle is determined by the size of the canvas.
//

public class CircleCanvas extends Canvas
{
     Color circleColor;

// When you create a CircleCanvas, you tell it what color to use.

     public CircleCanvas(Color drawColor)
     {
          circleColor = drawColor;
     }

     public void paint(Graphics g)
     {
          int circleDiameter, circleX, circleY;

          Dimension currentSize = size();

// Use the smaller of the height and width of the canvas.
// This guarantees that the circle will be drawn completely.


          if (currentSize.width < currentSize.height)
          {
               circleDiameter = currentSize.width;
          }
          else
          {
               circleDiameter = currentSize.height;
          }

          g.setColor(circleColor);

// The math here on the circleX and circleY may seem strange.  The x and y
// coordinates for fillOval are the upper-left coordinates of the rectangle
// that surrounds the circle.  If the canvas is wider than the circle, for
// instance, we want to find out how much wider (i.e. width - diameter)
// and then, since we want equal amounts of blank area on both sides,
// we divide the amount of blank area by 2.  In the case where the diameter
// equals the width, the amount of blank area is 0.

          circleX = (currentSize.width - circleDiameter) / 2;
          circleY = (currentSize.height - circleDiameter) / 2;

          g.fillOval(circleX, circleY, circleDiameter, circleDiameter);
     }
}

The CircleCanvas is only a component, not a runnable applet. Later in this chapter in the section titled "Grid Bag Layouts" you'll use this new class in an example of using the GridBagLayout layout manager.

Containers

In addition to all of these wonderful components, the AWT provides several useful containers:

Panels

Since panels are only used for organizing components, there are very few things you can actually do to a panel. You create a new panel with:

Panel myPanel = new Panel();

You can then add the panel to another container. For instance, you might want to add it to your applet:

add(myPanel);

You can also nest panels-one panel containing one or more other panels:

Panel mainPanel, subPanel1, subPanel2;
subPanel1 = new Panel();   // create the first sub-panel
subPanel2 = new Panel();   // create the second sub-panel
mainPanel = new Panel();   // create the main panel

mainPanel.add(subPanel1);  // Make subPanel1 a child (sub-panel) of mainPanel
mainPanel.add(subPanel2);  // Make subPanel2 a child of mainPanel

You can nest panels as many levels deep as you like. For instance, in the above example, you could have made subPanel2 a child of subPanel1 (obviously with different results).

Listing 21.5 shows how to create panels and nest sub-panels within them:

Listing 21.5-Source code for PanelApplet.java
import java.awt.*;
import java.applet.*;

// PanelApplet
//
// The PanelApplet applet creates a number of panels and
// adds buttons to them to demonstrate the use of panels
// for grouping components.

public class PanelApplet extends Applet
{
        public void init()
        {
// Create the main panels
                Panel mainPanel1 = new Panel();
                Panel mainPanel2 = new Panel();

// Create the sub-panels
                Panel subPanel1 = new Panel();
                Panel subPanel2 = new Panel();

// Add a button directly to the applet
                add(new Button("Applet Button"));

// Add the main panels to the applet
                add(mainPanel1);
                add(mainPanel2);

// Give mainPanel1 a button and a sub-panel
                mainPanel1.add(new Button("Main Panel 1 Button"));
                mainPanel1.add(subPanel1);

// Give mainPanel2 a button and a sub-panel
                mainPanel2.add(new Button("Main Panel 2 Button"));
                mainPanel2.add(subPanel2);

// Give each sub-panel a button
                subPanel1.add(new Button("Sub-panel 1 Button"));

                subPanel2.add(new Button("Sub-panel 2 Button"));
        }
}

Figure 21.10 shows the output from PanelApplet.

Fig. 21.10
Panels, like other containers, help group components together. 

Frames

Frames are a powerful feature of the AWT. They enable you to create separate windows for you application. For instance, you might want your application to run outside the main window of a Web browser. You can also use frames to build stand-alone graphical applications.

Creating Frames

You can create a frame that is initially invisible and has no title with:

Frame myFrame = new Frame();

You can give the frame a title when you create it, but it will still be invisible:

Frame myFrame = new Frame("Hi!  This is my frame!");

Frame Features

Once you have created a frame, you will probably want to see it. Before you can see the frame, you must give it a size. Use the resize method to set the size:

myFrame.resize(300, 100);  // Make the frame 300 pixels wide, 100 high

You can use the show method to make it visible:

myFrame.show();     // Show yourself, Frame!

You can send a frame back into hiding with the hide method. Even though the frame is invisible, it still exists:

myFrame.hide();

As long as a frame exists, invisible or not, it is consuming some amount of resources in the windowing system it is running on. If you have finished with a frame, you should get rid of it with the dispose method:

myFrame.dispose();     // Gets rid of the frame and releases its resources

You can change the title displayed at the top of the frame with setTitle:

myFrame.setTitle("With Frames like this, who needs enemies?");

The getTitle method will return the frame's title:

String currentTitle = myFrame.getTitle();

The Frame class has a number of different cursors. You can change the frame's cursor with setCursor:

myFrame.setCursor(Frame.HAND_CURSOR);        // Change cursor to a hand

The available cursors are: Frame.DEFAULT_CURSOR, Frame.CROSSHAIR_CURSOR, Frame.TEXT_CURSOR, Frame.WAIT_CURSOR, Frame.HAND_CURSOR, Frame.MOVE_CURSOR, Frame.N_RESIZE_CURSOR, Frame.NE_RESIZE_CURSOR, Frame.E_RESIZE_CURSOR, Frame.SE_RESIZE_CURSOR, Frame.S_RESIZE_CURSOR, Frame.SW_RESIZE_CURSOR, Frame.W_RESIZE_CURSOR, and Frame.NW_RESIZE_CURSOR.

The getCursorType method will return one of these values indicating the current cursor type.

If you do not want to allow your frame to be resized, you can call setResizable to turn resizing on or off:

myFrame.setResizable(false);     // Turn resizing off

You can change a frame's icon with setIconImage:

myFrame.setIconImage(someIconImage);
         // someIconImage must be an instance of Image

Using Frames to make your applet run standalone

You can create applets that can run either as an applet or as a standalone application. All you need to do is write a main method in the applet that creates a Frame then creates an instance of the applet that belongs to the frame. Listing 21.4 shows an applet that can run either as an applet or as a standalone application:

Listing 21.4-Source code for StanaloneApplet.java
import java.awt.*;
import java.applet.*;

// StandaloneApplet is an applet that runs either as
// an applet or a standalone application.  To run
// standalone, it provides a main method that creates
// a frame, then creates an instance of the applet and
// adds it to the frame.

public class StandaloneApplet extends Applet
{
        public void init()
        {
                add(new Button("Standalone Applet Button"));
        }

        public static void main(String args[])
        {
// Create the frame this applet will run in
                Frame appletFrame = new Frame("Some applet");

// Create an instance of the applet
                Applet myApplet = new StandaloneApplet();

// Initialize and start the applet
                myApplet.init();
                myApplet.start();

// The frame needs a layout manager
                appletFrame.setLayout(new FlowLayout());

// Add the applet to the frame
                appletFrame.add(myApplet);

// Have to give the frame a size before it is visible
                appletFrame.resize(300, 100);

// Make the frame appear on the screen
                appletFrame.show();
        }
}

Adding Menus to Frames

You can attach a MenuBar class to a frame to provide drop-down menu capabilities. You can create a menu bar with:

MenuBar myMenuBar = new MenuBar();

Once you have created a menubar, you can add it to a frame using the setMenuBar method:

myFrame.setMenuBar(myMenuBar);

Once you have a menu bar, you can add menus to it. The following code fragment creates a menu called "File" and adds it to the menu bar:

Menu fileMenu = new Menu("File");
myMenuBar.add(fileMenu);

Some windowing systems allow you to create menus that stay up after you release the mouse button. These are referred to as "tear-off" menus. You can specify that a menu is a "tear-off" menu when you create it:

Menu tearOffMenu = new Menu("Tear Me Off", true);
         // true indicates it can be torn off

In addition to adding sub-menus, you will want to add menu items to your menus. Menu items are the parts of a menu the user actually selects. Menus, on the other hand, are used to contain menu items as well as sub-menus. For instance, the File menu on many systems contains menu items such as New, Open, Save, and Save As. If you created a menu structure with no menu items, the menu structure would be useless. There would be nothing to select. You may add menu items to a menu in two ways. You can simply add an item name with:

fileMenu.add("Open");     // Add an "Open" option to the file menu

You can also add an instance of a MenuItem class to a menu:

MenuItem saveMenuItem = new MenuItem("Save");
         // Create a "Save" menu item
fileMenu.add(saveMenuItem);        // Add the "Save" option to the file menu

You can enable and disable menu items by using enable and disable. When you disable a menu item, it still appears on the menu, but it usually appears in gray (depending on the windowing system). You cannot select menu items that are disabled. The format for enable and disable is:

saveMenuItem.disable();     // Disables the save option from the file menu
saveMenuItem.enable();      // Enables the save option again

In addition to menu items, you can add sub-menus and menu separators to a menu. A separator is a line that appears on the menu to separate sections of the menu. To add a separator, just call the addSeparator method:

fileMenu.addSeparator();

To create a sub-menu, just create a new instance of a menu and add it to the current menu:

Menu printSubmenu = new Menu("Print");
fileMenu.add(printSubmenu);
printSubmenu.add("Print Preview");
         // Add print preview as option on Print menu
printSubmenu.add("Print Document");
         // Add print document as option on Print menu

You can also create special checkbox menu items. These items function like the checkbox buttons. The first time you select one, it becomes checked or "on." The next time you select it, it becomes unchecked or "off." To create a checkbox menu item:

CheckboxMenuItem autoSaveOption = new CheckboxMenuItem("Auto-save");
fileMenu.add(autoSaveOption);

You can check to see whether a checkbox menu item is checked or not with getState:

if (autoSaveOption.getState())
{
     // autoSaveOption is checked, or "on"
}
else
{
     // autoSaveOption is off
}

You can set the current state of a checkbox menu item with setState:

autoSaveOption.setState(true);     // Explicitly turn auto-save option on

Normally, menus are added to a menu bar in a left to right fashion. Many windowing systems, however, create a special "help" menu that is on the far right of a menu bar. You can add such a menu to your menu bar with the setHelpMenu method:

Menu helpMenu = new Menu();
myMenuBar.setHelpMenu(helpMenu);

Using Menus

Whenever a menu item is selected it generates an action. The whichAction parameter to the action method will be the name of the item selected:

public boolean action(Event evt, Object whichAction)
{

// First, make sure this event is a menu selection

     if (evt.target instanceof MenuItem)
     {
          if ((String)whichAction == "Save")
          {
               // Handle save option
          }
     }
     return true;
}

Listing 21.5 shows an application that sets up a simple File menu with New, Open, and Save menu items, a checkbox called Auto-Save, and a Print sub-menu with two menu items on it:

Listing 21.5-Source code for MenuApplication.java 
import java.awt.*;
import java.applet.*;

public class MenuApplication extends Object
{
        public static void main(String[] args)
        {
// Create the frame and the menubar
                Frame myFrame = new Frame("Menu Example");
                MenuBar myMenuBar = new MenuBar();

// Add the menubar to the frame
                myFrame.setMenuBar(myMenuBar);

// Create the File menu and add it to the menubar
                Menu fileMenu = new Menu("File");
                myMenuBar.add(fileMenu);

// Add the New and Open menuitems
                fileMenu.add(new MenuItem("New"));
                fileMenu.add(new MenuItem("Open"));

// Create a disabled Save menuitem
                MenuItem saveMenuItem = new MenuItem("Save");
                fileMenu.add(saveMenuItem);
                saveMenuItem.disable();

// Add an Auto-Save checkbox, followed by a separator
                fileMenu.add(new CheckboxMenuItem("Auto-Save"));
                fileMenu.addSeparator();

// Create the Print submenu
                Menu printSubmenu = new Menu("Print");
                fileMenu.add(printSubmenu);
                printSubmenu.add("Print Preview");
                printSubmenu.add("Print Document");

// Must resize the frame before it can be shown
                myFrame.resize(300, 200);

// Make the frame appear on the screen
                myFrame.show();
        }
}

Figure 21.11 shows the output from the MenuApplication program, with the "Print Document" option in the process of being selected.

Fig. 21.11
The AWT provides a number of popular menu features including checked menu items, disabled menu items, and separators.

Dialogs

Dialogs are pop-up windows that are not quite as flexible as frames. You can create a dialog as either "modal" or "non-modal." The term modal means the dialog box blocks input to other windows while it is being shown. This is useful for dialogs where you want to stop everything and get a crucial question answered such as "Are you sure you want to quit?" An example of a "non-modal" dialog box might be a control panel that changes settings in an application while the application continues to run.

Creating Dialogs

You must first have a frame in order to create a dialog. A dialog cannot belong to an applet. However, an applet may create a frame to which the dialog can then belong. You must specify whether a dialog is modal or non-modal at creation time and cannot change its "modality" once it has been created. The following example creates a dialog whose parent is myFrame and is modal:

Dialog myDialog = new Dialog(myFrame, true);      // true means model dialog

You can also create a dialog with a title:

Dialog myDialog = new Dialog(myFrame, "A Non-Modal Dialog", false);     
         // false = non-modal

Since a dialog cannot belong to an applet, your use of dialogs can be somewhat limited. One solution is to create a dummy frame as the dialog's parent. Unfortunately, you cannot create modal dialogs this way, since only the frame and its children would have their input blocked-the applet would continue on its merry way. A better solution is to use the technique discussed in the "Frames" section of this chapter in which you create a stand-alone application using frames, then have a "bootstrap" applet create a frame and run the real applet in it.

Once you have created a dialog, you can make it visible using the show method:
myDialog.show();

Dialog features

The Dialog class has several methods in common with the Frame class:

     void setResizable(boolean);
     boolean isResizable();
     void setTitle(String);
     String getTitle();

In addition, the isModal method will return true if the dialog is modal.

A Reusable OK dialog box

Listing 21.6 shows the OKDialog class which provides an OK dialog box that displays a message and waits for you to click OK:

Listing 21.6-Source code for OKDialog.java
import java.awt.*;

// Example 21.6 - OK Dialog class
//
// OKDialog - Custom dialog that presents a message and waits for 
// you to click on the OK button.
//
// Example use:
//      Dialog ok = new OKDialog(parentFrame, "Click OK to continue");
//      ok.show();      // Other input will be blocked until OK is pressed
// As a shortcut, you can use the static createOKDialog that will
// create its own frame and activate itself:
//      OKDialog.createOKDialog("Click OK to continue");
//

public class OKDialog extends Dialog
{
        protected Button okButton;
        protected static Frame createdFrame;

        public OKDialog(Frame parent, String message)
        {
                super(parent, true);    // Must call the parent's constructor
                
// This Dialog box uses the GridBagLayout to provide a pretty good layout.

                GridBagLayout gridbag = new GridBagLayout();
                GridBagConstraints constraints = new GridBagConstraints();

// Create the OK button and the message to display
                okButton = new Button("OK");
                Label messageLabel = new Label(message);

                setLayout(gridbag);

// The message should not fill, it should be centered within this area, with
// some extra padding.  The gridwidth of REMAINDER means this is the only
// thing on its row, and the gridheight of RELATIVE means there should only
// be one thing below it.
                constraints.fill = GridBagConstraints.NONE;
                constraints.anchor = GridBagConstraints.CENTER;
                constraints.ipadx = 20;
                constraints.ipady = 20;
                constraints.weightx = 1.0;
                constraints.weighty = 1.0;
                constraints.gridwidth = GridBagConstraints.REMAINDER;
                constraints.gridheight = GridBagConstraints.RELATIVE;

                gridbag.setConstraints(messageLabel, constraints);
                add(messageLabel);

// The button has no padding, no weight, taked up minimal width, and
// Is the last thing in its column.

                constraints.ipadx = 0;
                constraints.ipady = 0;
                constraints.weightx = 0.0;
                constraints.weighty = 0.0;
                constraints.gridwidth = 1;
                constraints.gridheight = GridBagConstraints.REMAINDER;

                gridbag.setConstraints(okButton, constraints);
                add(okButton);

// Pack is a special window method that makes the window take up the minimum
// space necessary to contain its components.

                pack();

        }

// The action method just waits for the OK button to be clicked and
// when it is it hides the dialog, causing the show() method to return
// back to whoever activated this dialog.

        public boolean action(Event evt, Object whichAction)
        {
                if (evt.target == okButton)
                {
                        hide();
                        if (createdFrame != null)
                        {
                                createdFrame.hide();
                        }
                }
                return true;
        }

// Shortcut to create a frame automatically, the frame is a static variable
// so all dialogs in an applet or application can use the same frame.

        public static void createOKDialog(String dialogString)
        {
// If the frame hasn't been created yet, create it
                if (createdFrame == null)
                {
                        createdFrame = new Frame("Dialog");
                }
// Create the dialog now
                OKDialog okDialog = new OKDialog(createdFrame, dialogString);

// Shrink the frame to just fit the dialog
                createdFrame.resize(okDialog.size().width,
                        okDialog.size().height);

// Show the dialog
                okDialog.show();

        }
}

The DialogApplet in listing 21.7 pops up an OK dialog whenever a button is pressed:

Listing 21.7-Source code for DialogApplet.java
import java.awt.*; 
import java.applet.*;

// DialogApplet
//
// Dialog applet creates a button, and when you press
// the button it brings up an OK dialog.  The input
// to the original button should be blocked until
// the OK button in the dialog is pressed.

public class DialogApplet extends Applet
{
        protected Button launchButton;

        public void init()
        {
                launchButton = new Button("Give me an OK");
                add(launchButton);
        }

        public boolean action(Event event, Object whichAction)
        {
// Make sure this action is for the launchButton
                if (event.target != launchButton)
                {
                        return false;
                }

// Create and display the OK dialog
                OKDialog.createOKDialog(
                        "Press OK when you are ready");

// Signal that we've handled the event
                return true;
        }
}

Figure 21.12 shows the DialogApplet with the OK dialog popped up.

Fig. 21.12
The OKDialog class creates a popup dialog box with an OK button.

Layout Managers

If you haven't noticed already, when you add components to a container you don't have to tell the container where to put a component. By using layout managers, you tell the AWT where you want your components to go relative to the other components. The layout manager figures out exactly where to put them. This helps you make platform-independent software. When you position things by absolute coordinates, it can cause a mess when someone running Windows 95 in 640[ts]480 resolution tried to run an applet designed to fit on a 1280[ts]1024 X-terminal.

The AWT provides five different types of layout managers:

Flow Layouts

A FlowLayout class treats a container as a set of rows. The height of the rows is determined by the height of the items placed in the row. The FlowLayout starts adding new components from left to right. If it cannot fit the next component onto the current row, it drops down to the next row and starts again from the left. It also tries to align the rows using either left-justification, right-justification, or centering. The default alignment for a FlowLayout is centered, which means that when it creates a row of components, it will try to keep it centered with respect to the left and right edges.

The FlowLayout layout manager is the default layout manager for all applets.

To create a FlowLayout with centered alignment and attach it to your applet:

myFlowLayout = new FlowLayout();
setLayout(myFlowLayout);

To create a FlowLayout with a left-justified alignment:

myFlowLayout = new FlowLayout(FlowLayout.LEFT);

The different types of FlowLayout alignment are FlowLayout.LEFT, FlowLayout.RIGHT, and FlowLayout.CENTER.

You may also give the FlowLayout horizontal and vertical gap values. These values specify the minimum amount of horizontal and vertical space to leave between components. These gaps are given in units of screen pixels. To create a right-justified FlowLayout with a horizontal gap of 10 pixels and a vertical gap of 5 pixels:

myFlowLayout = new FlowLayout(FlowLayout.RIGHT, 10, 5);

Figure 21.13 shows five buttons arranged in a flow layout.

Fig. 21.13
The flow layout places components from left to right.

Grid Layouts

A GridLayout class divides a container into a grid of equally-sized cells. When you add components to the container, the GridLayout places them from left to right starting in the top left cells. When you create a GridLayout class, you must tell it how many rows you want, or how many columns. If you give it a number of rows, it will compute the number of columns needed. If instead you give it a number of columns, it will compute the number of rows needed. If you add 6 components to a GridLayout with 2 rows, it will create 3 columns. The format of the GridLayout constructor is:

GridLayout(int numberOfRows, int numberOfColumns)

If you create a GridLayout with a fixed number of rows, you should use 0 for the number of columns. If you have a fixed number of columns, use 0 for the number of rows.

If you pass GridLayout non-zero values for both the number of rows and the number of columns, it will only use the number of rows. The number of columns will be computed based on the number of components and the number of rows. GridLayout(3, 4) is exactly the same as GridLayout(3, 0).

You may also specify a horizontal and vertical gap. The following code creates a GridLayout with 4 columns, a horizontal gap of 8, and a vertical gap of 10:

GridLayout myGridLayout = new GridLayout(0, 4, 8, 10);

Figure 21.14 shows five buttons arranged in a grid layout.

Fig. 22.14
The grid layout allocates equally sized areas for each component.

Border Layouts

A BorderLayout class divides a container up into five areas named "North," "South," "East," "West," and "Center." When you add components to the container, you must use a special form of the add method that includes one of these five area names. These five areas are arranged like the points on a compass. A component added to the "North" area is placed at the top of the container, while a component added to the "West" area is placed on the left side of the container. The BorderLayout class does not allow more than one component in an area. You may optionally specify a horizontal gap and a vertical gap. To create a BorderLayout without specifying a gap:

BorderLayout myBorderLayout = new BorderLayout();

To create a BorderLayout with a horizontal gap of 10 and a vertical gap of 20:

BorderLayout myBorderLayout = new BorderLayout(10, 20);

To add myButton to the "West" area of the BorderLayout:

myBorderLayout.add("West", myButton);

The BorderLayout class is very picky about how and where you add components. If you try to add a component using the regular add method (without the area name), you will not see your component. If you try to add two components to the same area, you will only see the last component added.

Listing 21.8 shows a BorderLayoutApplet that creates a BorderLayout, attaches it to the current applet, and adds some buttons to the applet:

Listing 21.8-Source code for BorderLayoutApplet.java
import java.applet.*;
import java.awt.*;

// Example 21.8 - BorderLayoutApplet
//
// This applet creates a BorderLayout and attaches it
// to the applet.  Then it creates buttons and places
// in all possible areas of the layout.

public class BorderLayoutApplet extends Applet
{
     public void init()
     {

// First create the layout and attach it to the applet

          setLayout(new BorderLayout());

// Now create some buttons and lay them out

          add("North", new Button("Larry"));
          add("South", new Button("Curly Joe"));
          add("East", new Button("Curly"));
          add("West", new Button("Shemp"));
          add("Center", new Button("Moe"));
     }
}

Figure 21.15 shows five buttons arranged in a border layout.

Fig. 22.15
The border layout places components at the north, south, east, and west compass points, as well as in the center.

Grid Bag Layouts

The GridBagLayout class, like the GridLayout, divides a container into a grid of equally-sized cells. Unlike the GridLayout, however, the GridBagLayout class decides how many rows and columns it will have, and it allows a component to occupy more than one cell if necessary. The total area that a component occupies is called its "display area." Before you add a component to a container, you must give the GridBagLayout a set of "suggestions" on where to put the component. These suggestions are in the form of a GridBagConstraints class. The GridBagConstraints class has a number of variables to control the placement of a component:

When you want to add a component to a container using a GridBagLayout, you create the component, then create an instance of GridBagConstraints and set the constraints for the component. For instance:

GridBagLayout myGridBagLayout = new GridBagLayout();
setLayout(myGridBagLayout);
         // Set the applet's Layout Manager to myGridBagLayout

Button myButton = new Button("My Button");
GridBagConstraints constraints = new GridBagConstraints();
constraints.weightx = 1.0;
constraints.gridwidth = GridBagConstraints.RELATIVE;
constraints.fill = GridBagConstraints.BOTH;

Next, you set the component's constraints in the GridBagLayout with:

myGridLayout.setConstraints(myButton, constraints);

Now you may add the component to the container:

add(myButton);

The applet in listing 21.9 uses the GridBagLayout class to arrange a few instances of CircleCanvas (created earlier in this chapter):

Listing 21.9-Source code for CircleApplet.java
import java.applet.*;
import java.awt.*;

// Example 21.9 CircleApplet
//
// This circle demonstrates the CircleCanvas class we
// created.  It also shows you how to use the GridBagLayout
// to arrange the circles.

public class CircleApplet extends Applet
{
     public void init()
     {
          GridBagLayout gridbag = new GridBagLayout();
          GridBagConstraints constraints = new GridBagConstraints();
          CircleCanvas newCircle;

          setLayout(gridbag);

// We'll use the weighting to determine relative circle sizes. Make the
// first one just have a weight of 1. Also, set fill for both directions
// so it will make the circles as big as possible.

          constraints.weightx = 1.0;
          constraints.weighty = 1.0;
          constraints.fill = GridBagConstraints.BOTH;

// Create a red circle and add it

          newCircle = new CircleCanvas(Color.red);
          gridbag.setConstraints(newCircle, constraints);
          add(newCircle);

// Now, we want to make the next circle twice as big as the previous
// one, so give it twice the weight.

          constraints.weightx = 2.0;
          constraints.weighty = 2.0;

// Create a blue circle and add it

          newCircle = new CircleCanvas(Color.blue);
          gridbag.setConstraints(newCircle, constraints);
          add(newCircle);

// We'll make the third circle the same size as the first one, so set the
// weight back down to 1.

          constraints.weightx = 1.0;
          constraints.weighty = 1.0;

// Create a green circle and add it.

          newCircle = new CircleCanvas(Color.green);
          gridbag.setConstraints(newCircle, constraints);
          add(newCircle);
          
     }
}

Figure 21.16 shows the three circle canvases from the GridBagApplet.

Fig. 21.16
The GridBagApplet creates three circle canvases.

Insets

Insets are not layout managers, but instructions to the layout manager about how much space to leave around the edges of the container. The layout manager determines the inset values for a container by calling the container's insets method. The insets method returns an instance of an Insets class, which contains the values for the top, bottom, left, and right insets. For example, if you want to leave a 20 pixel gap between the components in your applet and the applet border, you should create an insets method in your applet:

public Insets insets()
{
     return new Insets(20, 20, 20, 20);
         // Inset by 20 pixels all around
}

The constructor for the Insets class takes four inset values in the order top, left, bottom, and right.

Figure 21.17 shows what the GridBagApplet would look like if it used the above insets method. The gap between the circles is not from the Insets class, but from the fact that the circles are smaller. The gaps on the top, bottom, left, and right are created by the Insets class.

Fig. 21.17
Insets create a gap between components and the edges of their containers.
(c)Object-Oriented Thinking

Now that you have a good understanding of the various parts of the AWT, it's time to delve into a more esoteric area-object oriented design.

You may have heard all the talk about how wonderful object-oriented languages are, and that's not all hype. However, just because a language is object-oriented does not mean that the programs written in it are. Take a look again at the first applet in this chapter:

public class Button1Applet extends Applet
{
     public void init()
     {
          add(new Button("Red"));

          add(new Button("Blue"));

     }

     public boolean action(Event evt, Object whatAction)
     {
          String buttonLabel = (String) whatAction;

          if (buttonLabel == "Red")
          {
               setBackground(Color.red);
          }
          else if (buttonLabel == "Blue")
          {
               setBackground(Color.blue);
          }
          repaint();     // Make the change visible immediately
          return true;
     }
}

This applet is not a good example of object-oriented design. The main problem with the applet is that it is very authoritarian. It puts these poor buttons out there and takes all the responsibility on itself to handle the actions when the buttons are pressed. What you should strive for in object-oriented design is just the opposite. You want to create objects that handle as much as possible on their own. You want to get to the point where you create a new application just by using a set of existing objects. Your application becomes nothing more than the "glue holding the objects together." While that isn't so easy to reach, you can try to minimize the amount of work you do every time you sit down to write a program.

Another important feature of object-oriented programs is that they are easy to modify. How easy is this applet to modify? If you want to add 10 more colors what do you do? You have to add 10 more buttons, of course, then you have to go into the action method and add more else ifs to check for the other colors. That seems like a lot of work.

Try looking at the original description of the applet through object-oriented eyes. As you will recall, the applet was described as "an applet with buttons that change the background color." The key phrase there is "buttons that change the background color." You want to give that responsibility to the buttons, not heap it onto the applet. What you really want is intelligent buttons. If you had a button that changed the applet background by itself, all the applet would have to do is create buttons-it wouldn't even have an action method!

What would it take create such a button? You'll need be able to give the button a name and a color. It will also need to know what applet it belongs to in order to change the background.

When writing new classes, always ask yourself "What might I want to do with this in the future?"

Take a second to look at this new button.You were planning to have the button set the background color for an applet. Why restrict it to just an applet? All components have a setBackground method. Why not let it set the background on any component? For that matter, why just background? Why can't it set either the foreground or the background? Listing 21.10 shows a ColorButton class that changes the background color of a component:

A Better Button

Listing 21.10-Source code for ColorButton.java
import java.awt.*;

// Example 21.10 - ColorButton class

public class ColorButton extends Button
{
// Set up some values to indicate whether we want to set
// the foreground or background.

     public static final int FOREGROUND = 1;
     public static final int BACKGROUND = 2;

// Need a placeholder for which component to set the color on
     protected Component whichComponent;

// Need to remember which color to set
     protected Color color;

// Need to remember whether to set foreground or background
     protected int whichColorToChange;

// The constructor for this button will take a label, a color value
// and a whichColor flag telling whether to set foreground or
// background, and the Component it is supposed to modify.

     public ColorButton(String label, Color colorToChoose,
          int whichColor, Component comp)
     {
          super(label);     // Do the regular button creation first

          color = colorToChoose;
          whichColorToChange = whichColor;
          whichComponent = comp;
     }

     public boolean action(Event evt, Object whichAction)
     {

// See whether we are supposed to change the foreground or the background

          if (whichColorToChange == FOREGROUND)
          {
               whichComponent.setForeground(color);
          }
          else
          {
               whichComponent.setBackground(color);
          }
          whichComponent.repaint();     // redraw component

          return true;
     }
}

For completeness, the ColorButton class should also have methods to get and set the color, whichComponent, and whichColorToChange. You should consider this a standard practice for all the variables in a class that someone else might want to change.

You're probably asking "This looks like a whole lot more work than I had to do before! Why is this any better? I thought object-oriented meant less work, not more!" Think of object-oriented programming as an investment. You have to put in a little more up front, but you save a lot more later on. The next time you need a button to change the color on a component, you have the ColorButton ready to go. Now, how does using this new ColorButton affect the applet? Listing 21.11 shows the revised applet:

Listing 21.11-Source code for Button2Applet.java
import java.applet.*;
import java.awt.*;

// Example 21.11 - Button2Applet
//
// This applet uses the ColorButton to change its background color.
//

public class Button2Applet extends Applet
{
     public void init()
     {
          add(new ColorButton("Red", Color.red,
               ColorButton.BACKGROUND, this));

          add(new ColorButton("Blue", Color.blue,
               ColorButton.BACKGROUND, this));

     }
}

As promised, the applet no longer has an action method. Now look what happens if you want to add 10 more colors-you add 10 more buttons. That's it. You don't have to change the action method because there isn't one.

Using Observables

One of the most useful classes for creating modular user interfaces isn't part of the AWT at all-the Observable class. The concept of observables is borrowed from Smalltalk. In Smalltalk, an object may "express interest" in another object, meaning it would like to know when the other object changes. When building user interfaces, you might have multiple ways to change a piece of data, and changing that data might cause several different parts of the display to update. For instance, suppose you want to create a scrollbar that changes an integer value, and in turn, that integer value is displayed on some sort of graphical meter. You want the meter to update as the value is changed, but you don't want the meter to know anything about the scrollbar. If you are wondering why the meter shouldn't know about the scrollbar, what happens if you decide you don't want a scrollbar but want the number entered from a text field instead? You shouldn't have to change the meter every time you change the input source.

You would be better off creating an integer variable that is "observable." It allows other objects to express interest in it. When this integer variable changes, it notifies those interested parties (called observers) that it has changed. In the case of the graphical meter, it would be informed that the value changed and would query the integer variable for the new value and then redraw itself. This allows the meter to display the value correctly no matter what you are using to change the value.

This concept is known as "Model-View-Controller." A "model" is the non-visual part of an application. In the above example, the model is nothing more than a single integer variable. The "view" is anything that visually displays some part of the model. The graphical meter is an example of a view. The scrollbar could also be an example of a view since it updates its position whenever the integer value changes. A "controller" is any input source that modifies the view. The scrollbar in this case is also a controller (it can be both a view and a controller).

In Smalltalk, the mechanism for expressing interest in an object is built right into the Object class. Unfortunately, for whatever reason, Sun separated out the observing mechanism into a separate class. This means extra work for you since you cannot just register interest in an Integer class, you must create your own subclass of Observable. The most important methods to you in creating a subclass of Observable are setChanged and notifyObservers. The setChanged method marks the Observable as having been changed, so that when you call notifyObservers, the observers will be notified. The notifyObservers method checks to see if the changed flag has been set, and if not, it will not send any notification. The following code fragment sets the changed flag and notifies the observers of the change:

setChanged();          // Flag this observable as changed
notifyObservers();     // Tell observers about the change

The notifyObservers method can also be called with an argument:

public void notifyObservers(Object arg)

This argument can be used to pass additional information about the change, for instance, the new value. Calling notifyObservers with no argument is equivalent to calling it with an argument of null.

Observers may register interest in an Observable by calling the addObserver method. Any class that implements the Observer interface can register interest in an Observable with:

addObserver(this);     // Whatever "this" object is, it must implement Observer

Observers may deregister interest in an Observable by calling deleteObserver:

deleteObserver(this);

Listing 21.12 shows an example implementation of an ObservableInt class:

Listing 21.12-Source code for ObservableInt.java
import java.util.*;

// Example 21.11 - ObservableInt class
//
// ObservableInt - an integer Observable
//
// This class implements the Observable mechanism for
// a simple int variable.
// You can set the value with setValue(int)
// and int getValue() returns the current value.

public class ObservableInt extends Observable
{
     int value;     // The value everyone wants to observe

     public ObservableInt()
     {
          value = 0;     // By default, let value be 0
     }

     public ObservableInt(int newValue)
     {
          value = newValue;     // Allow value to be set when created
     }

     public synchronized void setValue(int newValue)
     {
//
// Check to see that this call is REALLY changing the value
//
          if (newValue != value)
          {
               value = newValue;
               setChanged();     // Mark this class as "changed"
               notifyObservers();     // Tell the observers about it
          }
     }

     public synchronized int getValue()
     {
          return value;
     }
}

The Observable class has a companion interface called Observer. Any class that wants to receive updates about a change in an observable needs to implement the Observer interface. The Observer interface consists of a single method called update that is called when an object changes. The format of update is:

public void update(Observable obs, Object arg);

Where obs is the Observable that has just changed, and arg is a value passed by the observable when it called notifyObservers. If notifyObservers is called with no arguments, arg will be null.

Listing 21.13 shows an example of a Label class that implements the Observer interface so it can be informed of changes in an integer variable and update itself with the new value:

Listing 21.13-Source code for IntLabel.java
import java.awt.*;
import java.util.*;

// Example 21.13 - IntLabel class
//
// IntLabel - a Label that displays the value of
// an ObservableInt.

public class IntLabel extends Label implements Observer
{
     private ObservableInt intValue;     // The value we're observing

     public IntLabel(ObservableInt theInt)
     {
          intValue = theInt;

// Tell intValue we're interested in it

          intValue.addObserver(this);

// Initialize the label to the current value of intValue

          setText(""+intValue.getValue());
     }

// Update will be called whenever intValue is changed, so just update
// the label text.

     public void update(Observable obs, Object arg)
     {
          setText(""+intValue.getValue());
     }
}

Now that you have a "model" object defined in the form of the ObservableInt, and a "view" in the form of the IntLabel, you can create a "controller"-the IntScrollbar. Listing 21.14 shows the implementation of IntScrollbar:

Listing 21.14-file name Source code for IntScrollbar.java
import java.awt.*;
import java.util.*;

// Example 21.14 - IntScrollbar class
//
// IntScrollbar - a Scrollbar that modifies an
// ObservableInt.  This class functions as both a
// "view" of the observable, since the position of
// the scrollbar is changed as the observable's value
// is changed, and it is a "controller" since it also
// sets the value of the observable.
//
// IntScrollbar has the same constructors as Scrollbar,
// except that in each case, there is an additional
// parameter that is the ObservableInt.
// Note:  On the constructor where you pass in the initial
// scrollbar position, the position is ignored.

public class IntScrollbar extends Scrollbar implements Observer
{
     private ObservableInt intValue;

// The bulk of this class is implementing the various
// constructors that are available in the Scrollbar class.

     public IntScrollbar(ObservableInt newValue)
     {
          super();     // Call the Scrollbar constructor
          intValue = newValue;
          intValue.addObserver(this);        // Register interest
          setValue(intValue.getValue());     // Change scrollbar position
     }

     public IntScrollbar(ObservableInt newValue, int orientation)
     {
          super(orientation);         // Call the Scrollbar constructor
          intValue = newValue;
          intValue.addObserver(this);        // Register interest
          setValue(intValue.getValue());     // Change scrollbar position
     }

     public IntScrollbar(ObservableInt newValue, int orientation,
          int value, int pageSize, int lowValue, int highValue)
     {
          super(orientation, value, pageSize, lowValue, highValue);
          intValue = newValue;
          intValue.addObserver(this);        // Register interest
          setValue(intValue.getValue());     // Change scrollbar position
     }

// The handleEvent method check with the parent class (Scrollbar) to see
// if it wants the event, if not, just assume the scrollbar value has
// changed and update the observable int with the new position.

     public boolean handleEvent(Event evt)
     {
          if (super.handleEvent(evt))
          {
               return true;     // The Scrollbar class handled it
          }
          intValue.setValue(getValue());     // Update the observable int
          return true;
     }

// update is called whenever the observable int changes its value

     public void update(Observable obs, Object arg)
     {
          setValue(intValue.getValue());
     }
}

This may look like a lot of work, but watch how easy it is to create an applet with an IntScrollbar that modifies an ObservableInt and an IntLabel that displays one. Listing 21.14 shows an implementation of an applet that uses the IntScrollbet, the ObservableInt, and the IntLabel:

Listing 21.14-Source code for ObservableApplet1.java
import java.applet.*;
import java.awt.*;

// Example 21.14 - ObservableApplet1 applet

public class ObservableApplet1 extends Applet
{
     ObservableInt myIntValue;

     public void init()
     {

// Create the Observable int to play with

          myIntValue = new ObservableInt(5);

          setLayout(new GridLayout(2, 0));

// Create an IntScrollbar that modifies the observable int

          add(new IntScrollbar(myIntValue,
               Scrollbar.HORIZONTAL,
               0, 10, 0, 100));

// Create an IntLabel that displays the observable int

          add(new IntLabel(myIntValue));
     }
}

You will notice when you run this applet that the label value changes whenever you update the scrollbar, yet the label has no knowledge of the scrollbar, and the scrollbar has no knowledge of the label.

Now, suppose you also want to allow the value to be updated from a TextField. All you need to do is create a subclass of TextField that modifies the ObservableInt. Listing 21.15 shows an implementation of an IntTextField:

Listing 21.15-Source code for IntTextField.java
import java.awt.*;
import java.util.*;

// Example 21.15 - IntTextField class
//
// IntTextField - a TextField that reads in integer values and
// updates an Observable int with the new value.  This class
// is both a "view" of the Observable int, since it displays
// its current value, and a "controller" since it updates the
// value.

public class IntTextField extends TextField implements Observer
{
     private ObservableInt intValue;

     public IntTextField(ObservableInt theInt)
     {
// Initialize the field to the current value, allow 3 input columns

          super(""+theInt.getValue(), 3);
          intValue = theInt;
          intValue.addObserver(this);     // Express interest in value
     }

// The action for the text field is called whenever someone presses "return"
// We'll try to convert the string in the field to an integer, and if
// successful, update the observable int.

     public boolean action(Event evt, Object whatAction)
     {
          Integer intStr;          // to be converted from a string

          try {     // The conversion can throw an exception
               intStr = new Integer(getText());

// If we get here, there was no exception, update the observable

               intValue.setValue(intStr.intValue());
          } catch (Exception oops) {
// We just ignore the exception
          }
          return true;
     }

// The update action is called whenever the observable int's value changes.
// We just update the text in the field with the new int value

     public void update(Observable obs, Object arg)
     {
          setText(""+intValue.getValue());
     }
}

Once you have created this class, how much code do you think you have to add to the applet? You add one line (and change GridLayout to have 3 rows). Listing 21.16 shows an implementation of an applet that uses an ObservableInt, an IntScrollbar, an IntLabel, and an IntTextField:

Listing 21.16-Source code for ObservableApplet2.java
import java.applet.*;
import java.awt.*;

// Example 21.16 - ObservableApplet2 applet

public class ObservableApplet2 extends Applet
{
     ObservableInt myIntValue;

     public void init()
     {

// Create the Observable int to play with

          myIntValue = new ObservableInt(5);

          setLayout(new GridLayout(3, 0));

// Create an IntScrollbar that modifies the observable int

          add(new IntScrollbar(myIntValue,
               Scrollbar.HORIZONTAL,
               0, 10, 0, 100));

// Create an IntLabel that displays the observable int

          add(new IntLabel(myIntValue));

// Create an IntTextField that displays and updates the observable int

          add(new IntTextField(myIntValue));
     }
}

Again, the components that modify and display the integer value have no knowledge of each other, yet whenever the value is changed, they are all updated with the new value.

QUE Home Page

For technical support for our books and software contact support@mcp.com

Copyright ©1996, Que Corporation