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

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. Unfortunately, a component that requires this treatment on one platform might not require it on another platform. In Sun Microsystem's implementation of Java, for example, applet objects must be treated in this way. So if you create a subclass of Applet that is supposed to be able to respond to keyboard events, you should be sure to set up a mouse listener for your class, and call requestFocus() in the mousePressed() method. If you don't do this, your applet might work on some versions of Java, but on others it will fail because it never receives the input focus.

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, typing the character 'R', 'G', 'B', or 'K' 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 unfocussed applet, it requests 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.

Sorry, your browser doesn't
support Java.

In Java, keyboard event objects belong to a class called KeyEvent. An object that needs to listen for KeyEvents must implement the interface, KeyListener. Furthermore, the object must be registered with a component by calling the component's addKeyListener() method. When an applet is to listen for keyboard events on itself, the registration is done with the command "addKeyListener(this);" in the applet's init() method. 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.

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 a keyPressed event. 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, 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 repaint() to redraw the whole 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.
          
      int key = evt.getKeyCode();  // Keyboard code for the pressed key.
      
      if (key == KeyEvent.VK_LEFT) {
         squareLeft -= 8;
         if (squareLeft < 3)
            squareLeft = 3;
         repaint();
      }
      else if (key == KeyEvent.VK_RIGHT) {
         squareLeft += 8;
         if (squareLeft > getSize().width - 3 - SQUARE_SIZE)
            squareLeft = getSize().width - 3 - SQUARE_SIZE;
         repaint();
      }
      else if (key == KeyEvent.VK_UP) {
         squareTop -= 8;
         if (squareTop < 3)
            squareTop = 3;
         repaint();
      }
      else if (key == KeyEvent.VK_DOWN) {
         squareTop += 8;
         if (squareTop > getSize().height - 3 - SQUARE_SIZE)
            squareTop = getSize().height - 3 - SQUARE_SIZE;
         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. The applet's paint() method looks at the value of focussed to decide what color the border of the applet should be. The value of the variable is set in the focusGained() and focusLost() methods. These methods call repaint() so that the applet will be redrawn with the correct border color. The method definitions are very simple:

      public void focusGained(FocusEvent evt) {
             // The applet now has the input focus.
         focussed = true;
         repaint();  // redraw with cyan border
      }

      public void focusLost(FocusEvent evt) {
            // The applet has now lost the input focus.
         focussed = false;
         repaint();  // redraw with gray border
      }

The other aspect of handling focus is to make sure that the applet requests the focus when the user clicks on it. To do this, the applet implements the MouseListener interface and listens for mouse events on itself. It defines a mousePressed routine that asks for the input focus:

       public void mousePressed(MouseEvent evt) {
          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 Applet 
                       implements KeyListener, FocusListener, MouseListener

The init() method registers the applet to listen for all three types of events. To do this, the init() method includes the lines

         addFocusListener(this);
         addKeyListener(this);
         addMouseListener(this);


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 paint() 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 paint() 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 KeyboardAnimationApplet. (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 KeyboardAnimationApplet 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 KeyboardAnimationApplet.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 object 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.

Sorry, your browser doesn't
support Java.

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. When no explosion is happening, the value of explosionFrameNumber is zero.

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. 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 KeyboardAnimationApplet 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(). 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 positioned 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. But several other things can 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.


[ Next Section | Previous Section | Chapter Index | Main Index ]