TOC
BACK
FORWARD
HOME

Java 1.1 Unleashed

- 14 -
The Windowing (AWT) Package

by David R. Chung revised by Christopher Burdess

IN THIS CHAPTER

  • Components
  • Events
  • Layout Management
  • Utility Classes
  • Peers


The Abstract Windowing Toolkit (AWT) package in the JDK is a library of Java classes you can use to create graphical user interfaces (GUIs). These interfaces allow users to interact with the Java application or applet in the same way they are accustomed to interacting with other applications on their native platforms.

Because Java is a multiplatform language, the AWT is not intended to implement all the features of any specific GUI operating system, merely the features common to all operating systems. Programming with the AWT is therefore a highly portable user interface solution.

The AWT classes can essentially be divided into the following three categories:

  • Components

  • Layouts

  • Utility classes

This chapter examines each of these categories and provides examples of how to incorporate AWT classes into your Java applications and applets. Additionally, this chapter presents a discussion of peer components and their advantages and limitations.

Components

Otherwise known as user interface control elements, controls, or widgets, components is the name given to a wide variety of interface elements that can be displayed to the user. Components can also be programmed to respond to interaction by the user with the mouse or keyboard. This event handling is an important part of GUI programming and is discussed later in this chapter in the "Events" section.

Most AWT components are derived from the Component class. The notable exception is the MenuComponent class and its subclasses; these are discussed later in this section.

The Component class is an abstract class that defines elements common to all components: font, color, painting, reshaping, and event handling.


NOTE: Abstract classes are classes that declare one or more methods as abstract. Abstract methods must be overridden by a subclass; abstract classes cannot be instantiated.

The Component class tree is shown in Figure 14.1. All classes belong to the java.awt package except the Applet class, which is in the java.applet package.


NOTE: A class in one package can be derived from a superclass in another package. Thus, the Applet class, in the java.applet package, is derived from the Panel class in the java.awt package. A package is not an indication of class hierarchy, merely a convenient way to bundle certain kinds of functionality. The aspect of primary functional importance of Applet objects is that they can be embedded in Web documents and be interpreted by a Web browser according to certain security rules, not that they form part of a graphical user interface to a Java program.

Figure 14.1.

The Component class tree.


Components can be further subdivided into four main functional groups:

  • Simple widgets

  • Text components

  • Containers

  • The Canvas class

This chapter also discusses menus. Although menus are not derived from the Component class and do not share some of the properties of components (such as position, width, color, and so forth), menus do have other properties similar to components--notably fonts and event handling. In any case, I will continue to refer to menu elements as components because they are evidently visible elements of the user interface. See the "Menus" section, later in this chapter, for further details.

Simple Widgets

Simple widgets are components for which the user interaction required is minimal or nonexistent and which are not containers for any other kind of component. Simple widgets include the Button, Checkbox, Choice, Label, List, and Scrollbar classes. Correlations of these components exist on most GUI operating systems. The SimpleWidgetApplet applet in Figure 14.2 shows the AWT's simple widgets on the Microsoft Windows NT 4.0 platform. The code for the SimpleWidgetApplet applet is shown in Listing 14.1 and is provided on the CD-ROM that accompanies this book.

Figure 14.2.

The SimpleWidgetApplet applet shows the AWT's simple widgets.

Listing 14.1. The SimpleWidgetApplet applet.

import java.awt.*;

public class SimpleWidgetApplet extends java.applet.Applet {

     Button button = new Button("Click me!");
     Checkbox checkbox = new Checkbox("Tick me!");
     Choice choice = new Choice();
     Label label = new Label("This just displays some text.");
     List list = new List();
     Scrollbar scrollbar = new Scrollbar();

     public void init() {
          choice.add("Item 1");
          choice.add("Item 2");
          choice.add("Item 3");

          list.add("Item 1");
          list.add("Item 2");
          list.add("Item 3");

          add(button);
          add(checkbox);
          add(choice);
          add(label);
          add(list);
          add(scrollbar);
     }
}

First of all, we create the widgets. Some of them have handy constructors that can be used to set their initial values; for example, the Button class can be passed a String in its constructor to initialize the button's label. Other components do not have useful constructors, usually because the number of values that must be passed is not known (as is the case with the Choice and List components). In this case, you use the default constructor and set its properties later. With the Choice widget in SimpleWidgetApplet, we use its add(String) method to set the strings we want to appear in the choice list. Note that when you add items to a list (and later to menus) in this way, no sorting takes place--the strings appear in the order you add them.

Then we are ready to add these components to the applet using the applet's add(Component) method. Sometimes, you can cut out a step by creating a component and adding it at the same time:

add(new Button("Hello!"));

This approach is sometimes useful for labels. However, you usually want to keep a reference to the object you have created, as we do in SimpleWidgetApplet. Keeping a reference to the object allows you to easily refer to the objects you have created in other methods in the class-- especially event-handling methods--so that you can examine and set their properties.

The functionality provided by the simple widgets is as follows:

  • The Label component, as we have discovered, is used just to display text for the user--the user can't do anything with a label.

  • The Button component is used to trigger an associated action.

  • The Checkbox component is used to represent the state of a boolean variable (true or false, on or off). Checkboxes can be associated with a particular CheckboxGroup object, either by constructing them with it or by using the setCheckboxGroup(CheckboxGroup) method. When a checkbox is part of a group, only one of the checkboxes can be selected at a time; if one is selected, the others are all deselected.

  • The Choice component is used to select one of a number of string options. Only the selected option is shown--other options are displayed in a popup window when the Choice component is clicked.

  • The List component can be used in the same way as the Choice component: to select one of a number of string options. Additionally, a List component can contain multiple selections; you can specify this either in the constructor or using the setMultipleMode(boolean) method. For a multiple-selection List component, the user can select several options at a time.

  • The Scrollbar component is used to represent a bounded numeric value. Normally, this value is itself used to represent the amount of a component that is visible in a container. See the discussion about the ScrollPane class in the "Containers" section, later in this chapter, for details.

Text Components

You can add to your Java applications and applets two AWT components into which the user can enter text with the keyboard. These text components are TextField and TextArea; they are both subclasses of TextComponent (refer back to Figure 14.1).

