Week 8 - Event Handling

Recommended Reading:

I suggest very strongly that you read the Writing Event Listeners section of the tutorial. I have posted links to specific articles in that section throughout these notes. This part of the tutorial blabs a lot about Swing components, none of you need to stay up late at night studying Swing stuff, but you should get the basics down.

Follow-ups:

Access Modifiers: We've gone over what the notion of public, private, protected, and "naked" access methods in Java means. The skinny on access methods is found in the section of the tutorial entitled Controlling Access to Members of a Class. (FYI: The entry in the table called "package" is the same as what I have described as a "naked" specifier.)

Virtual Methods / Classes: Last week I talked about how all methods in Java are implicitly virtual. There are also these things called pure virtual functions which do not contain a body and are expected to be overridden. The counterpart to the virtual keyword in C++ is abstract in Java.

Applet Debugging: When you are debugging your applets, I suggest that you use the appletviewer tha comes with the JDK. Several of you have already noticed that when you make a change to your applet and recompile it, you still get the old applet displayed no matter how many times you hit "Reload". This is because the init() method (where most of the work of setting up your applet is done) only gets called the first time the applet is loaded. Thereafter, only start() and stop() get called. One approach (read "hack") to making it work would be to put initialization stuff in your start() method, but I wouldn't recommend doing that. Just use the appletviewer instead. Incidentally, that can be done on Zonker via an XDM session (which I will show you how to do in the lab).

Alternatively, holding down 'Shift' while you hit 'Reload' in Netscape will actually re-call your applet's init() method.

Approaches to Event Handling

In all GUI environments, events need to be tracked and handled: Mouse movements, window resizing, controls getting clicked by the user, keypresses, etc. There are basically two ways to go about doing this.

Event Handling the Java Way

There are two components to the Java event mechanism.

Please read the Overview of Event Handling in the tutorial to understand the theory behind the Java event model.

Language Extensions to Facilitate Event Handling

Anonymous Classes: Since the advent of the 1.0 JDK, Java has supported anonymous classes. Simply put, an anonymous class is a class with no name. An example would be the following init() method of an applet.

	public void init() {
		// Set the layout to NESWC
		setLayout(new BorderLayout());
		// Add a label along the top
		add("North", new Label("This Is My Applet"));
		// Add a button along the bottom
		add("South", new Button("Exit"));
		// Add a canvas in the center
		add("Center", new Canvas());
	}

Inner Classes: As of JDK 1.1, inner classes are also supported. This means that you can make a class declaration inside the body of a containing class. Inner classes can "see" all of the methods and members of the outer class.
	class MyClass
	{
		void myMethod() {
			doSomethingNeat();
		}

		class MyInnerClass
		{
			// myMethod() can be called from here
		}
	}
Anonymous, Inner Classes: Naturally, the above two approaches can be combined to create a real abomination.

class MyApplet extends Applet
{
	public void init() {
		Button btn = new Button("Push Me!");
		add(btn);
		btn.addActionListener(

			// The next line creates a new, anonymous, inner class
			new ActionAdapter() { 

				// This next is a method of the anonymous, inner class.
				// It will be called when the button is pushed.
				public void actionPerformed(ActionEvent evt) {
					btn.setText("Thanks!");
				}

			} // This is the end of the class

		): // This is the end of the call to addActionListener()
	}
}

Tricky eh? This feature is actually incredibly useful for handling events. For more info on how that's done, see the tutorial section entitled Using Adapters and Inner Classes to Handle Events.

Types of Event Listeners

As previously mentioned, there are a number of different types of events which can be generated. Correspondingly, there are a variety of different interfaces / objects which can listen for those events. Some of the most common ones are Action listeners, Mouse listeners, Mouse Motion listeners, Window listeners, and Key listeners.

Adapters

Each of the aforementioned 'Listener' interfaces has a corresponding 'Adapter' class that implements the listener and overrides all of the interface's methods with empty function bodies. This is provided as a convenience.

Big Table Listing

The tutorial has a section entitled Handling Common Events that has a huge table which lists all of the various Listeners and Adapters and what events they listen for. Scattered throughout the table is a bunch of Swing Listeners/Adapters. These are easily identified by the javax.swing package declaration. Don't kill yourself over those.

