Chapter 28
java.awt Events

by Mark Wutka

Event handling is one of the most important functions in a graphical program. You have to know when someone presses a key, moves the mouse, or activates a GUI component, such as a button. You can choose which events you want to handle, and which components' events you would like to receive.

The event handling mechanism under Java 1.0 was one of the most heavily criticized parts of Java. The complaints were mostly that it promoted bad design and made it difficult to handle events. Sun responded to this criticism by creating a new event handling mechanism for Java 1.1. This new event mechanism provides added flexibility, increased speed, and promotes better design principles.

Since Java 1.1 should run all Java 1.0 programs with no changes, the old AWT event model is also supported. Sun recommends that you use the Java 1.1 mechanism for any new development, and that you migrate your existing programs to the new model, since the Java 1.0 model may be abandoned in some future release of Java.

The Java 1.0 Event Model

Before plunging into the Java 1.1 event model, you should understand the Java 1.0 event model, how it works, and why it is not the best solution.

Under the Java 1.0 event model, every AWT component can receive events. The event delivery is performed by the handleEvent method that is defined in java.awt.Component. The handleEvent method then analyzes the event and calls an appropriate handler method. For instance, if handleEvent receives a MOUSE_DOWN event, it calls the mouseDown method. If the mouseDown method returns false, the handleEvent method assumes that the mouseDown event hasn't been processed and it passes the event up to the component's container, where the same sequence is repeated. Eventually, the event is handled or it gets to the topmost container without being handled and is discarded.

While this method seems pretty sound on the surface, in practice it causes some serious design problems. Many developers take advantage of the fact that events are propagated up to the parent container. When writing applets, they simply handle all the events for the components in the applet's handleEvent method (or in its event handlers like mouseDown, keyDown, and so on). This makes the applet more difficult to maintain, and certainly difficult to reuse. In general, when you have a central object that controls the entire program flow, you probably do not have a good object-oriented design. You should strive to have a system where objects communicate with each other to accomplish a set of tasks without having to know everything that is going on.

Suppose, for instance, you want to create a button that plays a neat little musical tune. If you handle the button's ACTION event (the event generated when you press the button) in your applet, you make it difficult to reuse the button. You have to extract the code from the applet and put it in another applet. It would be better if you could define a MusicalButton class that any applet can use without having to handle the button's events.

Designers who tried to avoid this centralized design encountered another problem. In order to handle the events in a distributed manner, you had to create a subclass of a component and override the component's event handling methods. For instance, in the case of the MusicalButton object, you would create a subclass of Button and override the button's action method. This was a reasonable solution, but as people started creating interesting new types of buttons, the problems began to show up. For every type of button you wanted to use, you had to create a separate subclass of that button. If someone created an ImageButton that let you use a picture instead of text as the button's label, you would have to create a separate subclass of ImageButton in order to implement the MusicalButton. The only difference between the MusicalButton and the MusicalImageButton is the parent class. This is a huge waste of time and resources.

The Java 1.1 Event Model

The Java 1.1 event model makes the important observation that an event is often handled by another object. For instance, when you press a button, you want to perform the processing for the action event in some object other than the button. In order to support this, the event model supports the notion of event listeners as defined in java.util.EventListener.

An event listener is any object that implements one or more listener interfaces. There are different listeners for each category of AWT event. For instance, the MouseListener interface defines methods such as mouseClicked, mousePressed, and MouseReleased. In order to receive events from a component, an object adds itself as a listener for that component's events. If an object implements the MouseListener interface, it listens for a component's mouse events by calling addMouseListener on that component. This allows you to handle a component's events without having to create a subclass of the component, and without handling the events in the parent container.

The MusicalButton object, for instance, would implement the ActionListener interface, which would receive ActionEvent objects through the actionPerformed method. Since MusicalButton is no longer a subclass of button, you can hook it up to any type of button without adding additional code. All buttons support the addActionListener method to add listeners.

For each type of listener in the Java 1.1 event model there is also an adapter object. The adapters are very simple objects that implement a specific interface, containing empty methods for each method defined in the interface. If you are creating an object specifically to implement the KeyListener interface, you can just create an object that is a subclass of KeyAdapter and then override whichever methods you are interested in. The same is true for all listener interfaces.

One of the other complaints against the Java 1.0 event model was that there was one big Event object that contained attributes for all possible events. Under Java 1.1, there are different event objects for different events. Keyboard events are delivered in a KeyEvent object, while actions are delivered in an ActionEvent object. This allows the events to stay relatively small, since they don't have to contain all possible variations of events.