The TextComponent class is an abstract base class that encapsulates much that is common to the two components. The TextField class is a simple one-line text editing component; the TextArea class supports multiple lines and vertical scrolling. Both components can be used to display and let the user specify string values. An example is shown in Figure 14.3. The code for the TextComponentApplet applet is given in Listing 14.2 and on the CD-ROM that accompanies this book.

Figure 14.3.

The TextComponentApplet applet.

Listing 14.2. The TextComponentApplet applet.

import java.awt.*;

public class TextComponentApplet extends java.applet.Applet {
     TextField textfield = new TextField("This is a TextField");
     TextArea textarea = new TextArea("This is a TextArea\nThis is the second line.");

     public void init() {
          add(textfield);
          add(textarea);
     }
}

TIP: As you can see from this example, text components have handy constructors you can use to specify the strings to be displayed initially. You can also set other properties of text components: Both TextField and TextArea allow you to specify the number of (character) columns in the component; the TextArea component also allows you to set the number of rows (lines) in the component.

Containers

A container is a component that can hold other components. All containers in the AWT are derived from the Container abstract class (refer back to Figure 14.1). The main containers are Panel, ScrollPane, and Window.

Panels are normally used to group components into specific areas of the display. Because every component can be given a separate layout, panels are useful if you want to position components according to a different layout than what was used by its parent container (usually either some form of Window or an Applet). An Applet is a kind of panel with additional properties relating to browser implementations; the Applet class in the java.applet package is the only AWT-derived class that is not in the java.awt package or one of its subpackages.

Scroll panes are a form of panel. You can add a child component to the ScrollPane container that may have dimensions greater than the ScrollPane container itself. When this is the case, the scroll pane provides horizontal and/or vertical scrollbars that allow the user to display different areas of the child component.

The Window class encapsulates a top-level window. By itself, the Window class represents a window with no title bar or border. Subclasses of the Window class provide titles, icon images, and window user control functions such as close commands and minimize/maximize capabilities. These subclasses are Frame and Dialog.


TIP: You can use the Window class as a base superclass for popup windows and components, such as ToolTips.

The Frame class provides all the functionality of an application window in the native operating system--with the notable exception that you cannot access the operating system's handle or PID for the window (or other windows in the environment). Frames are the basis for the majority of Java GUI applications. Because frames also implement the MenuContainer interface, they can possess a menu bar (see "Menus," later in this chapter).

The Dialog class represents a window that normally appears for a short time to display or request transient information. If some information is needed for a step in a computation (for example, This file has been edited. Do you want to save it?) but is not required after that step, you should use a dialog box. In the same way, if your application must notify the user that some step has been carried out and requires feedback from the user indicating that the user has understood the notification, you should use a dialog box. A Dialog object can be declared modal either by constructing it as such or by calling setModal(true). A modal dialog box is one that does not allow user interaction with its parent frame until the dialog box has been dismissed.

The FileDialog class represents a special kind of dialog box that prompts the user for a path or filename. Because paths and filenames are specified differently under different operating systems, this class is used to remove the associated difficulty of catering for all possible specifications.

Note that all windows are initially invisible when they are created. To make a window you have created visible, you must invoke its show() method. Normally, you do this after you have added all the components to it (unless, of course, you are developing dynamic components).

For an example of how frames and dialog boxes work, look at the DialogFrame application detailed in "Events," later in this chapter.

Menus

Menu components, as mentioned earlier in this chapter, are not derived from the Component class. Menu components all derive from the abstract MenuComponent class. Figure 14.4 shows the MenuComponent hierarchy.

Figure 14.4.

The MenuComponent class tree.

The MenuBar class encapsulates the concept of a menu attached to a Frame. You can set the MenuBar for a Frame by invoking its setMenuBar(MenuBar) method. The MenuFrame application shown in Figure 14.5 and Listing 14.3 (and provided on the CD-ROM that accompanies this book) demonstrates how to do this.

Figure 14.5.

The MenuFrame application.

Listing 14.3. The MenuFrame application.

import java.awt.*;

public class MenuFrame extends Frame {
     MenuItem fileNew = new MenuItem("New");
     MenuItem fileOpen = new MenuItem("Open...");
     MenuItem fileSave = new MenuItem("Save");
     MenuItem fileExit = new MenuItem("Exit");
     MenuItem editUndo = new MenuItem("Undo");
     MenuItem editCut = new MenuItem("Cut");
     MenuItem editCopy = new MenuItem("Copy");
     MenuItem editPaste = new MenuItem("Paste");
     MenuItem helpContents = new MenuItem("Contents");
     MenuItem helpAbout = new MenuItem("About MenuFrame...");

     public MenuFrame() {
          super("Menu example");

          MenuBar menubar = new MenuBar();
          Menu fileMenu = new Menu("File");
          Menu editMenu = new Menu("Edit");
          Menu helpMenu = new Menu("Help");

          fileMenu.add(fileNew);
          fileMenu.add(fileOpen);
          fileSave.setEnabled(false);
          fileMenu.add(fileSave);
          fileMenu.addSeparator();
          fileMenu.add(fileExit);

          editUndo.setEnabled(false);
          editMenu.add(editUndo);
          editMenu.addSeparator();
          editCut.setEnabled(false);
          editMenu.add(editCut);
          editCopy.setEnabled(false);
          editMenu.add(editCopy);
          editPaste.setEnabled(false);
          editMenu.add(editPaste);

          helpMenu.add(helpContents);
          helpMenu.addSeparator();
          helpMenu.add(helpAbout);

          menubar.add(fileMenu);
          menubar.add(editMenu);
          menubar.add(helpMenu);
          menubar.setHelpMenu(helpMenu);

          setMenuBar(menubar);
          setSize(new Dimension(400, 300));
          show();
     }

     public static void main(String[] args) {
          MenuFrame me = new MenuFrame();
     }
}

As well as specifying the label to appear on menu items, the MenuItem class also allows you to construct the menu with a hotkey that can activate the specific MenuItem.

