In the sections that follow, we discuss in more detail the events that are not linked to specific user interface components, in particular, events related to keystrokes and mouse activity. You can find a detailed discussion of semantic events generated by user interface components in the next chapter. Keyboard EventsWhen the user pushes a key, a KeyEvent with ID KEY_PRESSED is generated. When the user releases the key, a KEY_RELEASED KeyEvent is triggered. You trap these events in the keyPressed and keyReleased methods of any class that implements the KeyListener interface. Use these methods to trap raw keystrokes. A third method, keyTyped, combines the two: it reports on the characters that were generated by the user's keystrokes. The best way to see what happens is with an example. But before we can do that, we have to add a little more terminology. Java makes a distinction between characters and virtual key codes. Virtual key codes are indicated with a prefix of VK_, such as VK_A or VK_SHIFT. Virtual key codes correspond to keys on the keyboard. For example, VK_A denotes the key marked A. There is no separate lowercase virtual key code the keyboard does not have separate lowercase keys. NOTE
So, suppose that the user types an uppercase "A" in the usual way, by pressing the SHIFT key along with the A key. Java reports five events in response to this user action. Here are the actions and the associated events:
On the other hand, if the user typed a lowercase "a" by simply pressing the A key, then only three events occur:
Thus, the keyTyped procedure reports the character that was typed ("A" or "a"), whereas the keyPressed and keyReleased methods report on the actual keys that the user pressed. To work with the keyPressed and keyReleased methods, you should first check the key code. public void keyPressed(KeyEvent event) { int keyCode = event.getKeyCode(); . . . } The key code will equal one of the following (reasonably mnemonic) constants. They are defined in the KeyEvent class.
To find the current state of the SHIFT, CONTROL, ALT, and META keys, you can, of course, track the VK_SHIFT, VK_CONTROL, VK_ALT, and VK_META key presses, but that is tedious. Instead, simply use the isShiftDown, isControlDown, isAltDown, and isMetaDown methods. (Sun and Macintosh keyboards have a special META key. On a Sun keyboard, the key is marked a diamond. On a Macintosh, the key is marked with an apple and a cloverleaf.) For example, the following code tests whether the user presses SHIFT + RIGHT ARROW: public void keyPressed(KeyEvent event) { int keyCode = event.getKeyCode(); if (keyCode == KeyEvent.VK_RIGHT && event.isShiftDown()) { . . . } } In the keyTyped method, you call the getKeyChar method to obtain the actual character that was typed. NOTE
Example 8-3 shows how to handle keystrokes. The program (shown in Figure 8-7) is a simple implementation of the Etch-A-Sketch™ toy. Figure 8-7. A sketch programYou move a pen up, down, left, and right with the cursor keys. If you hold down the SHIFT key, the pen moves by a larger increment. Or, if you are experienced in using the vi editor, you can bypass the cursor keys and use the lowercase h, j, k, and l keys to move the pen. The uppercase H, J, K, and L move the pen by a larger increment. We trap the cursor keys in the keyPressed method and the characters in the keyTyped method. There is one technicality: Normally, a panel does not receive any key events. To override this default, we call the setFocusable method. We discuss the concept of keyboard focus later in this chapter. Example 8-3. Sketch.java1. import java.awt.*; 2. import java.awt.geom.*; 3. import java.util.*; 4. import java.awt.event.*; 5. import javax.swing.*; 6. 7. public class Sketch 8. { 9. public static void main(String[] args) 10. { 11. SketchFrame frame = new SketchFrame(); 12. frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 13. frame.setVisible(true); 14. } 15. } 16. 17. /** 18. A frame with a panel for sketching a figure 19. */ 20. class SketchFrame extends JFrame 21. { 22. public SketchFrame() 23. { 24. setTitle("Sketch"); 25. setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); 26. 27. // add panel to frame 28. 29. SketchPanel panel = new SketchPanel(); 30. add(panel); 31. } 32. 33. public static final int DEFAULT_WIDTH = 300; 34. public static final int DEFAULT_HEIGHT = 200; 35. } 36. 37. /** 38. A panel for sketching with the keyboard. 39. */ 40. class SketchPanel extends JPanel 41. { 42. public SketchPanel() 43. { 44. last = new Point2D.Double(100, 100); 45. lines = new ArrayList<Line2D>(); 46. KeyHandler listener = new KeyHandler(); 47. addKeyListener(listener); 48. setFocusable(true); 49. } 50. 51. /** 52. Add a new line segment to the sketch. 53. @param dx the movement in x direction 54. @param dy the movement in y direction 55. */ 56. public void add(int dx, int dy) 57. { 58. // compute new end point 59. Point2D end = new Point2D.Double(last.getX() + dx, last.getY() + dy); 60. 61. // add line segment 62. Line2D line = new Line2D.Double(last, end); 63. lines.add(line); 64. repaint(); 65. 66. // remember new end point 67. last = end; 68. } 69. 70. public void paintComponent(Graphics g) 71. { 72. super.paintComponent(g); 73. Graphics2D g2 = (Graphics2D) g; 74. 75. // draw all lines 76. for (Line2D l : lines) 77. g2.draw(l); 78. } 79. 80. private Point2D last; 81. private ArrayList<Line2D> lines; 82. 83. private static final int SMALL_INCREMENT = 1; 84. private static final int LARGE_INCREMENT = 5; 85. 86. private class KeyHandler implements KeyListener 87. { 88. public void keyPressed(KeyEvent event) 89. { 90. int keyCode = event.getKeyCode(); 91. 92. // set distance 93. int d; 94. if (event.isShiftDown()) 95. d = LARGE_INCREMENT; 96. else 97. d = SMALL_INCREMENT; 98. 99. // add line segment 100. if (keyCode == KeyEvent.VK_LEFT) add(-d, 0); 101. else if (keyCode == KeyEvent.VK_RIGHT) add(d, 0); 102. else if (keyCode == KeyEvent.VK_UP) add(0, -d); 103. else if (keyCode == KeyEvent.VK_DOWN) add(0, d); 104. } 105. 106. public void keyReleased(KeyEvent event) {} 107. 108. public void keyTyped(KeyEvent event) 109. { 110. char keyChar = event.getKeyChar(); 111. 112. // set distance 113. int d; 114. if (Character.isUpperCase(keyChar)) 115. { 116. d = LARGE_INCREMENT; 117. keyChar = Character.toLowerCase(keyChar); 118. } 119. else 120. d = SMALL_INCREMENT; 121. 122. // add line segment 123. if (keyChar == 'h') add(-d, 0); 124. else if (keyChar == 'l') add(d, 0); 125. else if (keyChar == 'k') add(0, -d); 126. else if (keyChar == 'j') add(0, d); 127. } 128. } 129. } java.awt.event.KeyEvent 1.1
java.awt.event.InputEvent 1.1
Mouse EventsYou do not need to handle mouse events explicitly if you just want the user to be able to click on a button or menu. These mouse operations are handled internally by the various components in the user interface and then translated into the appropriate semantic event. However, if you want to enable the user to draw with the mouse, you will need to trap mouse move, click, and drag events. In this section, we show you a simple graphics editor application that allows the user to place, move, and erase squares on a canvas (see Figure 8-8). Figure 8-8. A mouse test programWhen the user clicks a mouse button, three listener methods are called: mousePressed when the mouse is first pressed, mouseReleased when the mouse is released, and, finally, mouseClicked. If you are only interested in complete clicks, you can ignore the first two methods. By using the getX and getY methods on the MouseEvent argument, you can obtain the x- and y-coordinates of the mouse pointer when the mouse was clicked. To distinguish between single, double, and triple (!) clicks, use the getClickCount method. Some user interface designers inflict mouse click and keyboard modifier combinations, such as CONTROL + SHIFT + CLICK, on their users. We find this practice reprehensible, but if you disagree, you will find that checking for mouse buttons and keyboard modifiers is a mess. In the original API, two of the button masks equal two keyboard modifier masks, namely: BUTTON2_MASK == ALT_MASK BUTTON3_MASK == META_MASK This was done so that users with a one-button mouse could simulate the other mouse buttons by holding down modifier keys instead. However, as of JDK 1.4, a different approach is recommended. There are now masks BUTTON1_DOWN_MASK BUTTON2_DOWN_MASK BUTTON3_DOWN_MASK SHIFT_DOWN_MASK CTRL_DOWN_MASK ALT_DOWN_MASK ALT_GRAPH_DOWN_MASK META_DOWN_MASK The getModifiersEx method accurately reports the mouse buttons and keyboard modifiers of a mouse event. Note that BUTTON3_DOWN_MASK tests for the right (nonprimary) mouse button under Windows. For example, you can use code like this to detect whether the right mouse button is down: if ((event.getModifiersEx() & InputEvent.BUTTON3_DOWN_MASK) != 0) . . . // code for right click In our sample program, we supply both a mousePressed and a mouseClicked method. When you click onto a pixel that is not inside any of the squares that have been drawn, a new square is added. We implemented this in the mousePressed method so that the user receives immediate feedback and does not have to wait until the mouse button is released. When a user double-clicks inside an existing square, it is erased. We implemented this in the mouseClicked method because we need the click count. public void mousePressed(MouseEvent event) { current = find(event.getPoint()); if (current == null) // not inside a square add(event.getPoint()); } public void mouseClicked(MouseEvent event) { current = find(event.getPoint()); if (current != null && event.getClickCount() >= 2) remove(current); } As the mouse moves over a window, the window receives a steady stream of mouse movement events. These are ignored by most applications. However, our test application traps the events to change the cursor to a different shape (a cross hair) when it is over a square. This is done with the getPredefinedCursor method of the Cursor class. Table 8-2 lists the constants to use with this method along with what the cursors look like under Windows. (Note that several of these cursors look the same, but you should check how they look on your platform.)
TIP
Here is the mouseMoved method of the MouseMotionListener in our example program: public void mouseMoved(MouseEvent event) { if (find(event.getPoint()) == null) setCursor(Cursor.getDefaultCursor()); else setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR)); } NOTE
If the user presses a mouse button while the mouse is in motion, mouseDragged calls are generated instead of mouseMoved calls. Our test application lets a user drag the square under the cursor. We simply update the currently dragged rectangle to be centered under the mouse position. Then, we repaint the canvas to show the new mouse position. public void mouseDragged(MouseEvent event) { if (current != null) { int x = event.getX(); int y = event.getY(); current.setFrame( x - SIDELENGTH / 2, y - SIDELENGTH / 2, SIDELENGTH, SIDELENGTH); repaint(); } NOTE
There are two other mouse event methods: mouseEntered and mouseExited. These methods are called when the mouse enters or exits a component. Finally, we explain how to listen to mouse events. Mouse clicks are reported through the mouseClicked procedure, which is part of the MouseListener interface. Because many applications are interested only in mouse clicks and not in mouse moves and because mouse move events occur so frequently, the mouse move and drag events are defined in a separate interface called MouseMotionListener. In our program we are interested in both types of mouse events. We define two inner classes: MouseHandler and MouseMotionHandler. The MouseHandler class extends the MouseAdapter class because it defines only two of the five MouseListener methods. The MouseMotionHandler implements the MouseMotionListener and defines both methods of that interface. Example 8-4 is the program listing. Example 8-4. MouseTest.java1. import java.awt.*; 2. import java.awt.event.*; 3. import java.util.*; 4. import java.awt.geom.*; 5. import javax.swing.*; 6. 7. public class MouseTest 8. { 9. public static void main(String[] args) 10. { 11. MouseFrame frame = new MouseFrame(); 12. frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 13. frame.setVisible(true); 14. } 15. } 16. 17. /** 18. A frame containing a panel for testing mouse operations 19. */ 20. class MouseFrame extends JFrame 21. { 22. public MouseFrame() 23. { 24. setTitle("MouseTest"); 25. setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); 26. 27. // add panel to frame 28. 29. MousePanel panel = new MousePanel(); 30. add(panel); 31. } 32. 33. public static final int DEFAULT_WIDTH = 300; 34. public static final int DEFAULT_HEIGHT = 200; 35. } 36. 37. /** 38. A panel with mouse operations for adding and removing squares. 39. */ 40. class MousePanel extends JPanel 41. { 42. public MousePanel() 43. { 44. squares = new ArrayList<Rectangle2D>(); 45. current = null; 46. 47. addMouseListener(new MouseHandler()); 48. addMouseMotionListener(new MouseMotionHandler()); 49. } 50. 51. public void paintComponent(Graphics g) 52. { 53. super.paintComponent(g); 54. Graphics2D g2 = (Graphics2D) g; 55. 56. // draw all squares 57. for (Rectangle2D r : squares) 58. g2.draw(r); 59. } 60. 61. /** 62. Finds the first square containing a point. 63. @param p a point 64. @return the first square that contains p 65. */ 66. public Rectangle2D find(Point2D p) 67. { 68. for (Rectangle2D r : squares) 69. { 70. if (r.contains(p)) return r; 71. } 72. return null; 73. } 74. 75. /** 76. Adds a square to the collection. 77. @param p the center of the square 78. */ 79. public void add(Point2D p) 80. { 81. double x = p.getX(); 82. double y = p.getY(); 83. 84. current = new Rectangle2D.Double( 85. x - SIDELENGTH / 2, 86. y - SIDELENGTH / 2, 87. SIDELENGTH, 88. SIDELENGTH); 89. squares.add(current); 90. repaint(); 91. } 92. 93. /** 94. Removes a square from the collection. 95. @param s the square to remove 96. */ 97. public void remove(Rectangle2D s) 98. { 99. if (s == null) return; 100. if (s == current) current = null; 101. squares.remove(s); 102. repaint(); 103. } 104. 105. 106. private static final int SIDELENGTH = 10; 107. private ArrayList<Rectangle2D> squares; 108. private Rectangle2D current; 109. // the square containing the mouse cursor 110. 111. private class MouseHandler extends MouseAdapter 112. { 113. public void mousePressed(MouseEvent event) 114. { 115. // add a new square if the cursor isn't inside a square 116. current = find(event.getPoint()); 117. if (current == null) 118. add(event.getPoint()); 119. } 120. 121. public void mouseClicked(MouseEvent event) 122. { 123. // remove the current square if double clicked 124. current = find(event.getPoint()); 125. if (current != null && event.getClickCount() >= 2) 126. remove(current); 127. } 128. } 129. 130. private class MouseMotionHandler 131. implements MouseMotionListener 132. { 133. public void mouseMoved(MouseEvent event) 134. { 135. // set the mouse cursor to cross hairs if it is inside 136. // a rectangle 137. 138. if (find(event.getPoint()) == null) 139. setCursor(Cursor.getDefaultCursor()); 140. else 141. setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR)); 142. } 143. 144. public void mouseDragged(MouseEvent event) 145. { 146. if (current != null) 147. { 148. int x = event.getX(); 149. int y = event.getY(); 150. 151. // drag the current rectangle to center it at (x, y) 152. current.setFrame( 153. x - SIDELENGTH / 2, 154. y - SIDELENGTH / 2, 155. SIDELENGTH, 156. SIDELENGTH); 157. repaint(); 158. } 159. } 160. } 161. } java.awt.event.MouseEvent 1.1
java.awt.event.InputEvent 1.1
java.awt.Toolkit 1.0
java.awt.Component 1.0
Focus EventsWhen you use a mouse, you can point to any object on the screen. But when you type, your keystrokes must go to a specific screen object. The window manager (such as Windows or X Windows) directs all keystrokes to the active window. Often, the active window is distinguished by a highlighted title bar. Only one window can be active at any one time. Now suppose the active window is controlled by a Java program. The Java window receives the keystrokes, and it in turn directs them toward a particular component. That component is said to have focus. Just like the active window is usually distinguished in some way, most Swing components give a visual cue if they currently have focus. A text field has a blinking caret, a button has a rectangle around the label, and so on. When a text field has focus, you can enter text into the text field. When a button has focus, you can "click" it by pressing the space bar. Only one component in a window can have focus. A component loses focus if the user clicks on another component, which then gains focus. The user can also use the TAB key to give focus to each component in turn. This traverses all components that are able to receive input focus. By default, Swing components are traversed from left to right, then top to bottom, as they are laid out in the container. You can change the focus traversal order; see the next chapter for more on this subject. NOTE
Fortunately, most application programmers don't need to worry too much about focus handling. Before JDK 1.4, a common use for trapping component focus events was error checking or data validation. Suppose you have a text field that contains a credit card number. When the user is done editing the field and moves to another field, you trap the lost focus event. If the credit card format was not formatted properly, you can display an error message and give the focus back to the credit card field. However, JDK 1.4 has robust validation mechanisms that are easier to program. We discuss validation in Chapter 9. Some components, such as labels or panels, do not get focus by default because it is assumed that they are just used for decoration or grouping. You need to override this default if you implement a drawing program with panels that paint something in reaction to keystrokes. As of JDK 1.4, you can simply call panel.setFocusable(true); NOTE
In the remainder of this section, we discuss focus event details that you can safely skip until you have a special need that requires fine-grained control of focus handling. In JDK 1.4, you can easily find out
The focused window is usually the same as the active window. You get a different result only when the focus owner is contained in a top-level window with no frame decorations, such as a pop-up menu. To obtain that information, first get the keyboard focus manager: KeyboardFocusManager manager = KeyboardFocusManager.getCurrentKeyboardFocusManager(); Then call Component owner = manager.getFocusOwner(); Window focused = manager.getFocusedWindow(); Window active = manager.getActiveWindow(); // a frame or dialog For notification of focus changes, you need to install focus listeners into components or windows. A component focus listener must implement the FocusListener interface with two methods, focusGained and focusLost. These methods are triggered when the component gains or loses the focus. Each of these methods has a FocusEvent parameter. There are several useful methods for this class. The getComponent method reports the component that gained or lost the focus, and the isTemporary method returns true if the focus change was temporary. A temporary focus change occurs when a component loses control temporarily but will automatically get it back. This happens, for example, when the user selects a different active window. As soon as the user selects the current window again, the same component regains focus. JDK 1.4 introduces the delivery of window focus events. You add a WindowFocusListener to a window and implement the windowGainedFocus and windowLostFocus methods. As of JDK 1.4, you can find out the "opposite" component or window when focus is transferred. When a component or window has lost focus, the opposite is the component or window that gains focus. Conversely, when a component or window gains focus, then its opposite is the one that lost focus. The getOppositeComponent method of the FocusEvent class reports the opposite component, and the getOppositeWindow of the WindowEvent class reports the opposite window. You can programmatically move the focus to another component by calling the requestFocus method of the Component class. However, the behavior is intrinsically platform dependent if the component is not contained in the currently focused window. To enable programmers to develop platform-independent code, JDK 1.4 adds a method requestFocusInWindow to the Component class. That method succeeds only if the component is contained in the focused window. NOTE
NOTE
java.awt.Component 1.0
java.awt.KeyboardFocusManager 1.4
java.awt.Window() 1.0
java.awt.event.FocusEvent 1.1
java.awt.event.WindowEvent 1.4
java.awt.event.WindowFocusListener 1.4
|