CAUTION:
Although the Java 1.0 event model is supported in the current Java release, you should not intermix the 1.0 event model with the 1.1 event model in the same program. They are not guaranteed to work at the same time.


Keyboard and Mouse Events

Your applet can receive information about the keyboard and mouse. You can be notified when a key is pressed and when it is released; when the mouse enters the applet window and when it leaves the applet window; when the mouse button is pressed and when it is released; when the mouse moves and when it is dragged (moved with the button held down).

Keyboard Events in Java 1.1 and Above

To listen for keyboard events from an object under Java 1.1, you need to implement the KeyListener interface. The KeyListener interface contains three methods: keyPressed, keyReleased, and keyTyped. The keyPressed method is called whenever a key is pressed, and the keyReleased method is called whenever a key is released. The keyTyped method is a combination of keyPressed and keyReleased. When a key is pressed and then released (as in normal typing), the keyTyped method is called. Here are the method declarations for the KeyListener interface:

public abstract void keyTyped(KeyEvent event)
public abstract void keyPressed(KeyEvent event)
public abstract void keyReleased(KeyEvent event)

Every keyboard event has an associated key code, which is returned by the getKeyCode method in KeyEvent:

public int getKeyCode()

A key code can be the character typed, in the case of a normal letter, or it can be a special key (function keys, cursor movement keys, keyboard control keys, and so on). Certain keys are also considered action keys. The action keys are the cursor movement keys (arrows, home, end), the function keys F1-F12, the print screen key, and the lock keys (caps lock, num lock, and scroll lock). The isActionKey method in KeyEvent returns true if the key involved is an action key:

public boolean isActionKey()

Since keycodes vary from system to system, the AWT defines its own codes for common keys. The key codes defined in the KeyEvent class are:
Key Codes Key
KeyEvent.F1-KeyEvent.F12 Function keys F1-F12
KeyEvent.LEFT left-arrow key
KeyEvent.RIGHT right-arrow key
KeyEvent.UP up-arrow key
KeyEvent.DOWN down-arrow key
KeyEvent.END End key
KeyEvent.HOME Home key
KeyEvent.PGDN Page Down key
KeyEvent.PGUP Page Up key
KeyEvent.PRINT_SCREEN Print Screen key
KeyEvent.SCROLL_LOCK Scroll Lock Key
KeyEvent.CAPS_LOCK Caps Lock Key
KeyEvent.NUM_LOCK Num Lock Key
KeyEvent.PAUSE Pause Key
KeyEvent.INSERT Insert Key
KeyEvent.DELETE Delete Key
KeyEvent.ENTER Enter Key
KeyEvent.TAB Tab Key
KeyEvent.BACK_SPACE Backspace Key
KeyEvent.ESCAPE Escape Key
Since many keycodes are really just normal characters, you can retrieve the character code for a keystroke with getKeyChar:

public char getKeyChar()

Modifier Keys in Java 1.1

You might think that the modifier keys (control, alt, shift, meta) are keyboard events, but they aren't. Under most windowing systems, you can use these keys in conjunction with the mouse as well. The Java 1.1 event hierarchy contains an InputEvent class, which is the superclass of both KeyEvent and MouseEvent. The getModifiers method in InputEvent returns a bitmap indicating which modifier keys were active when the event occurred:

public int getModifiers()

You can use the SHIFT_MASK, CTRL_MASK, META_MASK, and ALT_MASK attributes of InputEvent to examine the modifier bits returned by getModifiers. For example, the following code snippet checks an event to see if the alt key was down when the event occurred:

InputEvent evt;
if ((evt.getModifiers() & InputEvent.ALT_MASK) != 0) {
     // the alt key was down
}

Since this can be a cumbersome way to check for modifiers, the InputEvent class also defines the following shortcuts:

public boolean isShiftDown()
public boolean isControlDown()
public boolean isMetaDown()

The mouse buttons are also considered modifier keys. The BUTTON1_MASK, BUTTON2_MASK, and BUTTON3_MASK attributes of InputEvent allow you to check to see if any of the buttons were pressed when the event occurred. There are no shortcuts for these methods, however.

In addition to the modifier information, you can also find out when an input event occurred by calling getWhen, which returns a timestamp similar to the one returned by System.currentTimeMillis:

public long getWhen()