As you may have noticed, the code in Listing 14.3 disables menu items that are not applicable. In this small example, it is redundant to do so because there will never be a time when the MenuFrame object has any content that must be saved or edited. The menu items were disabled only for purposes of explanation. Furthermore, nothing actually happens when the menu items are selected because we have not yet added any event-handling functionality to the application. To see an example of an application that responds to user interaction, see the "Events" section, later in this chapter.

Popup Menus

Popup menus, used to provide contextual functions for the underlying object of a referential component, have become very common on platforms such as Windows 95, NT 4.0, and CDE. The base class for creating popup menus is PopupMenu, derived from MenuComponent. You can compose a popup menu with this class and display it relative to the referential component by means of its show(Component, int, int) method. The other two variables in the show() method are the x and y coordinates (in pixels) relative to the specified component. You must add the popup menu to a valid component first, which may or may not be the component from which you specify its position.

One of the issues relating to popup menus is the event used to trigger the menu; this event differs from platform to platform. For example, in Motif, the menu pops up on a mouse-down event; in Windows, the menu appears on a mouse-up event. The MouseEvent class has a special isPopupTrigger() function to deal with this problem.

The Canvas Class and Graphics Contexts

The Canvas class is a generic component class that can be used to implement graphics methods. By itself, the Canvas class appears similar to a Panel class but it is not a container and you cannot add other components to it. Essentially, the Canvas class is just an instantiable Component class. Canvases are subclassed extensively to display images, draw graphs, and create custom components such as progress bars, tabbed folders, and custom buttons. This is achieved by overriding the paint(Graphics) method. With the provided Graphics context, you can invoke graphics methods to draw points, lines, text, and images into the canvas.

The Graphics object provides an encapsulation of the state information required for the various rendering methods Java uses, such as the current color, font, and rendering translation origin. Coordinates passed to Graphics methods are considered relative to this origin; rendering methods invoked on the Graphics object only modify pixels within the bounds of its clipping rectangle. Some of the more useful methods provided by this object include drawImage(), which is used to render an Image into the graphics context; drawLine(), which is used to draw lines; drawOval(), which is used to draw ellipses and circles; drawString(), which is used to draw text; fillRect(), which is used to draw filled rectangles; getFontMetrics(), which retrieves a FontMetrics object containing information about the current font, which can then be used to calculate where to draw text; and setColor() and setFont(), which are used to select colors and fonts for drawing. For more information about fonts, colors, and images, refer to "Utility Classes," later in this chapter.

The ImageButton class demonstrates how to subclass the Canvas class to draw a button that can display an image and text. Figure 14.6 shows an ImageButton object added to a Frame object on the Windows NT 4.0 platform. Listing 14.4 shows the code to accomplish this feat (the code is also provided on the CD-ROM that accompanies this book).

Figure 14.6.

The ImageButton component displayed in a frame.

Listing 14.4. The ImageButton class.

import java.awt.*;

public class ImageButton extends Canvas {

     private String label;
     private Image image;

     public ImageButton() {
          this(null, null);
     }

     public ImageButton(String label) {
          this(label, null);
     }

     public ImageButton(Image image) {
          this(null, image);
     }

     public ImageButton(String label, Image image) {
          this.label = label;
          this.image = image;
     }

     public String getLabel() {
          return label;
     }

     public Image getImage() {
          return image;
     }

     public void setLabel(String label) {
          if(!label.equals(this.label)) {
               this.label = label;
               repaint();
          }
     }

     public void setImage(Image image) {
          if(!image.equals(this.image)) {
               this.image = image;
               repaint();
          }
     }

     public void paint(Graphics g) {
          Color b = getBackground();
          Font f = g.getFont();
          Rectangle r = g.getClipBounds();

          super.paint(g);

          // Draw the image.
          // Scale it so it fits in the clip rectangle and centre it vertically.
          if(image != null && r.width>4 && r.height>4) {
               Dimension d = new Dimension(image.getWidth(this), image.getHeight(this));
               float scale = 1;
               float fdwidth = (new Integer(d.width)).floatValue();
               float frwidth = (new Integer(r.width)).floatValue();
               float fdheight = (new Integer(d.height)).floatValue();
               float frheight = (new Integer(r.height)).floatValue();
               if((fdwidth*scale) > (frwidth*scale))
                    scale = scale * (frwidth/fdwidth);
               if((fdheight*scale) > (frheight*scale))
                    scale = scale * (frheight/fdheight);
               int ndwidth = (new Float(fdwidth * scale)).intValue() - 4;
               int ndheight = (new Float(fdheight * scale)).intValue() - 4;
               int yOffset = (r.height - ndheight) / 2;
               g.drawImage(image, 2, yOffset, ndwidth, ndheight, this);
          }

          // Draw the label.
          // Centre it in the clip rectangle.
          if(label!=null) {
               FontMetrics fm = g.getFontMetrics();
               g.drawString(label, (r.width - fm.stringWidth(label)) / 2, (r.height/2) + (fm.getAscent()/2));
          }

          // Draw the button bevel.
          // We need to draw the lines twice to make them thicker.
          g.setColor(b.brighter());
          g.drawLine(0, r.height-1, 0, 0);
          g.drawLine(1, r.height-2, 1, 1);
          g.drawLine(0, 0, r.width-1, 0);
          g.drawLine(1, 1, r.width-2, 1);
          g.setColor(b.darker());
          g.drawLine(r.width-1, 0, r.width-1, r.height-1);
          g.drawLine(r.width-2, 1, r.width-2, r.height-2);
          g.drawLine(r.width-1, r.height-1, 0, r.height-1);
          g.drawLine(r.width-2, r.height-2, 1, r.height-2);
     }
}

You may have noticed that in the paint() routine, the variables height and width refer to the height and width of the ImageButton. These numbers are 1 greater than the index of the last pixel in the clip rectangle; the pixels are numbered 0 to width-1 and 0 to height-1, in the same way that the length of an array is 1 greater than the index of the last element of the array.

