CS102: The Java Event Model

Copyright © 1999, Kenneth J. Goldman

Introduction


In our discussion of the awt to this point, we've described how to create, display, and arrange graphical user interface components, but we've completely ignored the problem of handling user input.

In Java, events are handled in terms of event sources and event listeners. An event source is an object that produces an event, and an event listener is an object that wants to be informed when an event occurs.

For example, a button is an event source, and an animation object might be an event listener.

Using an addXXXListener

How do the sources(see example above) know who is interested in their events? For example, how does the button know to inform the animation object when the button is pressed?

The answer is: we have to tell the button who wants to listen for its events. That is, there are two parts to the communication:
  1. The Setup- who is listening to whom
  2. The Event- when sources infrom listeners of the occurence of an event


To handle this, each component has methods that can be used to register objects as listener's for its events. These methods are of the form:

addXXXListener(XXXListener l)

Where XXX describes the kinds of events that are of interest. For example, all Component objects have a method

addMouseMotionListener(MouseMotionListener l)

so if foo is a component and bar wants to know about mouse events in foo, we can say

foo.addMouseMotionListner(bar)

provided that bar implements the MouseMotionListener interface.

Listener Interfaces

If we look as the MouseMotionListener interface, we'll see the following methods:
So, any object that wants to listen for mouse motion events must implement this interface by providing bodies for these methods. The bodies would, typically, say what should happen when the event occurs.

The parameters to the methods in a listener interface are used for passing information to the listener about the event that occured.

For example, a MouseEvent object has getX() and getY() methods for finding out where the mouse was when the event occured.

Most Listener interfaces and Event types are contained in the package java.awt.event see pp.151-153 in the text for a table interfaces in this package.

Event Model Example 1: Registering a Listener

Suppose as part of a user interface we want a text field the user can type into, and we want a button that can be used to reset the text field to a default value, something like this.



Let's define a subclass of Panel with those two components.

      import java.awt.*;
      import jave.awt.event.*;
      
      public class ResettableTextField extends Panel implements ActionListener{
	String defaultText;
	TextField userText;
	Button resetButton;
	
	public ResettableTextField(String defaultText){
	   this.defaultText = defaultText;
           add(userText = new TextField(defaultText));
           add(resetButton = new Button("reset"));
           resetButton.addActionListener(this);
	}
	
	public String getText(){
            return userText.getText();
	}
	
	public void actionPerformed(ActionEvent ae){
            userText.setText(defaultText);
	}
	
     }
	

Event Model Example 2: A Separate Listener

Suppose we want an Animation object that looks like a still picture, but when you click on it, it goes through an animation sequence.

Since the ActionListener interface has only one method, it was a fairly simple matter to define that method and listen for the botton events in the previous examples. However, MouseListener has mouseClicked, mouseEntered, mouseExited, mousePressed, and mouseReleased. So to implement the Mouselistener interface, we must define all of the methods, even if we only care about mouseClicked. This makes us sad :(

However, there is another way. Instead of having to define all the methods of an interface, we can extend and "adapter" class which already defines all the methods of the interface with empty bodies. Then we can simply override the methods we care about. To support this, the java.awt.event package contains adapter classes for all eventListener interfaces with multiple methods. For example, see the mouseAdapter class. This makes us happy. :)

This approach sounds nice, but there is another problem. If our Animation class already extends Component, it can't extend the MouseAdapter beacause a class can only extend one other class - multiple inheritance isn't allowed. So we are sad again. :(

So what is the solution?
Implement the listener object as a seperate class. :)

We could define the following class in the same file as the Animation object, since no other class needs it:

      class AnimationRequestHandler extends Mouse Adapter{
	    Animation targetAnimation;
	    
	    AnimationRequestHandler(Animation a){
              targetAnimation = a;
	      a.addMouseListener(this);
	    }
	    
	    public void mouseClicked(MouseEvent e){
	      targetAnimation.beginAnimation();
            }
	}
      


Another approach would be not to save the Animation object in the AnimationRequestHandler, but instead to use the MouseEvent.getSource() method to determine the source of the event. In this case, the Animation object would probably be the one to call the addMouseListener method, passing an AnimationRequesthandler as the parameter:

      class AnimationRequestHandler extends Mouse Adapter{
	    
	    public void mouseClicked(MouseEvent e){
	      if(e.getSource() instanceof Animation){
	        ((Animation)e).getSource().beginAnimation();
	      } 
            }
	}
      


:) This is nice because it's short (since we subclass adapter) and because it isolates the work of handling the events.

The above solution was nice, but we can do better. One thing to notice about the above solution is that the Aniimation object and AnimationRequestListener are only loosely coupled. Even though they are defined in the same file, they exist as two seperate classes and their objects ahve completely separate identities. The only way one can know about the other is to be told about the other object through a parameter to a constructor or a method.

The above example motivates the need for a way to define classes that have a closer relationship. We'd like to be able to define a class that has an instrinsic relationship to another class, just be virtue of the way its defined.

Java supports several different varieties of such relationships, through a language feature called inner classes.(see next set of lecture notes)