Keyboard Events in Java 1.0

Under Java 1.0, the key handling methods are defined within the Component class (the superclass of all AWT components). In order to handle these methods, you must override the method in your own subclass. The keyDown method is called whenever a key is pressed. Its companion method, keyUp, is called whenever a key is released. You will normally just be concerned with a key being pressed, so you can usually ignore the keyUp method. The format for keyDown and keyUp is the following:

public boolean keyDown(Event event, int keyCode)
public boolean keyUp(Event event, int keyCode)

where event is an Event object that contains specific information about the keyboard event (the key press or the key release), and keyCode is the key that was pressed.

All of your event handling methods, such as keyDown and keyUp, should return a value of true if they actually handle the event, or false to pass the event up to their parent container.

For regular ASCII characters, the keyCode is the ASCII value of the character pressed. For instance, if you press the g key, the keyCode would be 107. You could also cast the keyCode to a character value, in which case it would be the character "g." If you were to hold down shift and press g, the keyCode would be 71, representing the character value G. If you hold down control and press g, the keyCode would be 7.

You can also determine if the shift, control, or alt (sometimes called meta) keys have been pressed by checking the shiftDown, controlDown, and metaDown methods in the event class. For example:

public boolean keyDown(Event event, int keyCode)
{
   if (event.shiftDown())
   {
     // someone pressed shift
   }
   if (event.controlDown())
   {
     // someone pressed control
   }
   if (event.metaDown())
   {
     // someone pressed meta (or alt)
   }
   return true;
}

Because the codes for certain keys vary from system to system, Java defines a number of key codes that can be used on all systems. These key codes are as follows:
Key Codes

Key

Event.F1-Event.F12 Function keys F1-F12
Event.LEFT left-arrow key
Event.RIGHT right-arrow key
Event.UP up-arrow key
Event.DOWN down-arrow key
Event.END End key
Event.HOME Home key
Event.PGDN Page Down key
Event.PGUP Page Up key

Mouse Events in Java 1.1

There are two different listener interfaces in Java 1.1 that listen to mouse events. Most of the time, you only need the MouseListener interface, which defines methods that are not related to the motion of the mouse:

The mousePressed and mouseReleased method indicate that a mouse button has been pressed or released:

public abstract void mousePressed(MouseEvent event)
public abstract void mouseReleased(MouseEvent event)

If you don't want to keep track of when a button is pressed and then released, you can use the mouseClicked method, which is called when a button is pressed and then released:

public abstract void mouseClicked(MouseEvent event)

The getClickCount method in the MouseEvent object tells you how many times the button was clicked, so you can detect double-clicks:

public int getClickCount()

The mouseEntered and mouseExited methods are called whenever the mouse enters a component and when it leaves the component:

public abstract void mouseEntered(MouseEvent event)
public abstract void mouseExited(MouseEvent event)