The ImageButton class is far from encapsulating a real button that can be clicked to trigger events. To make it a clickable button, you must define a boolean instance variable (for example, down) that is flagged when the button receives a mouse-down event and unflagged when it receives a mouse-up event. The paint() routine can then examine the down variable and draw the button appropriately. For example, you may want to switch the g.setColor() calls in the bevel-drawing routine to reverse the bevel; you may also want to offset the image and text to the right and down by some factor to give the user some feedback that the button has been clicked. In Windows, button backgrounds are dithered when they are clicked; you may want to implement this feature.

To bring some life to the ImageButton object, it must handle events. Event handling is discussed next.

Events

Event handling is one of the largest concerns for GUI developers. The other is layout management, which is dealt with in "Layout Management," later in this chapter. Fortunately, the AWT makes event handling relatively easy to learn and implement. Before the JDK 1.1 API, an inheritance event model was used, which was found to have considerable overhead and was cumbersome to deploy. In the current version of the JDK, the AWT uses a delegation event model, which is much more robust and flexible and allows for a greater degree of compile-time checking.

When the user initiates some event, such as clicking the mouse or typing at the keyboard, the event is generated and made available to your Java application or applet. The following sections describe how to catch these events and process them.

Listeners and Adapters

An event is propagated from a source object (component) to a "listener" object. The source object is the GUI object the user clicked on, typed text into, or otherwise interacted with. Listeners are objects that register themselves as interested in certain kinds of events on that source. Listeners can be other GUI objects or separate objects responsible for delegated events; in the latter case, you can write event-handling code that separates the GUI elements from their functionality. In the AWT, events that are passed are typically derived from the AWTEvent class.

The AWT provides for two conceptually different kinds of events: low-level and semantic. Low-level events are concerned with the specific user input or window system-level event occurrences. Semantic events are concerned not with the specifics of what interaction occurred, but with its meaning. For example, when you press the Enter key after typing something into a text field, you want to perform some action (such as validating the contents of the text field). In the same way, when you click a button or double-click an item in a list, you want to perform an action. However, if you click a scrollbar (or drag its marker), you want to adjust the value of something. You can see that the low-level event (the click with the mouse) does not perform the same role for different components; this is why you need semantic events.

Low-level events and semantic events are both subclassed from the AWTEvent class, which is subclassed from java.util.EventObject. AWTEvent is in the java.awt package. All other events discussed here are in the java.awt.event package. Figure 14.7 shows the class tree for low-level events.

Figure 14.7.

The class tree for low-level events.

There is, in fact, one more event, the (very) low-level PaintEvent, which is normally used only internally; it is not designed to be used with the listener model. Figure 14.8 shows the corresponding class tree for the semantic events.

Figure 14.8.

The class tree for semantic events.

The ActionEvent encapsulates our notion of "doing something" or "performing an action." The other two events are more specific: The AdjustmentEvent is representative of a numeric variable being adjusted; the ItemEvent indicates that the state of an item has changed.

According to this model, all your class has to do is to implement a certain listener interface and get itself registered with the source object. However, listeners for low-level events are designed to listen to multiple event types (the WindowListener, for example, listens to window activation, closed, closing, deactivation, deiconification, iconification, and opened events); if your class implements such an interface, you must supply the methods that handle these events.


TIP: To make things simpler, the AWT event model includes adapter classes for low-level event listeners. These classes, located in the java.awt.event package, provide default implementations of all the methods so that you can choose which methods to override in your code. Because the body of the adapter method is empty, using adapters is equivalent to using the listener interfaces directly except that you don't have to specify every method in the listener interface.

Figure 14.9 shows the low-level event listener and adapter hierarchy; Figure 14.10 shows the semantic event listener interfaces. All listeners inherit the java.util.EventListener interface and are contained in the java.awt.event package.

Figure 14.9.

Low-level event listeners and adapters.

Figure 14.10.

Semantic event listeners.

The Event Queue

The AWT provides a system event queue, which is represented by the java.awt.EventQueue class. The EventQueue class provides a static method to return the current system event queue: getEventQueue(). You can use the event queue to get sneak previews of events destined for objects in the system, and, most importantly, you can push new events onto the queue.

There are security issues involved here: Although applications can make free use of the event queue, allowing untrusted applets to freely manipulate events can have serious implications. Thus, the getEventQueue() method is protected by a SecurityManager check that does not allow untrusted applets direct access to the queue. Instead, appropriate Applet methods for peeking at the queue exist. These methods are restricted so that applets can only access events contained on components within their own containment hierarchy.

Event Handling

The DialogFrame application in Listing 14.5 (and included on the CD-ROM that accompanies this book) demonstrates the use of the various event objects and methods, as well as how the Frame and Dialog classes work.

Listing 14.5. The DialogFrame application.

import java.awt.*;
import java.awt.event.*;

public class DialogFrame extends Frame implements ActionListener, WindowListener {

     Dialog dialog;
     Button openDialogButton;
     Button closeDialogButton;
     Button closeFrameButton;

     public DialogFrame() {
          super("Dialog and Frame example");
          setLayout(new FlowLayout());
          addWindowListener(this);

          openDialogButton = new Button("Open dialog");
          openDialogButton.addActionListener(this);

          closeFrameButton = new Button("Close me");
          closeFrameButton.addActionListener(this);

          add(openDialogButton);
          add(closeFrameButton);

          pack();
          show();
     }

     public void showDialog() {
          dialog = new Dialog(this, "This is the dialog", true);
          dialog.setLayout(new FlowLayout());
          dialog.addWindowListener(this);

          closeDialogButton = new Button("Close dialog");
          closeDialogButton.addActionListener(this);

          dialog.add(closeDialogButton);

          dialog.pack();
          dialog.show();
     }

     public void actionPerformed(ActionEvent e) {
          String buttonCommand = e.getActionCommand();
          if(buttonCommand.equals("Open dialog"))
               showDialog();
          else if(buttonCommand.equals("Close dialog"))
               dialog.dispose();
          else if(buttonCommand.equals("Close me"))
               processEvent(new WindowEvent(this, WindowEvent.WINDOW_CLOSING));
     }

     public void windowClosing(WindowEvent e) {
          Window originator = e.getWindow();
          if(originator.equals(this)) {
               this.dispose();
               System.exit(0);
          } else if(originator.equals(dialog))
               dialog.dispose();
     }

