Section 6.5
Keyboard Events
IN JAVA, EVENTS are associated with GUI components.
When the user presses a button on the mouse,
the event that is generated is associated with the component that contains
the mouse cursor. What about keyboard events? When the user presses a key,
what component is associated with the key event that is generated?
A GUI uses the idea of input focus to determine the
component associated with keyboard events. At any given time, exactly one interface
element on the screen has the input focus, and that is where all keyboard events are
directed. If the interface element happens to be a Java component, then the information
about the keyboard event becomes a Java object of type KeyEvent, and it
is delivered to any listener objects that are listening for KeyEvents
associated with that component. The necessity of managing input focus adds
an extra twist to working with keyboard events.
It's a good idea to give the user some visual feedback about which component
has the input focus. For example, if the component is the typing area of a
word-processor, the feedback is usually in the form of a blinking text cursor.
Another common visual clue is to draw a brightly colored border around the edge
of a component when it has the input focus, as I do in the sample applet later
on this page.
A component that wants to have the input focus can call the method requestFocus(),
which is defined in the Component class. Calling this method does not absolutely
guarantee that the component will actually get the input focus. Several components might
request the focus; only one will get it. This method should only be used in
certain circumstances in any case, since it can be a rude surprise to the user to have
the focus suddenly pulled away from a component that the user is working with.
In a typical user interface, the user can choose to give the focus to a component by clicking on
that component with the mouse. And pressing the tab key will often move the focus
from one component to another.
Some components do not automatically receive the input focus when the user
clicks on them. To solve this problem, a program has to register a mouse listener
with the component to detect user clicks. In response to a user click, the
mousePressed() method should call requestFocus() for the
component. This is true, in particular, for the components that are used
as drawing surfaces in the examples in this chapter. These components are
defined as subclasses of JPanel, and JPanel objects do not receive
the input focus automatically. If you want to be able to use the keyboard
to interact with a JPanel named drawingSurface, you have to register
a listener to listen for mouse events on the drawingSurface and call
drawingSurface.requestFocus() in the mousePressed() method
of the listener object.
Here is a sample applet that processes keyboard events. If the applet has the
input focus, the arrow keys can be used to move the colored square. Furthermore,
pressing the 'R', 'G', 'B', or 'K' key will set the color of the square
to red, green, blue, or black. When the applet has the input focus, the border
of the applet is a bright cyan (blue-green) color. When the applet does not have
the focus, the border is gray, and a message, "Click to activate," is displayed.
When the user clicks on an unfocused applet, it requests the input focus.
(In some browsers, you also have to leave the mouse positioned inside the applet,
in order for it to have the input focus.)
The complete source code for this applet is in the file
KeyboardAndFocusDemo.java.
I will discuss some aspects of it below. After reading this section,
you should be able to understand the source code in its entirety.
In Java, keyboard event objects belong to a class called KeyEvent.
An object that needs to listen for KeyEvents must implement the
interface named KeyListener. Furthermore, the object must be registered
with a component by calling the component's addKeyListener() method.
The registration is done with the command "component.addKeyListener(listener);"
where listener is the object that is to listen for key events, and component is
the object that will generate the key events (when it has the input focus). It is possible for
component and listener to be the same object.
All this is, of course, directly analogous
to what you learned about mouse events in the previous
section. The KeyListener interface defines the following
methods, which must be included in any class that implements KeyListener:
public void keyPressed(KeyEvent evt);
public void keyReleased(KeyEvent evt);
public void keyTyped(KeyEvent evt);
Java makes a careful distinction between the keys that you press
and the characters that you type. There are lots of keys on a keyboard:
letter keys, number keys, modifier keys such as Control and Shift, arrow keys,
page up and page down keys, keypad keys, function keys. In many cases, pressing
a key does not type a character. On the other hand, typing a character sometimes
involves pressing several keys. For example, to type an uppercase 'A', you
have to press the Shift key and then press the A key before releasing the Shift
key. On my Macintosh computer, I can type an accented e, é, by holding
down the Option key, pressing the E key, releasing the Option key, and pressing
E again. Only one character was typed, but I had to perform three key-presses
and I had to release a key at the right time. In Java, there are three types
of KeyEvent. The types correspond to pressing a key, releasing a key, and
typing a character. The keyPressed method is called when the user
presses a key, the keyReleased method is called when the user releases a
key, and the keyTyped method is called when the user types a character.
Note that one user action, such as pressing the E key, can be responsible for two events,
a keyPressed event and a keyTyped event. Typing an upper case
'A' could generate two keyPressed, two keyReleased, and
one keyTyped event.
Usually, it is better to think in terms of two separate streams of events,
one consisting of keyPressed and keyReleased events and the
other consisting of keyTyped events. For some applications, you
want to monitor the first stream; for other applications, you want to monitor the second one.
Of course, the information in the keyTyped
stream could be extracted from the keyPressed/keyReleased stream, but it
would be difficult (and also system-dependent to some extent).
Some user actions, such as pressing the Shift key, can only be detected
as keyPressed events. I have a solitaire game on my computer that
hilites every card that can be moved, when I hold down the Shift key.
You could do something like that in Java by hiliting the cards when the
Shift key is pressed and removing the hilite when the Shift key is released.
There is one more complication. Usually, when you hold down a key on
the keyboard, that key will auto-repeat.
This means that it will generate multiple keyPressed events,
as long as it is held down. It can also generate multiple keyTyped
events. For the most part, this will not affect your programming, but you should
not expect every keyPressed event to have a corresponding keyReleased
event.
Every key on the keyboard has an integer code number. (Actually, this is
only true for keys that Java knows about. Many keyboards have extra keys that
can't be used with Java.) When the keyPressed or keyReleased
method is called, the parameter, evt, contains the code of the key
that was pressed or released. The code can be obtained by calling the
function evt.getKeyCode(). Rather than asking you to memorize a
table of code numbers (which can be different for different platforms in any case),
Java provides a named constant for each key.
These constants are defined in the KeyEvent class. For example
the constant for the shift key is KeyEvent.VK_SHIFT. If you want
to test whether the key that the user pressed is the Shift key, you could
say "if (evt.getKeyCode() == KeyEvent.VK_SHIFT)".
The key codes for the four arrow keys are KeyEvent.VK_LEFT,
KeyEvent.VK_RIGHT, KeyEvent.VK_UP, and KeyEvent.VK_DOWN.
Other keys have similar codes. (The "VK" stands for "Virtual
Keyboard". In reality, different keyboards use different key codes, but Java
translates the actual codes from the keyboard into its own "virtual"
codes. Your program only sees these virtual key codes, so it will work with
various keyboards on various platforms without modification.)
In the case of a keyTyped event, you want to know which character
was typed. This information can be obtained from the parameter, evt,
in the keyTyped method by calling the function evt.getKeyChar().
This function returns a value of type char representing the character
that was typed.
In the KeyboardAndFocusDemo applet, shown above, I use
the keyPressed routine to respond when the user presses one
of the arrow keys. The applet includes instance variables, squareLeft
and squareTop that give the position of the upper left corner of
the square. When the user presses one of the arrow keys, the
keyPressed routine modifies the appropriate instance variable
and calls canvas.repaint() to redraw the whole applet. ("canvas"
is the name I use for the drawing surface component in this applet.)
Note that
the values of squareLeft and squareRight are
restricted so that the square never moves outside the white area of the
applet:
public void keyPressed(KeyEvent evt) {
// Called when the user has pressed a key, which can be
// a special key such as an arrow key. If the key pressed
// was one of the arrow keys, move the square (but make sure
// that it doesn't move off the edge, allowing for a
// 3-pixel border all around the applet). SQUARE_SIZE is
// a named constant that specifies the size of the square.
// squareLeft and squareRight give the position of the
// top-left corner of the square.
int key = evt.getKeyCode(); // Keyboard code for the pressed key.
if (key == KeyEvent.VK_LEFT) { // left-arrow key; move square up
squareLeft -= 8;
if (squareLeft < 3)
squareLeft = 3;
canvas.repaint();
}
else if (key == KeyEvent.VK_RIGHT) { // right-arrow key; move right
squareLeft += 8;
if (squareLeft > getSize().width - 3 - SQUARE_SIZE)
squareLeft = getSize().width - 3 - SQUARE_SIZE;
canvas.repaint();
}
else if (key == KeyEvent.VK_UP) { // up-arrow key; move up
squareTop -= 8;
if (squareTop < 3)
squareTop = 3;
canvas.repaint();
}
else if (key == KeyEvent.VK_DOWN) { // down-arrow key; move down
squareTop += 8;
if (squareTop > getSize().height - 3 - SQUARE_SIZE)
squareTop = getSize().height - 3 - SQUARE_SIZE;
canvas.repaint();
}
} // end keyPressed()
Color changes -- which happen when the user types the characters 'R', 'G', 'B', and 'K',
or the lower case equivalents -- are handled in the keyTyped method. I won't
include it here, since it is so similar to the keyPressed method. Finally,
to complete the KeyListener interface, the keyReleased method
must be defined. In the sample applet, the body of this method is empty since the
applet does nothing to respond to keyReleased events.
Focus Events
If a component is to change its appearance when it has the input focus, it needs some
way to know when it has the focus. In Java, objects are notified about changes of input
focus by events of type FocusEvent. An object that wants to be notified of
changes in focus can implement the FocusListener interface. This interface
declares two methods:
public void focusGained(FocusEvent evt);
public void focusLost(FocusEvent evt);
Furthermore, the addFocusListener() method must be used to set
up a listener for the focus events. When a component gets the input focus, it calls the
focusGained() method of any object that has been registered with that
component as a FocusListener. When it loses the focus, it calls
the listener's focusLost() method. Often, it is the component itself
that listens for focus events.
In my sample applet, there is a boolean-valued instance variable
named focussed. This variable is true when the applet
has the input focus and is false when the applet does not have focus.
The applet implements the FocusListener interface and listens
for focus events from the canvas.
The paintComponent() method of the canvas looks at the value of focussed
to decide what color the border should be. The value of focussed
is set in the focusGained() and focusLost() methods.
These methods call canvas.repaint() so that the drawing surface
will be redrawn with the correct
border color. The method definitions are very simple:
public void focusGained(FocusEvent evt) {
// The canvas now has the input focus.
focussed = true;
canvas.repaint(); // redraw with cyan border
}
public void focusLost(FocusEvent evt) {
// The canvas has now lost the input focus.
focussed = false;
canvas.repaint(); // redraw with gray border
}
The other aspect of handling focus is to make sure that the canvas
gets the focus when the user clicks on it. To do this, the applet
implements the MouseListener interface and listens for mouse events
on the canvas. It defines a mousePressed routine that asks that
the input focus be given to the canvas:
public void mousePressed(MouseEvent evt) {
canvas.requestFocus();
}
The other four methods of the mouseListener interface are defined
to be empty. Note that the applet implements three listener interfaces,
so the class definition begins:
public class KeyboardAndFocusDemo extends JApplet
implements KeyListener, FocusListener, MouseListener
The applet's init() method registers the applet to listen for all three
types of events. To do this, the init() method includes the lines
canvas.addFocusListener(this);
canvas.addKeyListener(this);
canvas.addMouseListener(this);
There are, of course, other ways to organize this applet. It would be
possible, for example, to use the canvas object instead of the
applet object to listen for events. Or anonymous classes could be used
to define separate listening objects.
State Machines
The information stored in an object's instance variables is said to
represent the state of that object.
When one of the object's methods is called, the action taken by the
object can depend on its state. (Or, in the terminology we have been using,
the definition of the method
can look at the instance variables to decide what to do.) Furthermore,
the state can change. (That is, the definition of the method can
assign new values to the instance variables.) In computer science,
there is the idea of a state machine,
which is just something that has a state and can change state
in response to events or inputs. The response of a state machine
to an event or input depends on what state it's in. An object is
a kind of state machine. Sometimes, this point of view
can be very useful in designing classes.
The state machine point of view can be especially useful in the
type of event-oriented programming that is required by graphical user
interfaces. When designing an applet, you can ask yourself:
What information about state do I need to keep track of? What events
can change the state of the applet? How will my response to
a given event depend on the current state? Should the appearance
of the applet be changed to reflect a change in state? How should
the paintComponent() method take the state into account?
All this is an alternative to the top-down, step-wise-refinement style
of program design, which does not apply to the overall design of an
event-oriented program.
In the KeyboardAndFocusDemo applet, shown above, the state
of the applet is recorded in the instance variables focussed,
squareLeft, and squareTop. These state variables are
used in the paintComponent() method to decide how to draw the applet.
They are set in the various event-handling methods.
In the rest of this section, we'll look at another example, where the
state of the applet plays an even bigger role. In this example, the
user plays a simple arcade-style game by pressing the arrow keys.
The example is based on one of my frameworks, called KeyboardAnimationApplet2.
(See Section 3.7 for a discussion of frameworks
and a sample framework that supports animation.) The game is written
as an extension of the KeyboardAnimationApplet2 class. It includes
a method, drawFrame(), that draws one frame in the animation. It
also defines keyPressed to respond when the user presses the
arrow keys. The source code for the game is in the file
SubKillerGame.java. You can also look
at the source code in KeyboardAnimationApplet2.java,
but it uses some advanced techniques that I haven't covered yet.
You have to click on the game to activate it.
The applet shows a black "submarine" moving back and forth erratically
near the bottom. Near the top, there is a blue "boat". You can move
this boat back and forth by pressing the left and right arrow keys. Attached to
the boat is a red "depth charge." You can drop the depth charge
by hitting the down arrow key. The objective is to blow up the submarine by hitting
it with the depth charge. If the depth charge falls off the bottom of the screen,
you get a new one. If the sub explodes, a new sub is created and you get a new
depth charge. Try it! Make sure to hit the sub at least once, so you can see the
explosion.
Let's think about how this applet can be programmed. What constitutes the
"state" of the applet? That is, what things change from time to time
and affect the appearance or behavior of the applet? Of course, the state
includes the positions of the boat, submarine, and depth charge, so I need
instance variables to store the positions. Anything else, possibly less
obvious? Well, sometimes the depth charge is falling, and sometimes it's not.
That is a difference in state. Since there are two possibilities, I
represent this aspect of the state with a boolean variable, bombIsFalling.
Sometimes the submarine is moving left and sometimes it is moving right.
The difference is represented by another boolean variable, subIsMovingLeft.
Sometimes, the sub is exploding. This is also part of the state, but representing
it requires a little more thought. While an explosion is in progress, the sub
looks different in each frame, since the size of the explosion increases.
Also, I need to know when the explosion is over so that I can go back to
drawing the sub as usual. So, I use a variable, explosionFrameNumber, of type int, which
tells how many frames have been drawn since the explosion started. I represent the
fact that no explosion is happening by setting the value of explosionFrameNumber to zero.
Alternatively, I could have used another boolean variable to keep track of whether or
not an explosion is in progress.
How and when do the values of these instance variables change?
Some of them can change when the user presses certain keys. In the program, this
is checked in the keyPressed() method. If the user presses the left or
right arrow key, the position of the boat is changed. If the user presses
the down arrow key, the depth charge changes from not-falling to falling. This is coded
as follows:
public void keyPressed(KeyEvent evt) {
int code = evt.getKeyCode(); // which key was pressed
if (code == KeyEvent.VK_LEFT) {
// Move the boat left.
boatCenterX -= 15;
}
else if (code == KeyEvent.VK_RIGHT) {
// Move the boat right.
boatCenterX += 15;
}
else if (code == KeyEvent.VK_DOWN) {
// Start the bomb falling, if it is not already falling.
if ( bombIsFalling == false )
bombIsFalling = true;
}
} // end keyPressed()
Note that it's not necessary to call repaint() when the state changes,
since this applet is an animation that is constantly being redrawn anyway. Any changes
in the state will become visible to the user as soon as the next frame is drawn.
At some point
in the program, I have to make sure that the user does not move the boat off the screen.
I could have done this in keyPressed(), but I choose to check for this in another
routine, just before drawing the boat.
Other aspects of the state are changed in the drawFrame() routine. From the
point of view of programming, this method is handling an event ("Hey, it's time to
draw the next frame!"). It just happens to be an event that is generated by
the KeyboardAnimationApplet2 framework rather than by the user. In my applet,
the drawFrame() routine calls three other methods that I wrote to organize
the process of computing and drawing a new frame: doBombFrame(), doBoatFrame(),
and doSubFrame().
Consider doBombFrame(). This routine draws the depth charge.
What happens in this routine depends on the current state,
and the routine can make changes to the state when it is executed.
The state of the bomb can be falling or not-falling, as recorded
in the variable, bombIsFalling. If bombIsFalling is false, then the
bomb is simply drawn at the bottom of the boat. If bombIsFalling is
true, the vertical coordinate of the bomb has to be increased by some amount to make the bomb
move down a bit from one frame to the next. Several other
things can also happen. If the bomb has fallen off the bottom of the applet -- something that we can test
by looking at its vertical coordinate -- then bombIsFalling becomes false. This puts
the bomb back at the boat in the next frame. Also,
the bomb might hit the sub. This can be tested by comparing the locations
of the bomb and the sub. If the bomb hits the sub, then the state changes in two ways:
the bomb is no longer falling and the sub is exploding. These state changes are
implemented by setting bombIsFalling to false and explosionFrameNumber
to 1.
Most interesting is the submarine. What happens with the submarine depends on whether
it is exploding or not. If it is (that is, if explosionFrameNumber > 0),
then yellow and red ovals are drawn at the sub's position. The sizes of these ovals
depend on the value of explosionFrameNumber, so they grow with each
frame of the explosion. After the ovals are drawn, the value of explosionFrameNumber
is incremented. If its value has reached 14, it is reset to 0. This reflects a change
of state: The sub is no longer exploding. It's important for you to understand
what is happening here. There is no loop in the program to draw the stages of the explosion.
Each frame is a new event and is drawn separately, based on values stored in instance
variables. The state can change, which will make the next frame look different from
the current one.
In a frame where the sub is not exploding, it moves left or right. This is accomplished
by adding or subtracting a small amount to the horizontal coordinate of the sub. Whether
it moves left or right is determined by the value of the variable, subIsMovingLeft.
It's interesting to consider how and when this variable changes value. If the
sub reaches the left edge of the applet, subIsMovingLeft is set to false
to make the sub start moving right. Similarly, if the sub reaches the right edge.
But the sub can also reverse direction at random times. The way this is implemented
is that in each frame, there is a small chance that the sub will reverse direction.
This is done with the statement
if ( Math.random() < 0.04 )
sumIsMovingLeft = !subIsMovingLeft;
Since Math.random() is between 0 and 1, the condition "Math.random() < 0.04"
has a 4 in 100, or 1 in 25, chance of being true. In those frames where this conditions happens
to evaluate to true, the sub reverses direction. (The value of the
expression "!subIsMovingLeft" is false when subIsMovingLeft
is true, and it is true when subIsMovingLeft is false,
so it effectively reverses the value of subIsMovingLeft.)
While it's not very sophisticated as arcade games go, the SubKillerGame applet
does use some interesting programming. And it nicely illustrates how to apply state-machine thinking
in event-oriented programming.