At any time, you can get the x,y coordinate where the event occurred (relative to the com-ponent's x,y) by calling the getPoint method in the MouseEvent object, or by calling getX and getY:

public synchronized Point getPoint()
public int getX()
public int getY()

Since most applications do not need to track mouse motion, the mouse motion methods have been placed in a separate listener interface. This allows you to listen for simple button presses without getting an event every time someone sneezes near the mouse. The MouseListenerInterface implements two methods for tracking mouse movement. The mouseMoved method is called whenever the mouse is moved but no buttons have been pressed, while mouseDragged is called when the mouse is moved while a button is pressed:

public abstract void mouseMoved(MouseEvent event)
public abstract void mouseDragged(MouseEvent event)

Mouse Events in Java 1.0

You can receive information about the mouse through a number of different methods. The mouseDown event is called whenever the mouse button is pressed:

public boolean mouseDown(Event event, int x, int y)

where event is the Event class containing information about the event, and x and y are the coordinates where the mouse button was pressed.

You may also want to know when the mouse button is released. You can use the mouseUp method, which takes the same arguments as mouseDown:

public boolean mouseUp(Event event, int x, int y)

The mouseEnter and mouseExit methods are called whenever the mouse enters the applet area or leaves it. These methods also take the same arguments as mouseDown:

public boolean mouseEnter(Event event, int x, int y)
public boolean mouseExit(Event event, int x, int y)

You can also track the movement of the mouse with mouseMove and mouseDrag. mouseMove is called whenever the mouse is moved while the button is up; mouseDrag is called when the mouse is moved while the button is down. These methods also take the same arguments as mouseDown:

public boolean mouseMove(Event event, int x, int y)
public boolean mouseDrag(Event event, int x, int y)

As with the keyboard events, mouse events are not handled within a component under the Java 1.1 event model as they are in the Java 1.0 model. Future versions of Java may not even support the Java 1.0 model, so exercise caution when tracking the mouse this way.

The applet in Listing 28.1 uses keyboard events and mouse events to manipulate shapes. The applet in Listing 28.2 makes use of a utility class called Shape, which extends the Polygon class to enable a polygon to be moved around the screen easily.

Listing 28.1Source Code for Shape.java

import java.awt.*;
//
// The Shape class is an extension of the Polygon class that adds
// a method for moving the Polygon to a different location. It makes
// a copy of the original coordinates, then when you move it to a new
// location, it just adds the new position to each coordinate. In other words,
// if you moved the shape to (100,100), moveShape would add 100 to each x
// coordinate and each y coordinate. You should give the coordinates relative
// to 0, 0.
public class Shape extends Polygon
{

   private int[] originalXpoints;

   private int[] originalYpoints;
   public int x;

   public int y;
   public Shape(int x[], int y[], int n)

   {

     super(x, y, n);
// Make a copy of the x coordinates

     originalXpoints = new int[n];

     System.arraycopy(x, 0, originalXpoints, 0, n);
// Make a copy of the y coordinates

     originalYpoints = new int[n];

     System.arraycopy(y, 0, originalYpoints, 0, n);
   }
   public void moveShape(int newX, int newY)

   {

     int i;
// Add the new X and new Y values to the original coordinates, and make that

// the new position of this shape.
     for (i=0; i < npoints; i++)

     {

        xpoints[i] = originalXpoints[i] + newX;

        ypoints[i] = originalYpoints[i] + newY;
     }
   }
}

Listing 28.2Source Code for ShapeManipulator.java

import java.awt.*;
import java.applet.*;
//
// The ShapeManipulator applet lets you drag a shape
// around the screen by holding down the left mouse
// button. It uses three different shapes: a triangle,
// a square, and a pentagon. You can switch between these
// by hitting `t', `s', and `p' respectively.
//
// This applet makes use of the Shape class, which extends
// the functionality of Polygon to enable the polygon to be
// moved to a new location with a single method call.
public class ShapeManipulator extends Applet

{
    private int squareXCoords[] = { 0, 40, 40, 0 };

    private int squareYCoords[] = { 0, 0, 40, 40 };
    private int triangleXCoords[] = { 0, 20, 40 };

    private int triangleYCoords[] = { 40, 0, 40 };
    private int pentXCoords[] = { 0, 20, 40, 30, 10 };

    private int pentYCoords[] = { 15, 0, 15, 40, 40 };
    private int shapeX;   // the X and Y of the current shape

    private int shapeY;
    private Shape currentShape;   // What shape you are dragging
private Shape triangle;

    private Shape square;

    private Shape pentagon;
    public void init()

    {

        shapeX = 0;

        shapeY = 0;
        triangle = new Shape(triangleXCoords, triangleYCoords, 3);

        square = new Shape(squareXCoords, squareYCoords, 4);

        pentagon = new Shape(pentXCoords, pentYCoords, 5);
        currentShape = triangle;   // Start with a triangle

    }
    public void paint(Graphics g)

    {

        g.fillPolygon(currentShape);   // Draw the current shape

    }
    public boolean mouseDrag(Event event, int mouseX, int mouseY)

    {

        shapeX = mouseX; // make shape coordinates = mouse coordinates

        shapeY = mouseY;
// Now move the shape to its new coordinates

        currentShape.moveShape(shapeX, shapeY);
// Even though the shape is moved, you still need to call repaint to update

// the display.

repaint();
        return true;   // always do this in event handlers

    }
    public boolean keyDown(Event event, int keyCode)

   {
// Check the keyCode to see if it is a t, an s, or a p
     if ((char)keyCode == `t')

     {

        currentShape = triangle;

     }

     else if ((char)keyCode == `s')

     {

        currentShape = square;

     }

     else if ((char)keyCode == `p')

     {

        currentShape = pentagon;

     }
// because you may have changed the shape, make sure the current shape

// is moved to the current shape X and Y
     currentShape.moveShape(shapeX, shapeY);
// Make sure the screen shows the current shape

     repaint();
     return true;

   }

}