     public void windowActivated(WindowEvent e) { }
     public void windowDeactivated(WindowEvent e) { }
     public void windowDeiconified(WindowEvent e) { }
     public void windowClosed(WindowEvent e) { }
     public void windowIconified(WindowEvent e) { }
     public void windowOpened(WindowEvent e) { }

     public static void main(String[] args) {
          DialogFrame me = new DialogFrame();
     }
}

First, we ensure that the DialogFrame class can receive events from the sources it is interested in. And it is interested in the clicking of buttons (which are semantic events) and window events (so that it can intercept low-level window closing events--otherwise, the user cannot terminate the application by closing the window). Thus, DialogFrame implements the ActionListener and WindowListener interfaces and provides methods to handle the events it receives as a result.

When DialogFrame is constructed, it first calls the superconstructor in the Frame class with a string argument that sets the specified string to be the window title. The next line sets the layout (as described in "Layout Management," later in this chapter). The next method, addWindowListener(WindowListener), registers the DialogFrame with itself for the purposes of receiving window events. Then the code creates the buttons that go on the frame and registers itself as an ActionListener for action events on those buttons. Because windows are always created invisible, the code then calls show() to display the frame.

The showDialog() function constructs a modal dialog box and registers DialogFrame as a window listener for it. The method also creates a button on the dialog box and registers DialogFrame as an action listener for the button.

Now we implement the event-handling methods for DialogFrame. First, we implement the actionPerformed(ActionEvent) method for the button clicks. We work out which button has been clicked and perform the relevant action for it. In this example, we use the ActionEvent's getActionCommand() method to return the button label. Alternatively, we could have used ActionEvent's getSource() method to return the actual source object and then compared it with the button references we have (openDialogButton and so on). The openDialogButton click calls showDialog() to show the dialog box; the closeDialogButton click calls dispose() on the dialog box, which hides it and removes any excess system resources used to manage the window. The closeFrameButton click, however, does not close the frame directly; it posts a new window-closing event to the frame. This event is picked up by DialogFrame in its role as window listener and is processed as if it is a request to close the window (which, effectively, it is). Why would we bother to go through this tortuous arrangement of posting events to ourselves in this way, when disposing of the window and calling System.exit() is easy and straight-forward? In this case, that is all the processing to be done by the window listener's closing routine, but when you develop more complex applications, you will find that you must frequently do more processing before the application can exit successfully. If you start off duplicating code in different methods, and you have to change the code in some way at a later date, you must always consider all the other code snippets that are supposed to be providing the same functionality. Many experienced programmers write special methods such as closeAndExit(), which is called by any routine that wants to close down the application, thereby ensuring that there is only one way that the system can exit.


TIP: Because DialogFrame is an application and not an applet, instead of calling processEvent(), we could easily have requested a handle to the system event queue and posted the window-closing event there:

EventQueue.getEventQueue().postEvent(new WindowEvent(this,
WindowEvent.WINDOW_CLOSING));


However, because processEvent() causes the event to be handled immediately, this method is usually more useful.

Next, we must override the relevant methods in WindowListener to handle window events. We compare the source window with our references to DialogFrame and its child dialog box and then dispose of the windows accordingly. We must also provide empty implementations of the other WindowListener methods because we are not using a separate adapter object.

Note that because this is an application and not an applet, we must provide a main() method with the following form:

public static void main(String[])

In this form, the String array parameter references any command-line parameters passed to the application. As soon as we have compiled this class, we can run it with a Java interpreter from the command line, like this:

java DialogFrame

The Focus and Keyboard Events

The focus is a term used to describe which component is to receive keyboard events. Because numerous components, each potentially capable of handling keyboard events, may be visible in the same window, the focus is used to determine the destination of the keyboard events. For example, if you want to receive keyboard events on a Frame or Canvas object, the component must first request the focus using the requestFocus() method.

The AWT provides for mouseless focus traversal, which is implemented by pressing the Tab key (to move forwards) or Shift+Tab (to move backwards). Some components (those based on peers in the underlying platform) have this ability innately. For example, if you develop new components derived from Canvas that are destined to receive keyboard events, you should override the isTabbable() method to return true and catch the mouse-down event on the component to call requestFocus(). Most user-interface component designers also implement additional drawing routines when the component has the focus; a button, for example, may have a dotted line around the inside of the bevel.

Clipboard Functions

Most GUI users expect to be able to transfer data such as text and images between applications using the clipboard functions cut, copy, and paste. The API for transferable objects is based around the java.awt.datatransfer.Transferable interface. Objects that implement this interface must provide a list of "flavors" or formats in which it can provide the actual data. The java.awt.datatransfer package provides one convenient class for the most common data type: the StringSelection class implements the Transferable interface.

The Clipboard class encapsulates a clipboard in Java. You are free to create as many of these objects as you like for private purposes. One clipboard object, however, called System, is used to interface with the system clipboard and hence nonJava applications. You can retrieve the System clipboard with a call to getSystemClipboard() on a valid Toolkit object. To write data to a Clipboard object, you must implement the ClipboardOwner interface. Transfer occurs with two methods: To write data to a clipboard, you use the Clipboard method setContents(Transferable, ClipboardOwner); to read from the clipboard, use the getContents(Object) method. The ClipboardOwner and Object parameters refer to the calling object; the Transferable objects involved are the clipboard data. When you read from the clipboard, you must also request a list of available flavors from the Transferable object and read the data in the desired flavor with the Transferable object's getTransferData() method.

Layout Management

So far in this chapter, the examples have just been adding components to containers without any indication of how those components are to be arranged in the display. The AWT provides a group of classes known as layout managers, or layouts, that handle this kind of placement. All layouts implement the LayoutManager interface. Once you set a layout for a container using the setLayout(LayoutManager) method, and then add a component to the container, you also add the component to the container's layout. The layouts provided with the AWT library are discussed in the following sections.

The FlowLayout Manager

The FlowLayout manager sets out the components in rows. This layout is most useful for implementing button toolbars and the like. The FlowLayout manager fits as many components as it can on one row before populating the row below. The order of the components in the row is determined by the order in which they are added to the container. FlowLayout is the default layout for the Panel class, so applets also use this layout manager if none is specified.

