The new event handling model in Java 1.1

The 1.0 model

In the original release of Java (1.0.2 was the last version) event handling followed a simple model where events received by a component could be handled by that component or, by default, passed up the container hierarchy. In order for a component to handle an event, it was necessary to define a subclass of the component class so that the handleEvent method (or any of the special handlers, such as action, or mouseDown) could be overridden. Unless this was done, events simply could not be trapped at all, since the default versions of the methods did nothing. Below is the relevant part of the "sentence" applet concerning event handling.

public class Sentence extends Applet {

Button next = new Button("Next");;

  . . .
  public boolean action(Event event, Object arg) {
    if (event.target == next) {
      buffer.setLength(0);
      start();
      repaint();
    }
    return true;
  }
  . . .
}

In this code, the class Applet is a subclass of Panel, a component, so the method action defined as above, overrides the default in class Panel.

The reason this works is because when the button is pressed, it cannot handle its own event, so the AWT passes the event to its parent, the applet Sentence. Here the source of the event is tested (the button called next) and the event is handled appropriately.

The 1.1 model

In the new model, components do not need to be subclassed as in the old model. Now any class may become an event listener, by being registered with a component that can receive an event. In order for a class to be a listener, it has to implement the appropriate listener interface. Events are classified by a combination of their source (the type of AWT object) and their nature. Buttons generate ActionEvents and a class that implements the ActionListener interface (actually just the single method actionPerformed) can listen for button events from that button. A simple way to implement the idea in our example is to register the applet itself as the listener, but it could be any class.

public class Sentence extends Applet implements ActionListener {
  StringBuffer buffer = new StringBuffer();
  Button next = new Button("Next");
  . . .
  public void init() {
    next.setActionCommand("next");
    next.addActionListener(this);
      . . .
  }
  public void actionPerformed(ActionEvent event) {
    if (event.getActionCommand().equals("next")) {
      buffer.setLength(0);
      start();
      repaint();
    }
  }
}

 

In this code, the init method does the registration of the listener. The registration method is named after the type of listener, so the Button class has the addActionListener method, and we pass this to it, to register the applet as the listener object. We then implement the actionPerformed method from the ActionListener interface in order to handle the event. Note that the handler is passed a specifically typed event. The source of the event may be determined from a name set in init. Here the button is called "next" (note this is not the label displayed on the button) by the method setActionCommand. The handler can retrieve this name using the getActionCommand method.

Although this code works well, it is more usual to define an entirely new object to be the listener. This can be conveniently done with a local class definition, as below:

public class Sentence3 extends Applet {
  StringBuffer buffer = new StringBuffer();
  Button next = new Button("Next");;
  . . .

  public void init() {
    class ButtonListener implements ActionListener {
      public void actionPerformed(ActionEvent event) {
	buffer.setLength(0);
	start();
	repaint();
      }
    }
    
    next.addActionListener(new ButtonListener());
    . . .
  }
}

This code is cleaner because the event handling is separated from the real application code. It works because the methods of the local class ButtonListener have access to any objects or methods outside its scope just like methods of the class it is contained within.

A further refinement is to make the local class anonymous:

public class Sentence3 extends Applet {
  StringBuffer buffer = new StringBuffer();
  Button next = new Button("Next");;
  . . .

  public void init() {
    next.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent event) {
	  buffer.setLength(0);
	  start();
	  repaint();
        }
      });
    . . .
  }
}

 

Here, the addActionListener method is passed an object created from the ActionListener interface directly, with the actionPerformed method defined within it.

Changing from 1.0 event handling to 1.1

Unfortunately, there is no easy way to change a program from 1.0 event handling to the 1.1 style, and the two certainly cannot be mixed in the same program. Probably the simplest change is from an applet in which all events are handled by the applet itself in a single handleEvent method. By implementing the appropriate interface or interfaces, naming the components, and distributing the code in handleEvent among the appropriate listener methods, the changeover might may made a little simpler. The best way is obviously to start from scratch using the 1.1 model, and there will be no confusion.