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