You can specify the spacing between components in a FlowLayout by using the setHgap() and setVgap() methods or by constructing it with these values (the default is 5 pixels for both horizontal and vertical gaps). The FlowLayout manager respects a component's Insets, so these are added to the total gap between components if they are specified.


TIP: You can also specify the alignment of the flow (which side it aligns the components against) either in the constructor or with the setAlignment() method.

The ToolbarApplet shown in Figure 14.11 and Listing 14.6 (and included on the CD-ROM that accompanies this book) displays a set of buttons positioned with a FlowLayout manager with RIGHT alignment.

Figure 14.11.

The ToolbarApplet applet.


Listing 14.6. The ToolbarApplet applet.

import java.awt.*;

public class ToolbarApplet extends java.applet.Applet {

     public void init() {
          setLayout(new FlowLayout(FlowLayout.RIGHT));
          add(new Button("Button 1"));
          add(new Button("Button 2"));
          add(new Button("Button 3"));
          add(new Button("Button 4"));
          add(new Button("Button 5"));
     }
}

The BorderLayout Manager

The BorderLayout manager is a simple layout that places controls so that they fill their container. When you add a component to a container that has a BorderLayout layout, you can also specify one of five constraints in the form of a string: "North", "East", "South", "West", and "Center". The first four constraints specify which edge of the container the component will be attached to (north, east, south, or west). The "Center" constraint places the component in the center of the container. If you do not specify a constraint, the default constraint is "Center". A maximum of five components can be displayed in the container, although you can create more complex arrangements by embedding containers within one another. The BorderLayout manager is the default layout for the Window class, so frames and dialog boxes use the BorderLayout manager if none is specified.


TIP: You can specify horizontal and vertical gaps with the BorderLayout manager in the same way you can with the FlowLayout manager.

Figure 14.12 shows how components are laid out with the BorderLayout manager; Listing 14.7 shows the code used to accomplish this. This code is also included on the CD-ROM that accompanies this book.

Figure 14.12.

The BorderButtonApplet applet.

Listing 14.7. The BorderButtonApplet applet.

import java.awt.*;

public class BorderButtonApplet extends java.applet.Applet {

     public void init() {
          setLayout(new BorderLayout());
          add(new Button("North"), "North");
          add(new Button("East"), "East");
          add(new Button("South"), "South");
          add(new Button("West"), "West");
          add(new Button("Centre"));
     }
}

The CardLayout Manager

The CardLayout manager is an interesting layout in that it does not attempt to resize components to display them all in the container. Instead, it displays them one at a time in a Rolodex fashion. Listing 14.8 shows the ThreePagesApplet applet (the applet is also located on the CD-ROM that accompanies this book); Figure 14.13 shows this CardLayout container displaying its third control, a panel with some buttons and text.

Figure 14.13.

The ThreePagesApplet applet.

Listing 14.8. The ThreePagesApplet applet.

import java.awt.*;
import java.awt.event.*;

public class ThreePagesApplet extends java.applet.Applet implements MouseListener {

     Button page1Button;
     Label page2Label;
     TextArea page3Text;
     Button page3Top;
     Button page3Bottom;
     CardLayout layout;

     public void init() {
          setLayout(layout = new CardLayout());

          add(page1Button = new Button("Button page"), "page1Button");
          page1Button.addMouseListener(this);

          add(page2Label = new Label("Label page"), "page2Label");
          page2Label.addMouseListener(this);

          Panel panel = new Panel();
          panel.setLayout(new BorderLayout());

          panel.add(page3Text = new TextArea("Composite page"));
          page3Text.addMouseListener(this);

          panel.add(page3Top = new Button("Top button"), "North");
          page3Top.addMouseListener(this);

          panel.add(page3Bottom = new Button("Bottom button"), "South");
          page3Bottom.addMouseListener(this);

          add(panel, "panel");
     }

     public void mouseClicked(MouseEvent e) {
          layout.next(this);
     }

     public void mouseEntered(MouseEvent e) { }
     public void mouseExited(MouseEvent e) { }
     public void mousePressed(MouseEvent e) { }
     public void mouseReleased(MouseEvent e) { }

}

When the user clicks any of the components in the applet, the ThreePagesApplet shows the next component in its layout. This is done by calling the next() method on the layout. The position methods for the CardLayout are listed here:

  • first(Container)

  • next(Container)

  • previous(Container)

  • last(Container)

These methods move to the first, next, previous, and last components in the layout, respectively.


TIP: All these methods require a container to be specified, because you can use the same layout manager object for more than one container at a time. Normally (unless you have a good reason not to), you create a separate layout manager for each container. Doing so allows you to modularize your code more easily. If, however, you want to minimize the total number of objects in a large application, sharing a layout manager between containers may be one of your solutions.

When components are added to the container, you specify a string that the CardLayout manager uses to identify the control. Instead of calling position methods such as next() and first(), you can also jump to the component with the specified label by calling the show(Container, String) method on the layout. For example, if you want to jump to the second page in the ThreePagesApplet, you can use this call:

layout.show(this, "page2Label");

The GridLayout Manager

The GridLayout manager is used to lay out components in a grid of evenly spaced cells. The default constructor creates a grid with one column per component, but you can use other constructors to specify the exact number of rows and columns you need as well as the spacing between the cells. As you can see in Figure 14.14, the GridLayout manager populates its cells from left to right and top to bottom. Listing 14.9 shows the code used to create this applet. This applet is also located on the CD-ROM that accompanies this book.

Figure 14.14.

The GridButtonApplet applet.

Listing 14.9. The GridButtonApplet applet.

import java.awt.*;

public class GridButtonApplet extends java.applet.Applet {

     public void init() {
          setLayout(new GridLayout(3, 3));
          add(new Button("Cell 1"));
          add(new Button("Cell 2"));
          add(new Button("Cell 3"));
          add(new Button("Cell 4"));
          add(new Button("Cell 5"));
          add(new Button("Cell 6"));
          add(new Button("Cell 7"));
          add(new Button("Cell 8"));
          add(new Button("Cell 9"));
     }
}

