This chapter will take us through the creation of several event listeners as we incrementally add functionality and interactivity to the MainFrame application of the previous chapter. Each program in this chapter extends directly or indirectly from MainFrame, inheriting functionality from its ancestors as well as providing additional functionality. This use of inheritance is convenient for this chapter’s purposes and for keeping individual source files small, but an equally viable alternative would be to combine all the inherited code into one source file.
chap12.MainFrame chap13.ListeningMainFrame0 chap13.ListeningMainFrame1 chap13.ListeningMainFrame2 chap13.ListeningMainFrame3 chap13.ListeningMainFrame4 chap13.ListeningMainFrame5 chap13.ListeningMainFrame6 chap13.ListeningMainFrame7
The steps to creating and registering a Listener are always the same and we will follow them with the creation of each class. The first examples will discuss these steps in great detail but as you gain familiarity with the process, we will move quicker and quicker through the steps until, with the creation of the last class, we will breeze quite speedily through the process. Along the way, we will explore various design approaches to implementing an event listener. These design approaches can be applied to any event/listener combination, the choice being determined by factors other than the type of event, listener or component involved.
Steps to Handling an Event:
Step 1. Application Behavior: Determine the desired application behavior from the user’s perspective.
Step 2. The Source: Determine the component that will be the source of the event.
Step 3. The Event: Determine and become familiar with the event type of interest making sure that the source component can respond to it.
Step 4. The Listener: Become familiar with the associated event listener.
Step 5. The Design Approach: Choose a design approach and implement the listener.
Step 6. Registration: Register the listener with the source component.
First we will create the base class for this chapter, ListeningMainFrame0, which extends from MainFrame. ListeningMainFrame0 adds no event-handling code. It just customizes the title of the frame and provides an empty addListeners() method which its constructor calls. Each successive subclass will override the addListeners() method by calling super.addListeners() and adding new event-handling code.
Example 13.1: chap13.ListeningMainFrame0.java
1 package chap13; 2 3 public class ListeningMainFrame0 extends chap12.MainFrame { 4 5 public ListeningMainFrame0() { 6 setTitle("Handling GUI Events"); 7 addListeners(); 8 } 9 protected void addListeners() {} 10 public static void main(String[] arg) { 11 ListeningMainFrame0 frame = new ListeningMainFrame0(); 12 } 13 }
ListeningMainframe1 will handle the menubar.
When the user selects a menu item from the menu bar, we’ll cause a window to appear. It will report which menu item was selected.
The source components are the four JMenuItems: menuItem1, menuItem2, menuItem3 and menuItem4.
Remember that the event must be one to which the component can respond. Table 13-4 lists the EventListener registration methods available to JMenuItem as well as their associated Event types.
Listener Registration Method | Declaring Class | Corresponding Event Type |
---|---|---|
addMenuDragMouseListener() | JMenuItem | MenuDragMouseEvent |
addMenuKeyListener() | JMenuItem | MenuKeyEvent |
addActionListener() | AbstractButton | ActionEvent |
addChangeListener() | AbstractButton | ChangeEvent |
addItemListener() | AbstractButton | ItemEvent |
addAncestorListener() | JComponent | AncestorEvent |
addPropertyChangeListener() | JComponent | PropertyChangeEvent |
addVetoableChangeListener() | JComponent | PropertyChangeEvent |
addContainerListener() | Container | ContainerEvent |
addComponentListener() | Component | ComponentEvent |
addFocusListener() | Component | FocusEvent |
addHierarchyBoundsListener() | Component | HierarchyEvent |
addHierarchyListener() | Component | HierarchyEvent |
addInputMethodListener() | Component | InputMethodEvent |
addKeyListener() | Component | KeyEvent |
addMouseListener() | Component | MouseEvent |
addMouseMotionListener() | Component | MouseEvent |
addMouseWheelListener() | Component | MouseEvent |
Wow, that’s a lot of event types! Many of them are actually generated and sent to the JMenuItem when a user selects it. So what event type should we listen for when the user selects a menu item? Remember, we shouldn’t be interested in how the MenuItem was selected – just that it was selected. Referring back to the “Choosing the Right Event” section, we know that we should look for a semantic event. As with the “OK” button in a Dialog, ActionEvent is the event type that will cover all cases when the menu item is selected — whether by mouse press, key press or voice activation.
Figure 13-5 shows the inheritance hierarchy for the ActionEvent class.
java.util.EventObject java.awt.AWTEvent java.awt.event.ActionEvent
An ActionEvent is a semantic event that indicates that a component-defined action has occurred. Each component that can handle ActionEvents defines for itself what an “action” is. For example, buttons define the click of a mouse to be an action while text fields define the press of the enter-key to be an action. As figure 13-5 shows, Action-Event extends from AWTEvent, which is the root class for all AWT events. In addition to other methods, AWTEvent defines an “id” property. Event classes that extend AWTEvent define unique ids for each event the EventObject can represent, but it’s usually unnecessary to deal with event ids directly. Table 13-5 lists the one event id defined by ActionEvent.
Event ID and Purpose |
---|
public static final int ACTION_PERFORMED = 1001 This indicates that a meaningful action occurred. |
In addition to what it inherits from AWTEvent, the ActionEvent object defines several constants and contains several properties that may be of use in deciding how to handle the event. Some of these are listed in tables 13-6 and 13-7.
Constant Name and Purpose |
---|
public static final int SHIFT_MASK An indicator that the shift key was held down during the event. |
public static final int CTRL_MASK An indicator that the control key was held down during the event. |
public static final int META_MASK An indicator that the meta key was held down during the event. |
public static final int ALT_MASK An indicator that the alt key was held down during the event. |
Property Name and Purpose |
---|
public String getActionCommand() Returns a string that can be used to identify the particular action to take. The value of this string is often populated automatically. In the case of a button or a menu item it is set to the “text” property of the button or menu item. |
public long getWhen() Returns the time the event occurred. |
public int getModifiers() Returns the bitwise-or of the modifier constants associated with the modifier keys held down during this action event. These constants are SHIFT_MASK, CTRL_MASK, META_MASK and ALT_MASK. You could use this to see if the control-key or shift-key was pressed with code such as: int mod = event.getModifiers(); if ((mod & ActionEvent.SHIFT_MASK) != 0) { System.out.println("Shift key was down"); } if ((mod & ActionEvent.CTRL_MASK) != 0) { System.out.println("Control key was down"); } |
Components that respond to ActionEvents include AWT’s List, TextField, Button and MenuItem and Swing’s JFileChooser, JComboBox, JTextField, AbstractButton.
The associated listener is ActionListener which defines the one method listed in table 13-8. Figure 13-6 shows the inheritance hierarchy for the ActionListener interface.
Method Name and Purpose |
---|
public void actionPerformed(ActionEvent) Called whenever an action occurs on the target of the listener. |
java.util.EventListener java.awt.event.ActionListener
When an action occurs on a component, an ActionEvent object is passed as a parameter to the actionPerformed method of every ActionListener that is registered with the component.
In example 13.2, we will create an external class called MyMenuActionListener that implements ActionListener. We will create only one instance of it and add it to all four of our JMenuItems. No matter which JMenuItem is selected, the same instance of MyMenuActionListener will be notified, so we will have to query the ActionEvent for its source to determine which JMenuItem was actually selected. Note that in this particular example, the only reason we get the actual JMenuItem (line 13) is so that we can invoke getText() on it. Invoking the getActionCommand() method directly on the ActionEvent object would have returned the same value as invoking getText() on the JMenuItem.
Example 13.2: chap13.MyMenuActionListener.java
1 package chap13; 2 3 import java.awt.event.ActionEvent; 4 import java.awt.event.ActionListener; 5 6 import javax.swing.JMenuItem; 7 import javax.swing.JOptionPane; 8 9 public class MyMenuActionListener implements ActionListener { 10 11 public void actionPerformed(ActionEvent e) { 12 Object o = e.getSource(); 13 JMenuItem item = (JMenuItem)o; 14 JOptionPane.showMessageDialog( 15 item, 16 "You selected \"" + item.getText() + "\"."); 17 } 18 }
In example 13.3, we register the listener with each JMenuItem via its addActionListener() method.
Example 13.3: chap13.ListeningMainFrame1.java
1 package chap13; 2 3 import java.awt.event.ActionListener; 4 5 public class ListeningMainFrame1 extends ListeningMainFrame0 { 6 7 protected void addListeners() { 8 super.addListeners(); 9 ActionListener myMenuActionListener = new MyMenuActionListener(); 10 menuItem1.addActionListener(myMenuActionListener); 11 menuItem2.addActionListener(myMenuActionListener); 12 menuItem3.addActionListener(myMenuActionListener); 13 menuItem4.addActionListener(myMenuActionListener); 14 } 15 public static void main(String[] arg) { 16 ListeningMainFrame1 frame = new ListeningMainFrame1(); 17 } 18 }
Use the following commands to compile and execute the example. From the directory containing the src folder:
javac –d classes -sourcepath src src/chap13/ListeningMainFrame1.java java –cp classes chap13.ListeningMainFrame1
With ListeningMainFrame2, we will investigate mouse events.
We’ll use the event mechanism to capture all mouse activity in the text area and report it in the yellow label that says “Textarea events will display here” (eventLabel).
The source component is the JTextArea, textArea.
The event we need is MouseEvent whose inheritance hierarchy is shown in figure 13-7. A MouseEvent is a low-level event indicating that a mouse action occurred in a component. All descendants of java.awt.Component can respond to MouseEvents. This event is used for mouse events (press, release, click, enter, exit), mouse motion events (moves and drags) and the mouse-wheel event (the wheel was rotated). In general, a mouse action is considered to occur in a particular component if and only if the mouse cursor was over a visible portion of the component. Bear in mind that portions of a component may not be visible if other components such as windows or menus are obscuring them.
java.awt.event.ComponentEvent java.awt.event.InputEvent java.awt.event.MouseEvent java.awt.event.AWTEvent
MouseEvent inherits from ComponentEvent the convenience method getComponent(), which returns the source of the event as a Component (if it is a Component) or null otherwise. It inherits the methods getModifiers() and getWhen() from InputEvent, which are functionally equivalent to ActionEvent’s methods with the same name. It also inherits some convenience methods from InputEvent that are alternatives to using bit-operations on the result of getModifiers(). These methods are listed in tables 13-9 through 13-11.
Method Name and Purpose |
---|
public Component getComponent() Returns the source of the event if the source is a Component. Otherwise it returns null. |
Method Name and Purpose |
---|
public long getWhen() Functionally equivalent to ActionEvent.getWhen(). |
public int getModifiers() Functionally equivalent to ActionEvent.getModifiers(). |
Method Name and Purpose |
---|
public boolean isAltDown() Returns whether or not the Alt modifier is down on this event. |
public boolean isAltGraphDown() Returns whether or not the Alt-Graph modifier is down on this event. |
public boolean isControlDown() Returns whether or not the Control modifier is down on this event. |
public boolean isMetaDown() Returns whether or not the Meta modifier is down on this event. |
public boolean isShiftDown() Returns whether or not the Shift modifier is down on this event. |
MouseEvent defines several event ids as listed in table 13-12, each of which identifies a different mouse action.
ID Name and Purpose |
---|
public static final int MOUSE_CLICKED = 500 This corresponds to the click of a mouse button. The click of the mouse is a higher level event than the press and release. The user doesn’t actually “click” the mouse. The press and release generate a mouse click if the press and release were at the same coordinate (the mouse didn’t move in between). |
public static final int MOUSE_PRESSED = 501 This corresponds to the press of a mouse button. |
public static final int MOUSE_RELEASED = 502 This corresponds to the release of a mouse button. Whether or not it is generated depends on where the mouse was initially pressed. If the mouse was pressed inside the component then a mouse release will be reported even if it was dragged and rel eased outside the component. On the other hand, it will not be reported if the mouse was initially pressed outside the component and then dragged and released inside the component. |
public static final int MOUSE_MOVED = 503 This corresponds to the movement of the mouse while no mouse buttons were pressed. |
public static final int MOUSE_ENTERED = 504 This indicates that the mouse cursor entered a visible portion of the component. |
public static final int MOUSE_EXITED = 505 This indicates that the mouse cursor exited a visible portion of the component. |
public static final int MOUSE_DRAGGED = 506 This corresponds to the movement of the mouse while a mouse button was pressed. The event will be generated even when the mouse is outside the component as long as the drag was initiated inside the component. |
public static final int MOUSE_WHEEL = 507 This corresponds to the rotation of the mouse wheel. |
MouseEvent defines additional methods that are meaningful specifically to mouse events. These are listed in table 13-13.
Method Name and Purpose |
---|
public int getX() Returns the X-coordinate of the event relative to the source component. |
public int getY() Returns the Y-coordinate of the event relative to the source component. |
public Point getPoint() Returns the X- and Y-coordinates of the event relative to the source component. |
public int getClickCount() Returns the number of mouse clicks associated with this event. The number of mouse clicks is associated with mouse presses, releases and clicks. It is zero for other event types. |
public int getButton() Returns which, if any, of the mouse buttons has been pressed or released. It will return one of the constant integer values (defined by MouseEvent): BUTTON1, BUTTON 2, BUTTON3 or NOBUTTON. |
Because the MouseEvent constants BUTTON1, BUTTON2, BUTTON3 are not self explanatory, I prefer to use some SwingUtilities convenience methods for determining which mouse button is pressed. These are listed in table 13-14.
Method Name and Purpose |
---|
static boolean isLeftMouseButton(MouseEvent) Returns true if the mouse event specifies the left mouse button. |
static boolean isMiddleMouseButton(MouseEvent) Returns true if the mouse event specifies the middle mouse button. |
static boolean isRightMouseButton(MouseEvent) Returns true if the mouse event specifies the right mouse button. |
In order to report all possible mouse event types we will have to implement three listener interfaces. These are MouseListener, MouseMotionListener and MouseWheelListener whose inheritance hierarchy is shown in figure 13-8. Their various method names are obviously derived from the event ids that MouseEvent defines. The event framework automatically calls the correct listener method for the particular event.
java.util.EventListener java.awt.event.MouseListener java.awt.event.MouseMotionListener java.awt.event.MouseWheelListener
MouseListener declares the five methods listed in table 13-15. A MouseEvent is passed as a parameter to each method of a MouseListener that is registered with a component using the addMouseListener() method.
Method | Associated Event ID |
---|---|
public void mousePressed(MouseEvent) | MouseEvent.MOUSE_PRESSED |
public void mouseReleased(MouseEvent) | MouseEvent.MOUSE_RELEASED |
public void mouseClicked(MouseEvent) | MouseEvent.MOUSE_CLICKED |
public void mouseEntered(MouseEvent) | MouseEvent.MOUSE_ENTERED |
public void mouseExited(MouseEvent) | MouseEvent.MOUSE_EXITED |
MouseMotionListener declares the two methods listed in table 13-16. A MouseEvent is passed as a parameter to each method of a MouseMotionListener that is registered with a component using the addMouseMotionListener() method.
Method | Associated Event ID |
---|---|
public void mouseMoved(MouseEvent) | MouseEvent.MOUSE_MOVED |
public void mouseDragged(MouseEvent) | MouseEvent.MOUSE_DRAGGED |
MouseWheelListener declares the one method listed in table 13-17. A MouseEvent is passed as a parameter to the mouseWheelMoved() method of a MouseWheelListener that is registered with a component using the addMouse-WheelListener() method.
Method | Associated Event ID |
---|---|
public void mouseWheelMoved(MouseEvent) | MouseEvent.MOUSE_WHEEL |
You may be wondering why there isn’t just one listener type to handle all mouse events. Well, MOUSE_MOVED and MOUSE_DRAGGED events were separated into the separate listener type MouseMotionListener because tracking the cursor's motion involves significantly more system overhead than tracking other mouse events. And the MOUSE_WHEEL event is handled separately by the MouseWheelListener because it was first introduced in release 1.4. Just to be complete, there is a fourth Listener named MouseInputListener that extends from both MouseListener and MouseMotionListener.
In example 13.4, we create the MyMouseListener class. In order for it to change the text of eventLabel, its methods will need access to MainFrame’s eventLabel. Since the listener methods are only passed a MouseEvent object, we need to provide our listener with an eventLabel reference. There are many ways we might do this. In this example, we’ll pass the label as a parameter to the MyMouseListener constructor so that it can keep a reference to it. If Main-Frame were to ever remove eventLabel or replace it with another component after the construction of this listener, the listener would be stuck with an invalid reference and would no longer function correctly. So, as we develop Main-Frame we must keep this dependency in mind. We have adopted this simple approach because we do not intend for MainFrame to ever change or remove eventLabel.
Example 13.4: chap13.MyMouseListener.java
1 package chap13; 2 3 import java.awt.event.MouseEvent; 4 import java.awt.event.MouseListener; 5 import java.awt.event.MouseMotionListener; 6 import java.awt.event.MouseWheelEvent; 7 import java.awt.event.MouseWheelListener; 8 9 import javax.swing.JLabel; 10 11 public class MyMouseListener 12 implements MouseListener, MouseMotionListener, MouseWheelListener { 13 JLabel eventLabel; 14 15 public MyMouseListener(JLabel eventLabel) { 16 this.eventLabel = eventLabel; 17 } 18 public void mouseClicked(MouseEvent e) { 19 displayEvent(e); 20 } 21 public void mousePressed(MouseEvent e) { 22 displayEvent(e); 23 } 24 public void mouseReleased(MouseEvent e) { 25 displayEvent(e); 26 } 27 public void mouseEntered(MouseEvent e) { 28 displayEvent(e); 29 } 30 public void mouseExited(MouseEvent e) { 31 displayEvent(e); 32 } 33 public void mouseDragged(MouseEvent e) { 34 displayEvent(e); 35 } 36 public void mouseMoved(MouseEvent e) { 37 displayEvent(e); 38 } 39 public void mouseWheelMoved(MouseWheelEvent e) { 40 displayEvent(e); 41 } 42 private String getButtonName(MouseEvent e) { 43 int button = e.getButton(); 44 if (button == MouseEvent.BUTTON1) { 45 return "Button1"; 46 } else if (button == MouseEvent.BUTTON2) { 47 return "Button2"; 48 } else if (button == MouseEvent.BUTTON3) { 49 return "Button3"; 50 } else { 51 return "Unidentified Button"; 52 } 53 } 54 private void displayEvent(MouseEvent e) { 55 String s; 56 int id = e.getID(); 57 if (id == MouseEvent.MOUSE_CLICKED) { 58 s = getButtonName(e) + " mouseClicked[" + e.getClickCount() + "]"; 59 } else if (id == MouseEvent.MOUSE_PRESSED) { 60 s = getButtonName(e) + " mousePressed[" + e.getClickCount() + "]"; 61 } else if (id == MouseEvent.MOUSE_RELEASED) { 62 s = getButtonName(e) + " mouseReleased[" + e.getClickCount() + "]"; 63 } else if (id == MouseEvent.MOUSE_ENTERED) { 64 s = "mouseEntered"; 65 } else if (id == MouseEvent.MOUSE_EXITED) { 66 s = "mouseExited"; 67 } else if (id == MouseEvent.MOUSE_DRAGGED) { 68 s = "mouseDragged"; 69 } else if (id == MouseEvent.MOUSE_MOVED) { 70 s = "mouseMoved"; 71 } else if (id == MouseEvent.MOUSE_WHEEL) { 72 s = "mouseWheelMoved"; 73 } else { 74 s = "Unknown Event"; 75 } 76 77 s += " :" + "(" + e.getX() + ", " + e.getY() + ")"; 78 System.out.println(s); 79 eventLabel.setText(s); 80 } 81 }
The displayEvent() method (lines 54 - 80) prints event information to the console in addition to displaying it in eventLabel because the mouseClicked event is generated so quickly after the mouseReleased event that the label doesn’t have enough time to display the mouseReleased event.
In ListeningMainFrame2, we register the listener with the JTextArea. Even though MyMouseListener implements all three mouse listener types, a component only reports events to a listener based on which registration methods are used. So, we must register myMouseListener three times. If after running ListeningMainFrame2 you get tired of all the mouseMoved and mouseDragged reporting, you can simply comment out line 9 which registers the listener as a MouseMotionListener.
Example 13.5: chap13.ListeningMainFrame2
1 package chap13; 2 3 public class ListeningMainFrame2 extends ListeningMainFrame1 { 4 5 protected void addListeners() { 6 super.addListeners(); 7 MyMouseListener myMouseListener = new MyMouseListener(eventLabel); 8 textArea.addMouseListener(myMouseListener); 9 textArea.addMouseMotionListener(myMouseListener); 10 textArea.addMouseWheelListener(myMouseListener); 11 } 12 public static void main(String[] arg) { 13 ListeningMainFrame2 frame = new ListeningMainFrame2(); 14 } 15 }
Use the following commands to compile and execute the example. From the directory containing the src folder:
javac –d classes -sourcepath src src/chap13/ListeningMainFrame2.java java –cp classes chap13.ListeningMainFrame2
With ListeningMainFrame3, we will investigate key events. We will also encounter our first application of an inner class.
We’ll use the event mechanism to capture keyboard activity in the text area and report it in the same JLabel used for reporting mouse events (eventLabel).
The source component is again the JTextArea, textArea.
We are interested in KeyEvent whose inheritance hierarchy is shown in figure 13-9. A KeyEvent is a low-level event indicating that a key was pressed, released or typed in a component. All descendants of java.awt.Component can respond to KeyEvents. In order to receive KeyEvents, a component must have the keyboard focus. Whether or not a component has keyboard focus is beyond the scope of this chapter to discuss fully. In general, text fields and text areas will have the keyboard focus when they have been clicked with a mouse or when the press of the tab key brings the keyboard focus to them. KeyEvent defines the three event ids listed in table 13-18 and the three methods listed in table 13-19.
java.awt.AWTEvent java.awt.event.ComponentEvent java.awt.event.InputEvent java.awt.event.KeyEvent
ID Name and Purpose |
---|
public static final int KEY_TYPED = 400 This KeyEvent is reported to a component when a character is entered. The typing of a character is higher-level than key presses and releases and doesn’t generally depend on the platform or keyboard layout. It is often produced by a single key press but it can be produced by a combination of key presses (as in ‘shift’ + ‘a’). Key typed events are only generated for keys that produce Unicode characters. That excludes keys like modifier keys or arrow keys. |
public static final int KEY_PRESSED = 401 This corresponds to the press of a key. This KeyEvent is generated repeatedly when a key is held down. If the key press signifies a Unicode character such as the letter ‘a’ for example then a key typed event is also generated. If, on the other hand, the key pressed was the shift-key or some other key that by itself does not signify a Unicode character, then no key typed event is generated. |
public static final int KEY_RELEASED = 402 This corresponds to the release of a key. Key releases are not usually necessary to generate a key typed event, but there are some cases where the key typed event is not generated until a key is released (e.g., entering ASCII sequences via the Alt-Numpad method in Windows). |
Method Name and Purpose |
---|
public char getKeyChar() Returns the character associated with the key in this event. The getKeyChar() method always returns a valid Unicode character or CHAR_UNDEFINED. |
public int getKeyCode() Returns the integer keyCode associated with the key in this event. For key pressed and key released events, the getKey-Code method returns the event's keyCode. This value will be one of the many constants with names like VK_XXX which are defined by KeyEvent. Calling this method is the only way to find out about keys that don't generate character input (e.g., action keys, modifier keys, etc.). For key typed events, the getKeyCode() method always returns VK_UNDEFINED. |
public int getKeyLocation() Returns the location of the key that originated a key press or key released event. This provides a way to distinguish keys that occur more than once on a keyboard, such as the two shift keys, for example. The possible values are KEY_LOCATION_STANDARD, KEY_LOCATION_LEFT, KEY_LOCATION_RIGHT, KEY_LOCATION_NUMPAD, or KEY_LOCATION_UNKNOWN. This method always returns KEY_LOCATION_UNKNOWN for key-typed events. |
The Listener we need is KeyListener whose inheritance hierarcy is shown in figure 13-10. KeyListener declares the three methods listed in table 13-20 whose names are obviously derived from the event ids that KeyEvent defines. Every time a key event occurs on a component, a KeyEvent object is passed as a parameter to the appropriate method of every KeyListener that is registered with that component.
java.util.EventListener java.awt.event.KeyListener
Method | Associated Event ID |
---|---|
public void keyTyped(KeyEvent) | KeyEvent.KEY_TYPED |
public void keyPressed(KeyEvent) | KeyEvent.KEY_PRESSED |
public void keyReleased(KeyEvent) | KeyEvent.KEY_RELEASED |
Just as did MyMouseListener, MyKeyListener will need access to eventLabel. Many times, an EventListener class needs access to fields or methods of an object (in our case MainFrame) that would best be left private or protected. If, as is often the case, that listener will only ever be created or referenced by that same object, then it may be preferable to declare the listener as an inner class of the object, thereby giving it automatic access to all the object’s fields and simplifying access issues such as pointed out in MyMouseListener. So, unlike MyMouseListener, MyKey-Listener will be an inner class of MainFrame and will not need any special coding to access eventLabel. Nor need it ever worry that eventLabel might ever change. It will always have a valid reference to eventLabel.
MyKeyListener will be an inner class of ListeningMainFrame3, and ListeningMainFrame3 will be its enclosing class. We will define it at the same level as methods and fields of ListeningMainFrame3, effectively making it a property of the ListeningMainFrame3 object. Consequently, it will have the same accessibility as other class fields and methods of ListeningFrame3. By declaring the MyKeyListener class to be private, its visibility will be restricted to ListeningFrame3 only. If it were declared protected, it would be visible only to ListeningMainFrame3 and its subclasses. If it were declared public or given package protection, then other external classes would be able to reference it.
An inner class is referenced by a non-enclosing class like this: “EnclosingClassName.InnerClassName”. So, if we were to declare MyKeyListener as a public inner class (we won’t), then an external class could contain statements such as
ListeningMainFrame3 frame = new ListeningMainFrame3(); ListeningMainFrame3.MyKeyListener keyL = frame.new MyKeyListener();
If ListeningMainFrame3.MyKeyListener were a static class, then it wouldn’t be necessary to have an instance of ListeningMainFrame3 from which to create the listener instance. Code such as this could be used:
ListeningMainFrame3.MyKeyListener keyL = new ListeningMainFrame3.MyKeyListener();
Within the body of an inner class, the reserved word “this” refers to the inner class instance. An inner class can refer to its enclosing class instance with the notation <EnclosingClassName>.this. As an example, MyKeyListener can refer to its enclosing ListeningMainFrame3 class instance as “ListeningMainFrame3.this”.
Event information is printed to the console, in addition to being displayed in eventLabel, because the key released event often follows so quickly after the key typed event that the label doesn’t have enough time to display the key typed event.
Registering the key listener is handled by line 11 of example 13.6.
Example 13.6: chap13.ListeningMainFrame3.java
1 package chap13; 2 3 import java.awt.event.KeyEvent; 4 import java.awt.event.KeyListener; 5 6 public class ListeningMainFrame3 extends ListeningMainFrame2 { 7 8 protected void addListeners() { 9 super.addListeners(); 10 KeyListener myKeyListener = new MyKeyListener(); 11 textArea.addKeyListener(myKeyListener); 12 } 13 protected class MyKeyListener implements KeyListener { 14 //implementing KeyListener 15 public void keyTyped(KeyEvent e) { 16 String display = "keyTyped: " + String.valueOf(e.getKeyChar()); 17 System.out.println(display); 18 eventLabel.setText(display); 19 } 20 public void keyPressed(KeyEvent e) { 21 String display = "keyPressed: " + String.valueOf(e.getKeyChar()); 22 System.out.println(display); 23 eventLabel.setText(display); 24 } 25 public void keyReleased(KeyEvent e) { 26 String display = "keyReleased: " + String.valueOf(e.getKeyChar()); 27 System.out.println(display); 28 eventLabel.setText(display); 29 } 30 } 31 public static void main(String[] arg) { 32 ListeningMainFrame3 frame = new ListeningMainFrame3(); 33 } 34 }
Use the following commands to compile and execute the example. From the directory containing the src folder:
javac –d classes -sourcepath src src/chap13/ListeningMainFrame3.java java –cp classes chap13.ListeningMainFrame3
Incidentally, although the MyKeyListener class is internal to ListeningMainFrame3, the compiler generates a separate class file for it. Look at the generated class files and you should see a file with a name like ListeningMainFrame3$MyKeyListener.class corresponding to the inner class! Its name clearly reflects its nested nature.
With ListeningMainFrame4, we will employ another type of inner class.
When the user clicks on the “Choose Background Color” button we will popup a JColorChooser to allow the user to change the background color of one of MainFrame’s panels. When the user clicks the “Default Background Color” button, the background will return to its default color.
The source components are the two JButtons, bgColorButton and defaultColorButton.
We could of course choose the MouseEvent, but as you understand by now, it would be better to use the ActionEvent.
This will be the ActionListener.
While we’re on the subject of creating inner classes, let’s try another way to create an inner class. In the previous approach, we declared MyKeyListener at the same level as class fields and methods are declared. We can also declare a class within the body of a method at the local variable scope. A class declared this way is visible only within the body of the method, and neither an external class nor its enclosing class can reference it. The inner class can still refer to its enclosing class in the same manner as do field-level inner classes. We will create an inner class at the local variable level in lines 14 - 32 of example 13.7.
Example 13.7: chap13.ListeningMainFrame4.java
1 package chap13; 2 3 import java.awt.Color; 4 import java.awt.event.ActionEvent; 5 import java.awt.event.ActionListener; 6 7 import javax.swing.JColorChooser; 8 9 public class ListeningMainFrame4 extends ListeningMainFrame3 { 10 11 protected void addListeners() { 12 super.addListeners(); 13 14 class ColorButtonListener implements ActionListener { 15 public void actionPerformed(ActionEvent e) { 16 Object o = e.getSource(); 17 18 if (o == bgColorButton) { 19 Color color = displayOptionsPanel.getBackground(); 20 color = 21 JColorChooser.showDialog( 22 bgColorButton, 23 "Choose Background Color", 24 color); 25 if (color != null) { 26 displayOptionsPanel.setBackground(color); 27 } 28 } else if (o == defaultColorButton) { 29 displayOptionsPanel.setBackground(null); 30 } 31 } 32 }; 33 34 ColorButtonListener colorButtonListener = new ColorButtonListener(); 35 bgColorButton.addActionListener(colorButtonListener); 36 defaultColorButton.addActionListener(colorButtonListener); 37 } 38 public static void main(String[] arg) { 39 ListeningMainFrame4 frame = new ListeningMainFrame4(); 40 } 41 }
Registration is handled by lines 35 and 36 of example 13.7.
Use the following commands to compile and execute the example: From the directory containing the src folder:
javac –d classes -sourcepath src src/chap13/ListeningMainFrame4.java java –cp classes chap13.ListeningMainFrame4
The compiler generates a separate class file for any inner class. Look at the generated class files and you should see a file with a name like ListeningMainFrame4$1$ColorButtonListener.class corresponding to our inner class.
ListeningMainFrame5 will add lots of interactivity to our application without creating any additional class at all.
This time we will add the following interactivity to our application.
The five radio buttons will change the border of one of MainFrame’s JPanels (displayOptionsPanel).
The four checkboxes will add or remove items from the JList (saladList).
The combo box will set the font style of the JTextArea (textArea).
The “Lock Text Area” button will toggle the JTextArea’s (textArea) editability.
The “Enter Secret Code” field will compare its text to the JTextField’s (chosenItemTextField) text and popup a message.
The source components are the five JRadioButtons (titleBorderRadioButton, lineBorderRadioButton, etchedBorderRadioButton, bevelBorderRadioButton and noBorderRadioButton), the four JCheckBoxes (vegetablesCheckBox, fruitsCheckBox, nutsCheckBox and cheesesCheckBox), the JToggleButton (lockingToggleButton), the JComboBox (fontStyleComboBox) and the JPasswordField (secretCodeField).
We’ll listen for ActionEvents in all cases.
We’ll use ActionListener again, of course.
Creating an inner class is certainly convenient (and we will revisit that approach again later with a twist), but if all we’re doing is implementing an interface, we don’t really need to create a new class at all. We could take any existing class and use it as a listener simply by having it implement whatever EventListener interfaces we need. In this example, we’ll have ListeningMainFrame5 implement ActionListener. Its actionPerformed method will query the ActionEvent object to find out what component generated the event.
Using an existing class is perhaps the most efficient way to approach the implementation of an event listener as there is always a bit of overhead with the loading of new class type. However, it can lead to long method definitions, and it tends to clump lots of code together that might be more elegantly expressed separately.
Registration is handled by the addListeners method (lines 20 - 39) of example 13.8. It might seem a little strange to pass “this” as the parameter to the addActionListener() methods but it’s perfectly legitimate. Once again, the design approach has changed, but we’re still just implementing an EventListener interface and registering the implementing object (“this” in this case) with the components.
Example 13.8: chap13.ListeningMainFrame5.java
1 package chap13; 2 3 import java.awt.Color; 4 import java.awt.Font; 5 import java.awt.event.ActionEvent; 6 import java.awt.event.ActionListener; 7 import java.util.List; 8 9 import javax.swing.JCheckBox; 10 import javax.swing.JOptionPane; 11 import javax.swing.border.BevelBorder; 12 import javax.swing.border.EtchedBorder; 13 import javax.swing.border.LineBorder; 14 import javax.swing.border.TitledBorder; 15 16 public class ListeningMainFrame5 17 extends ListeningMainFrame4 18 implements ActionListener { 19 20 protected void addListeners() { 21 super.addListeners(); 22 23 titleBorderRadioButton.addActionListener(this); 24 lineBorderRadioButton.addActionListener(this); 25 etchedBorderRadioButton.addActionListener(this); 26 bevelBorderRadioButton.addActionListener(this); 27 noBorderRadioButton.addActionListener(this); 28 29 vegetablesCheckBox.addActionListener(this); 30 fruitsCheckBox.addActionListener(this); 31 nutsCheckBox.addActionListener(this); 32 cheesesCheckBox.addActionListener(this); 33 34 lockingToggleButton.addActionListener(this); 35 36 fontStyleComboBox.addActionListener(this); 37 38 secretCodeField.addActionListener(this); 39 } 40 public void actionPerformed(ActionEvent e) { 41 Object o = e.getSource(); 42 43 //borders 44 if (o == titleBorderRadioButton) { 45 displayOptionsPanel.setBorder(new TitledBorder("Panel Options")); 46 System.out.println(displayOptionsPanel.getInsets()); 47 } else if (o == lineBorderRadioButton) { 48 displayOptionsPanel.setBorder(new LineBorder(Color.blue, 10)); 49 System.out.println(displayOptionsPanel.getInsets()); 50 } else if (o == etchedBorderRadioButton) { 51 displayOptionsPanel.setBorder(new EtchedBorder()); 52 System.out.println(displayOptionsPanel.getInsets()); 53 } else if (o == bevelBorderRadioButton) { 54 displayOptionsPanel.setBorder(new BevelBorder(BevelBorder.LOWERED)); 55 System.out.println(displayOptionsPanel.getInsets()); 56 } else if (o == noBorderRadioButton) { 57 displayOptionsPanel.setBorder(null); 58 System.out.println(displayOptionsPanel.getInsets()); 59 60 //lock/unlock the textarea 61 } else if (o == lockingToggleButton) { 62 boolean selected = lockingToggleButton.isSelected(); 63 textArea.setEditable(!selected); 64 textArea.setOpaque(!selected); 65 66 //update the list 67 } else if ( 68 o == vegetablesCheckBox 69 || o == fruitsCheckBox 70 || o == nutsCheckBox 71 || o == cheesesCheckBox) { 72 boolean selected = ((JCheckBox)o).isSelected(); 73 List items = null; 74 if (o == vegetablesCheckBox) { 75 items = vegetables; 76 } else if (o == fruitsCheckBox) { 77 items = fruits; 78 } else if (o == nutsCheckBox) { 79 items = nuts; 80 } else if (o == cheesesCheckBox) { 81 items = cheeses; 82 } 83 if (selected) { 84 saladListItems.addAll(items); 85 } else { 86 saladListItems.removeAll(items); 87 } 88 saladList.setListData(saladListItems); 89 90 //change the font style 91 } else if (o == fontStyleComboBox) { 92 int fontStyle = Font.PLAIN; 93 int i = fontStyleComboBox.getSelectedIndex(); 94 if (i == 0) { 95 fontStyle = Font.PLAIN; 96 } else if (i == 1) { 97 fontStyle = Font.BOLD; 98 } else if (i == 2) { 99 fontStyle = Font.ITALIC; 100 } else if (i == 3) { 101 fontStyle = Font.BOLD | Font.ITALIC; 102 } 103 textArea.setFont(textArea.getFont().deriveFont(fontStyle)); 104 105 //compare secretCodeField to chosenItemTextField 106 } else if (o == secretCodeField) { 107 String message; 108 int messageType; 109 if (secretCodeField 110 .getPassword() 111 .equals(chosenItemTextField.getText())) { 112 message = "You guessed the secret code!"; 113 messageType = JOptionPane.INFORMATION_MESSAGE; 114 } else { 115 message = "That was not the secret code."; 116 messageType = JOptionPane.ERROR_MESSAGE; 117 } 118 JOptionPane.showMessageDialog( 119 secretCodeField, 120 message, 121 "Results", 122 messageType); 123 } 124 } 125 public static void main(String[] arg) { 126 ListeningMainFrame5 frame = new ListeningMainFrame5(); 127 } 128 }
Use the following commands to compile and execute the example. From the directory containing the src folder:
javac –d classes -sourcepath src src/chap13/ListeningMainFrame5.java java –cp classes chap13.ListeningMainFrame5
With ListeningMainFrame6, we will investigate an event type that does not extend from AWTEvent.
The slider in the font panel will set the font size for text in the JTextArea (textArea).
This time the source component is the JSlider fontSizeSlider.
ChangeEvent is the event of choice. It is definitely a semantic event and is the first event we have dealt with that doesn’t extend from AWTEvent. ChangeEvent extends directly from EventObject (as shown in figure 13-11) and defines no methods or fields of its own. It is expected that you would query the object returned by its getSource() method or have some other means for discerning what the change is. Components that respond to ChangeEvents define for themselves what signifies a “change”. In the case of the JSlider, moving the knob generates a ChangeEvent. JComponents that respond to ChangeEvents include JSlider, JProgressBar, JSpinner, JTabbedPane, JViewport and AbstractButton.
java.util.EventObject javax.swing.event.ChangeEvent
The associated listener is ChangeListener whose inheritance hierarchy is shown in figure 13-12. ChangeListener defines one method (listed in table 13-21) which is called whenever a change event occurs.
java.util.EventListener javax.swing.event.ChangeListener
Method Name and Purpose |
---|
public void stateChanged(ChangeEvent) Called when the target of the listener has changed its “state”. |
An anonymous class is a class that cannot be referred to – not because of visibility issues but because it is never assigned a name. This is often an attractive approach when it comes to creating listeners. In this example we will implement ChangeListener in an anonymous class.Notice how ListeningMainFrame6 implements the ChangeListener interface without ever assigning a class name to it. The variable myChangeListener is an instance of a new class whose entire reason for existence is to be assigned to the myChangeListener instance variable.
Registration is handled by line 23 of example 13.9.
Example 13.9: chap13.ListeningMainFrame6.java
1 package chap13; 2 3 import java.awt.Font; 4 5 import javax.swing.JSlider; 6 import javax.swing.event.ChangeEvent; 7 import javax.swing.event.ChangeListener; 8 9 public class ListeningMainFrame6 extends ListeningMainFrame5 { 10 11 protected void addListeners() { 12 super.addListeners(); 13 ChangeListener myChangeListener = new ChangeListener() { 14 public void stateChanged(ChangeEvent e) { 15 JSlider slider = (JSlider)e.getSource(); 16 int value = slider.getValue(); 17 sliderLabel.setText(String.valueOf(value)); 18 Font font = textArea.getFont(); 19 textArea.setFont(font.deriveFont((float)value)); 20 } 21 }; 22 23 fontSizeSlider.addChangeListener(myChangeListener); 24 } 25 public static void main(String[] arg) { 26 ListeningMainFrame6 frame = new ListeningMainFrame6(); 27 } 28 }
Use the following commands to compile and execute the example. From the directory containing the src folder:
javac –d classes -sourcepath src src/chap13/ListeningMainFrame6.java java –cp classes chap13.ListeningMainFrame6
Even though our listener class is “anonymous”, the compiler gives it a name when it generates the class file for ListeningMainFrame6. If you’re curious, look at the generated class files and you will see a file named something like ListeningMainFrame6$1.class.
ListeningMainFrame7 will introduce another event that doesn’t extend from AWTEvent. It will also employ the most syntactically concise design approach.
When the user selects an item in the “Salad Options” list, that item’s name will become the text for the JTextField (chosenItemField).
The source component is the JList saladList.
ListSelectionEvent is the event of choice. Its inheritance hierarchy is shown in figure 13-13. This semantic event is generated when there has been a change in the current selection of a JTable or JList. When it is generated, the selection state may have changed for one or more rows in the list or table. Similarly to ChangeEvent, you have to query the source to obtain more information than the ListSelectionEvent instance contains. ListSelectionEvent defines the three methods listed in table 13-22.
java.util.EventObject javax.swing.event.ListSelectionEvent
Method Name and Purpose |
---|
public int getFirstIndex() Returns the index of the first row whose selection may have changed. |
public int getLastIndex() Returns the index of the last row whose selection may have changed. |
public boolean getValueIsAdjusting() Returns true if this is one of multiple change events. |
The corresponding listener is the ListSelectionListener whose inheritance hierarchy is shown in figure 13-14.
java.util.EventListener javax.swing.event.ListSelectionListener
ListSelectionListener defines one method which is called whenever a ListSelectionEvent occurs. This method is listed in table 13-23.
Method Name and Purpose |
---|
public void valueChanged(ListSelectionEvent e) Called whenever the value of the selection changes. |
Finally, in our exploration of design approaches, we will create an anonymous listener class whose instance is also anonymous. We don’t need to assign a name to an instance variable if we’re only going to refer to it long enough to pass it once as a parameter. So, in this last design approach we create the listener class and its instance both anonymously. This is a wonderfully concise and frequently appropriate design approach.
Registration is handled in place by line 10 of example 13.10. The use of anonymous inner classes can be helpful once you get used to the syntax because the listener instance is defined in the very same place as it is used.
Example 13.10: chap13.ListeningMainFrame7.java
1 package chap13; 2 3 import javax.swing.event.ListSelectionEvent; 4 import javax.swing.event.ListSelectionListener; 5 6 public class ListeningMainFrame7 extends ListeningMainFrame6 { 7 8 protected void addListeners() { 9 super.addListeners(); 10 saladList.addListSelectionListener(new ListSelectionListener() { 11 public void valueChanged(ListSelectionEvent e) { 12 chosenItemTextField.setText((String)saladList.getSelectedValue()); 13 } 14 }); 15 } 16 public static void main(String[] arg) { 17 ListeningMainFrame7 frame = new ListeningMainFrame7(); 18 } 19 }
Use the following commands to compile and execute the example. From the directory containing the src folder:
javac –d classes -sourcepath src src/chap13/ListeningMainFrame7.java java –cp classes chap13.ListeningMainFrame7