Relationships Between Widgets and Containers

Before we talk about approaches to listening, we need to briefly discuss how "parent" container objects (like applets, frames, etc.) can keep track of their "child" widgets (like buttons, labels, choice lists, etc.), and how child widgets can remember who their parents are.

Has-a Relationship: As discussed earlier, there are three relationships which objects can have: is-a (aka inheritance), has-a (aka containment), or uses (passed to method). In a nutshell, the parent will contain an instance of its children. Additionally, the children might contain a reference to their parent.

Parent-To-Child: The relationship which a parent (applet or frame) should have with it's child widgets is has-a because it contains those widgets.

Example:


class MyApplet extends Applet
{
	Button myButton;
	TextArea myTextArea;

	public void init() {
		myButton = new Button("Push Me!");
		add(myButton);
		myTextArea = new TextArea("Edit Me!");
		add(myTextArea);
	}
.
.
.

This way, the Applet can use / manipulate the widgets in its own methods. For example, you might have a function later on which goes like this:


	void logMessage(String text) {
		myTextArea.append(text);
	}

Child-To-Parent: Very often, you will derive classes of your own from the basic widgets which will be contained in an applet. It can be convenient to keep track of the parent applet for later use. You do this by passing a reference of the parent to the constructor of the child widget.

Example:


class CartesianGraph extends Canvas
{
	MyApplet parent;

	public CartesianGraph(MyApplet applet) {
		parent = applet;
	}
.
.
.

Then, later on, you could have a method in the child widget which does something like this:


	void update() {
		String equation = parent.getEquation();
		drawEquation(equation);
	}

The Problem with Child-To-Parent Containment: The above implementation seems pretty handy, doesn't it? Implementing it this way allows the child widget to respond to its own events, and that kind of containment can be nice. There is one problem with having each of the children keep track of their parents: doing so creates a too-tightly coupled relationship, which becomes a real problem when you want to re-use one of the child widgets in another applet later on. Example:


class DifferentApplet extends Applet
{
	CartesianGraph myGraph;

	public void init() {
		myGraph = new CartesianGraph(this); // DOESN'T WORK!!!
	}
}

That constructor will fail because the types are wrong. Java is a strongly-typed language, and doesn't take it lightly when programmers try to mix-n-match their classes.

So beware of the pitfalls of child-to-parent containment. It can be very convenient, but if you ever need to re-use the widget elsewhere, you'll be pulling your hair out.

The best bet is to make any classes which derive from the basic widgets container agnostic so that they don't get tied too tightly to a particular container. This is accomplished by providing a few, public methods which expose the functionality of your child and having the parent (or some auxiliary class derived from an Adapter) handle the events.

Approaches to Listening

Before you dig into this, you'll probably want to read the section entitled Some Simple Event-Handling Examples from the tutorial, as well as the section entitled General Rules for Writing Event Listeners.

The architecture Java provides for event listening is very flexible (some would say too flexible). Basically, here are the approaches.

  1. Container Class Listens by Implementing an Interface

    With this approach, you have your container class implement a 'Listener' interface which will listen for particular events.

    
    class MyApplet extends Applet
    	implements MouseListener
    {
    	Label lbl;
    
    	public void init() {
    		lbl = new Label("Click Me!");
    		btn.addActionListener(this); // The applet is the action listener
    	}
    
    	// We *must* override all of these as they are methods of the interface
    	public void mousePressed(MouseEvent e) {}
    	public void mouseReleased(MouseEvent e) {}
    	public void mouseEntered(MouseEvent e) {}
    	public void mouseExited(MouseEvent e) {}
    
    	// Even though this is the only one we need
    	public void mouseClicked(MouseEvent e) {
    		lbl.setText("I was clicked!");
    	}
    }
    
    

    Advantages: Very clean and simple
    Drawbacks: You have to override all of the interface's methods, even if you only need to use one or two of them.
    Bottom Line: This is my second-favorite way to write event-handling code. Feel free to use it.

  2. Derived Widget Class Listens by Implementing an Interface

    Similar to the previous example, you have a class derived from a widget implement an interface and provide overrides.