The GridBagLayout Manager

The GridBagLayout manager is the most complex and highly flexible layout manager in the AWT. Once you get used to it, you will use it all the time to develop form-like interfaces. However, it is difficult to learn and has many options.

The basis of the GridBagLayout manager is a grid. Unlike the GridLayout manager, components in the GridBagLayout manager are not constrained to a single, regular cell. Each cell has a horizontal and vertical weight, which is used to determine the proportion of the space it takes up as a double-precision floating-point value between 0 (none of the available space) and 1 (all the available space). Also, each component can take up more than one cell. A number of other factors control the placement and sizing of components in a GridBagLayout manager; any or all of these factors are specified to the layout by means of a GridBagConstraints object, which is added to the layout at the same time as the component, and which specifies the factors for that component. The GridBagConstraints class contains the following data members:

  • gridx, gridy: These variables specify the cell coordinates for the northwest corner of the component. You can use the default value GridBagConstraints.RELATIVE to specify a horizontal or vertical offset of 1 from the cell of the most recently added component.

  • gridwidth, gridheight: These variables specify the number of horizontal and vertical cells the component is to take up. You can use GridBagConstraints.REMAINDER to take up all the remaining cells in a row or column.

  • fill: If the component is smaller than its cell(s), this variable determines whether (and if so, how) to resize the component to fit into the available area. Valid arguments are GridBagConstraints.NONE (don't resize), GridBagConstraints.HORIZONTAL (make use of all the width), GridBagConstraints.VERTICAL (make use of all the height), and GridBagConstraints.BOTH (make use of all the space).

  • anchor: If the component is smaller than its cell(s), this variable determines what corner or edge of the cell area to attach the component to. Valid arguments are GridBagConstraints.CENTER (the default), GridBagConstraints.NORTH, GridBagConstraints.EAST, GridBagConstraints.SOUTH, GridBagConstraints.WEST, GridBagConstraints.NORTHWEST, GridBagConstraints.NORTHEAST, GridBagConstraints.SOUTHEAST, and GridBagConstraints.SOUTHWEST.

  • weightx, weighty: These variables are used to determine the weight of a cell, or to what extent excess space is added to the row or column. If you do not specify the weight of at least one row and column, the components are laid out in the center because specifying a weight of 0 (the default) adds any extra space at the edges.

  • Insets: The Insets object specifies the distance in pixels between the edges of the component and the edges of its cell(s).

  • ipadx, ipady:These variables control the horizontal and vertical padding between the component's edge and that of its cell(s).

CAUTION: Although the variables ipadx and ipady are still supported in GridBagConstraints and by the layout manager, they are essentially superseded by the Insets variable and their use is no longer recommended.

To add a component to a GridBagLayout manager, you must first set the data members just listed for a GridBagConstraints object and then add the component and the constraints to the container. The GridBagSimpleWidgetApplet in Listing 14.10 is an example of using a GridBagLayout manager to control the layout of the components created in SimpleWidgetApplet, earlier in this chapter (see Figure 14.15). GridBagSimpleWidgetApplet is also included on the CD-ROM that accompanies this book.

Figure 14.15.

The GridBag- SimpleWidgetApplet applet.

Listing 14.10. The GridBagSimpleWidgetApplet applet.

import java.awt.*;

public class GridBagSimpleWidgetApplet extends java.applet.Applet {

     public void init() {

          GridBagConstraints gbc;
          setLayout(new GridBagLayout());

          Button button = new Button("Click me!");
          Checkbox checkbox = new Checkbox("Tick me!");
          Choice choice = new Choice();
          Label label = new Label("This just displays some text.");
          List list = new List();
          Scrollbar scrollbar = new Scrollbar();

          choice.add("Item 1");
          choice.add("Item 2");
          choice.add("Item 3");

          list.add("Item 1");
          list.add("Item 2");
          list.add("Item 3");

          gbc = new GridBagConstraints();
          gbc.gridx = gbc.gridy = 0;
          gbc.anchor = GridBagConstraints.SOUTH;
          gbc.weightx = gbc.weighty = 1.0;
          add(label, gbc);

          gbc = new GridBagConstraints();
          gbc.gridx = 0;
          gbc.fill = GridBagConstraints.BOTH;
          gbc.gridwidth = 2;
          gbc.gridheight = 3;
          gbc.weightx = 1.0;
          gbc.weighty = 3.0;
          add(list, gbc);

          gbc = new GridBagConstraints();
          gbc.gridy = 0;
          gbc.fill = GridBagConstraints.HORIZONTAL;
          gbc.anchor = GridBagConstraints.NORTH;
          gbc.weightx = gbc.weighty = 1.0;
          add(choice, gbc);

          gbc = new GridBagConstraints();
          gbc.gridy = 0;
          gbc.weightx = gbc.weighty = 1.0;
          add(button, gbc);

          gbc = new GridBagConstraints();
          gbc.gridy = 2;
          gbc.anchor = GridBagConstraints.NORTHEAST;
          gbc.weightx = gbc.weighty = 1.0;
          add(checkbox, gbc);

          gbc = new GridBagConstraints();
          gbc.gridy = 0;
          gbc.gridheight = GridBagConstraints.REMAINDER;
          gbc.fill = GridBagConstraints.VERTICAL;
          gbc.anchor = GridBagConstraints.EAST;
          gbc.weightx = gbc.weighty = 1.0;
          add(scrollbar, gbc);

     }

}

Custom Layouts

If you still can't get the required layout behavior for your container, you can write your own layout manager. You must implement either the LayoutManager or LayoutManager2 interface and supply the relevant methods. LayoutManager2 is an interface that provides a minimal extension to LayoutManager for the purpose of assigning components to the layout based on a constraints object, such as a GridBagConstraints object for the GridBagLayout manager, or the "North" string object for the BorderLayout manager.

Utility Classes

Utility classes represent useful structures and resources, many of which are fundamental to graphics programming and can act as properties of widgets. The utility classes are grouped as follows:

  • Vectors: Dimension, Insets, Point, Polygon, Rectangle, Shape

  • Color: Color, SystemColor

  • Resource: Cursor, Font, FontMetrics, Graphics, Image, PrintGraphics, PrintJob, Toolkit

  • Resource-manipulation utilities MediaTracker

Because the vector classes are primarily intended to store and retrieve position information, all their relevant fields are public. An Insets object is attached to a component to give an idea of the spacing between that component and the ones around it; Insets objects are handled by the layout manager.

The color classes effectively encapsulate color to a depth of 16,777,216 colors, which is as many as most current operating systems support. The SystemColor class can be used to retrieve information about the current desktop colors, including text and window foreground and background colors.

Cursor objects encapsulate the shape of the mouse pointer. They can be one of a number of predefined types, including the default cursor (usually an arrow), a text cursor for positioning the caret, a wait cursor to show that some uninterruptable task is in progress, a hand cursor to select relevant items (such as hypertext links), a crosshair cursor for graphics positioning, and various resizing cursors. You cannot define your own cursor shapes; the cursor shapes are determined from the shapes of the platform's cursors.

Fonts and font metrics provide a way to describe fonts in Java. Every implementation of Java is guaranteed to have at least six fonts: Courier, Dialog, DialogInput, Helvetica, Times Roman, and ZapfDingbats. The actual font used by the platform depends on the specific platform. For example, Windows uses the MS Sans Serif font for the Dialog font.

The Toolkit class encapsulates a windowing toolkit on the underlying platform. The Toolkit class is used to create peers (see "Peers," later in this chapter), return lists of fonts and current print jobs, and provide a way to request information such as screen resolution and size.

Loading Images

The Image class represents an image. There are two standard ways you can load an image into Java, depending on whether you are developing an applet or an application. For an applet, you can use the getImage(URL) method on the applet itself. For an application, you can use Toolkit.getDefaultToolkit().getImage(URL) independently. Images in GIF and JPEG format are currently supported by the AWT. When you call getImage(), it returns immediately with a valid image reference but loads the image in a separate thread. The MediaTracker class can be used to keep track of the process of loading images; for example, if you want to wait until an image is completely loaded before showing a frame, you can use the MediaTracker class as follows:

Frame f = new Frame("MediaTracker test");
MediaTracker mt = new MediaTracker(f);
Image img = Toolkit.getDefaultToolkit().getImage(new URL("http://myserver.com/ myimage.gif"));
mt.addImage(img, 0);
mt.waitForAll();
f.show();

Printing

The PrintJob class and PrintGraphics interface are used to provide the printing API. Printing in Java is very simple. First, you initiate a print job by calling getPrintJob(Frame, String, Properties) on a valid Toolkit object. The Properties object allows you to assign default values for the print job, which is specific to the platform (and sometimes to the printer). The getPrintJob() method displays a print dialog box to the user, so the returned PrintJob object will be complete with all the properties specified by the user. Next, you can call getGraphics() on the PrintJob. This method provides a Graphics object that implements the PrintGraphics interface, which you can pass to the paint() or print() method, or simply use graphics methods on. When you finish with the graphics context, you can flush it to the printer by calling dispose(). Each graphics context represents a page, so you can print multiple pages by making multiple calls to getGraphics(). You can determine page attributes and resolution with the getPageDimensions() and getPageResolution() methods on the PrintJob. Note that the initiation of a print job is an operation bound by security considerations: Untrusted applets are not allowed to print potentially subversive material on the printer of their choice.

Peers

The simple widgets (and the Panel and Canvas classes) in the AWT class library use a peer mechanism to provide the visual aspects of the component. A peer is an object created by the native windowing platform that the Java virtual machine manages. Peers have the same "look and feel" as other, nonJava components on the same platform. For example, a Choice list instantiated on a Motif desktop creates a raised component with a small 3D hyphen that pops up a 3D menu centered on the item currently selected, but on a Windows 95 desktop, the same Choice list creates a recessed component (like a text field) with a button showing a down arrow that pops up a flat menu below the button.

The advantage of using a peer mechanism is that users of a particular windowing platform can always be provided with GUI components that resemble those they are used to, and it allows simple data transfer to and from the system clipboard, because the objects that are created are managed by the platform and not by the Java virtual machine.

There are a number of disadvantages with peers, however. First, the area covered by the bounding box of peer-based components will always be filled with the background color of that component. If you want a background image to your application, for example, the image will always be covered by any peer-based components you add to the application. Second, the windowing platform requires additional overhead in the form of system resources to manage the peers that are created, and calls from the Java virtual machine to the operating system to synchronize operations on components with their peer correlates can add to overall processing time.

There is an alternative to using peers, however. Instead of subclassing components from Canvas and Panel, you can instead derive them directly from Component and Container, writing paint() methods in the same way but without the overhead of maintaining a peer. For example, you can derive the ImageButton class (described in the preceding section on the Canvas class) from Component instead of from Canvas. This approach means that any painting that occurred on the ImageButton's container would show through the button, unless you first chose to fill the clip rectangle with the button's background color.

One other advantage to writing your own peerless components is that you have a great deal of control over the specifics of how the components look. You can also correct failings in the platform-specific implementations of peer classes that you otherwise have no control over in Java. For example, a peer-based List widget on a Windows 95 platform always has a black border. If the user wants a black background, he or she will not be able to see the border of their List widget. You can correct this possible problem in your peerless component by examining the system colors (described in "Utility Classes," earlier in this chapter) and drawing the border in an appropriate color.


NOTE: Sun intends to distribute a larger set of peerless simple widget components (also called lightweight components) in the next version of the AWT. The next version of the AWT should also have, as standards, more complex rendering abilities and drag-and-drop functionality.

Summary

The Java AWT contains a collection of components and ways to lay them out that you can use as-is or extend to provide a complete and consistent multiplatform graphical user interface. Presenting information to the user and being able to respond to a wide variety of ways that users manipulate the application is a complex and challenging task; this task is made considerably easier by the encapsulation of function and display features in AWT objects. By using the AWT, you can create small applets as well as fully featured applications that run on a variety of hardware and software platforms.

TOCBACKFORWARDHOME


©Copyright, Macmillan Computer Publishing. All rights reserved.