    Advantages: Event-handling code is contained in the widget, which is a good organization.
    Drawbacks: Widget typically needs to contain a reference to the parent container, and then you have the tight-coupling problem.
    Bottom Line: You're better off putting event-handling code closer to the parent container.

  3. Container / Widget Class Derives From an Adapter

    With this approach, you derive a container or widget class from one of the base Adapter classes, overriding methods as needed.

    
    class MyApplet extends MouseAdapter
    	// don't need to implement a MouseListener interface,
    	// cuz it's all handled in the adapter.
    {
    	public void mouseClicked(MouseEvent e) {
    		System.err.println("I was clicked!");
    	}
    
    	// Uh, oh, no init() method...
    }
    
    

    There is a huge, huge problem with doing this though: Because Java only supports single inheritance after you've derived a class from an Adapter, you can't derive from anything else! No Applet, no Frame, nothing. For that matter, in the above example, we did not create an Applet (even though the class is confusingly called 'MyApplet'), we created an Adapter. This is, not coincidentally, one of the big reasons why interfaces were invented.

    Advantages: None.
    Drawbacks: You've used up all your inheritance!
    Bottom Line: Don't ever do this.

  4. Seperate Class Derived From an Adapter Handles Events

    With this approach, you take the derived addapter class above and "register" it with a seperate applet class.

    
    class LabelMouseAdapter extends MouseAdapter
    {
    	Label listenLabel;
    
    	public LabelMouseAdapter(Label lbl) {
    		listenLabel = lbl;
    	}
    
    	public void mouseClicked(MouseEvent e) {
    		listenLabel.setText("I was clicked!");
    	}
    }
    
    class MyApplet extends Applet
    	// No need to implement an interface. The custom adapter we made will
    	// listen for events.
    {
    	public void init() {
    		Label lbl = new Label("Click me!");
    		add(lbl);
    		// This line creates an anonymous class
    		lbl.addMouseListener(new LabelMouseAdapter(lbl));
    	}
    }
    
    

    Advantages: Good organization, event-handling code is seperated from the parent container and the child widget and can be re-used if needed (although reusing event-handling code is pretty rare).
    Drawbacks: Perhaps the biggest drawback is that you'll need to make one derived class for each widget that you want to listen to. This adds up fast. Another possible problem is that the derived Adapter class might need to know about the parent container and the widgets it contains, which again gives you the tightly-coupled problem.
    Bottom Line: This is a pretty good approach. Check first to see if any of the following approaches would work better for you.

  5. Inner Class Derived From an Adapter Handles Events

    Very similar to the previous approach, the only difference is that the derived adapter class is inside the container class.

    
    class MyApplet extends Applet
    {
    	Label myLabel;
    
    	class LabelMouseAdapter extends MouseAdapter
    	{
    		public void mouseClicked(MouseEvent e) {
    			myLabel.setText("I was clicked!");
    		}
    	}
    
    	public void init() {
    		myLabel = new Label();
    		// We are creating a new LabelMouseAdapter as an anonymous class
    		myLabel.addMouseListener(new LabelMouseAdapter());
    	}
    }
    
    

    Advantages: All of the advantages of the previous, with the added bonus that the event-handling code is encapsulated within the container class.
    Drawbacks: The event-handling code cannot be re-used. This is not a big drawback however, as event-handling code is typically very container-specific and not often reused.
    Bottom Line: This is a good approach. Feel free to use it.

  6. Anonymous Inner Class Derived From an Adapter Handles Events

    This approach is the most self-contained, the most cryptic, and definitely the most fun. See the example given above.

    Advantages: Best encapsulation of event-handling code, no extraneous classes to keep track of.
    Drawbacks: Most cryptic and can be difficult to understand. Make sure to include good comments to help you keep track of what's going on. Also, event-handling code can't be reused, but as previously mentioned, it seldom ever is anyway.
    Bottom Line: I like this one the best. Use it and impress your friends.

Example Applet

Lastly, here is a Sample Applet that demonstrates how mouse events work. It uses an anonymous, inner class.

And here is the source.


Changelog:

3/4/99 - Initial revision

3/5/99 - Added Example Applet