Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / Java

Defining Custom Source-Event-Listener in Java

4.87/5 (10 votes)
17 Apr 2015CPOL5 min read 74.4K   769  
Event-Driven Programming Model

* Note: updated new source-code (SpeedEvent does NOT need to extend EventObject, my fault!)

Download:
Source-code [NEW]
Source-code [OLD]

Background

* If you are already familiar with standard Java GUI event-listener pair, you might skip this and go to Define custom source, event and listener.

With Java (especially GUI) programs, the user might interact with a button, checkbox, car, or bank account. The program decides either to ignore or to respond to when such cases happen. For example, if we want to do something when a user clicks a button, then we need to identify 3 things: (1) the button itself, (2) the event when the button is clicked, and (3) the responding code that is interested in the button-clicked event (called 'listener').

In java, the event when the button is clicked is called ActionEvent. When such the event happens, the listener that is interested in that ActionEvent, which is called ActionListener, contains the method (called 'handler') to be invoked. Again, Java pre-defines this handler method as actionPerformed(ActionEvent e). It was possible because the ActionListener had been registered to 'listen' to the ActionEvent by the button.

Image 1

The Event-Driving Programming Model in Java consists of 3 type objects:

  1. The source
  2. The event
  3. The listener

 

The source object (a JCheckbox, for example) fires an event object (of type ItemEvent, for example) when the user checks or un-checks the checkbox.

The event object contains information about its source object.

The listener object is an interface that must be registered as a 'listener' by the source object to be able to respond when the event object was fired, and invoke the the handler method of the listener object.

* Note: a source object can fire one or multiple events, and one or multiple listeners can be registered by a source object. Also, a listener can declare one or multiple handlers. For example, Java has provided standard models:
- A JCheckBox can fire both ActionEvent and ItemEvent when the user checks or un-checks the checkbox. The corresponding listener interface is ActionListener (with the handler method actionPerformed(ActionEvent e)), and ItemListener (with the handler itemStateChanged(ItemEvent e)).
- A Component (JTextArea, or JLabel) can fire MouseEvent or KeyEvent when the user has pressed, clicked, moved, exited mouse, pressed or typed key on/from that component. Corresponding listener interface is MouseLister (which defines several handlers, such as mousePressed(MouseEvent e), and mouseEntered(MouseEvent e)), and KeyListener (with pre-defined handlers).

Custom source, event and listener

Java has already provided many standard components with event-listener pair. But we want to define our own because it is better suited with our application. For example, a bank account can fire a custom event BalanceEvent when the customer widthraws too much from his/her account. For our custom BalanceListener's handler balanceViolated(BalanceEvent e) responds appropriately, it should be able to know how much the current balance is, the minimum amount that the account holder to maintain, and the date and time of that attempted transaction. That means the BalanceEvent contains the data needed for the handler. This is not possible with pre-defined Java event-listener pairs.

When defining our own event-listener pair, we should definitely follow the naming convention:
XEvent - XListener (where X can be 'Action', 'Mouse', 'Account', 'Balance', 'Speed', etc.)

Custom XEvent

Java
class XEvent { // extends java.util.EventObject
	// Data that are associated with the source object
	String customer;
	int balance;
	Date dateWidthraw;
	
	// an optional object argument, representing the source object
	// Other optional arguments are information related to the source object
	public XEvent(/*Object source, */ String customer, int balance, Date date,...) {
		// super(source);
		// code to set the associated data here
	}
	
}

* Note: the constructor of the custom XEvent: it's how the source object pass the data to the fired event.

Custom XListener

Java
interface XListener { // extends java.util.EventListener
	// It's up to the client code to decide how to implement the handler(s)
	void handlerMethod1(XEvent e);
	void handlerMethod2(XEvent e);
}

The Source

Java
class Source {
	private XListener listener; 
	// or more flexible, the Source should maintain a list (collection) of XListeners
	
	// The Source must have code to register/de-register the XListener
	void addXListener(XListener myListener) 
	// code to add myListener to the list of XListeners
	// the same for removeXListener method
	
	// When a condition happens, source object fires the event
	XEvent myEvent = new XEvent(this, param1, param2,...)
	// 'this' is the source object that fires myEvent
	// param1, param2,... are date describe the status, what is going on to the event
	listener.handlerMethod(myEvent) // invoke the handler(s) 
}

As we see, the source object can maintain a list of listeners. When a certain condition happends (such as low balance on the bank account, or a biology class is full and cannot take more students), the source object invokes the handler(s) method on its listeners. Since we pass the XEvent object to these handler(s), the handler(s) can respond appropriately. For example, informing the client code the current balance, attempted ammount to widraw on what date.

The client code (with main method)

Java
// The client code that makes use of custom Source, XEvent-XListener pair
public class Program {
	// Create a source object that can fire an XEvent
	// source object can be a bank account, a course in college, or a car
	Source source = new Source(); 
	
	XListener listener = new ClassThatImplementsXListener(); 
	// listener(s) interested in the XEvent, and contains the handler(s)
	// to be invoked when such event occurs
	// ClassThatImplementsXListener is an 'inner' class of the class Program
	// to implement the handler(s) in a custom way based on our need
	
	source.addXListener(listener);
	// Without this registering, nothing will happend!!
	
	// method on the source object here that causes source to fire the event
	// for example:
	source.widthDraw(9000); // for bank account object or
	source.speedUp(100); // for car object
	// After this method, the source delegates the event fired 
	// to the listener for processing (invoking the handle(s))	
	
	// Inner class
	private class ClassThatImplementsXListener implements XListener {
		@Override handler(s)
		
		void handlerMethod1(XEvent e) {
			// code
		}
		
		void handlerMethod2(XEvent e) {
			// code
		}		
	}	
}

Example

Now, we are modeling a simple car object, which can fire a SpeedEvent. The listener is SpeedListener interface. Max speed is temporarily set to 60MPH, min speed 40, and the default 50 when the car starts driving on the highway.

When the car runs too fast or too slow, it fires SpeedEvent. the SpeedEvent is defined as:

Java
public class SpeedEvent {
	private int maxSpeed;
	private int minSpeed;
	private int currentSpeed;

	public SpeedEvent(/*Object source, */ int maxSpeed, int minSpeed, int currentSpeed) {
		// super(source);
		this.maxSpeed = maxSpeed;
		this.minSpeed = minSpeed;
		this.currentSpeed = currentSpeed;
	}
	
	// and some getters here
}

Corresponding SpeedListener declares 2 handlers:

Java
public interface SpeedListener {
	public void speedExceeded(SpeedEvent e);

	public void speedGoneBelow(SpeedEvent e);
}

Come back to the Car, it maintains a list of SpeedListeners:

Java
private ArrayList<speedlistener> speedListenerList = new ArrayList<speedlistener>();
</speedlistener></speedlistener>

The register method for the SpeedListener:

Java
// Register an event listener
	public synchronized void addSpeedListener(SpeedListener listener) {
		if (!speedListenerList.contains(listener)) {
			speedListenerList.add(listener);
		}
	}

When the car speeds up:

Java
public void speedUp(int increment) {
		this.currentSpeed += increment;
		if (this.currentSpeed > this.maxSpeed) {
			// fire SpeedEvent
			processSpeedEvent(new SpeedEvent(this.maxSpeed, this.minSpeed, this.currentSpeed));
		}		
	}

We see that when the current speed exceeds the max speed, an EventSpeed object is created with relevant information: the source, max and current speed. And then fire that EventSpeed object:

Java
private void processSpeedEvent(SpeedEvent speedEvent) {
		ArrayList<speedlistener> tempSpeedListenerList;

		synchronized (this) {
			if (speedListenerList.size() == 0)
				return;
			tempSpeedListenerList = (ArrayList<speedlistener>) speedListenerList.clone();
		}

		for (SpeedListener listener : tempSpeedListenerList) {
			listener.speedExceeded(speedEvent);
			listener.speedGoneBelow(speedEvent);
		}
	}
</speedlistener></speedlistener>

This processSpeedEvent(SpeedEvent e) method is to be executed when the SpeedEvent is fired. It calls each handler of each SpeedListener object in the speedListenerList, to notify and do something about that event.

That we has created and fired the event, and delegated to the listener to process the event. It's up to the client code to implement the concrete handler(s).

* Note: the use of 'synchronized' and 'clone' is because a new listener might be added to, or the current listener might be removed from the speedListenerList while the processSpeedEvent() method is running. That leads to the corruption of speedListenerList.

It seems all done. Now is the client code (with main method) to make use of above custom Car and SpeedEvent-SpeedListener pair.

Java
public static void main(String[] args) {
		Car myCar = new Car(60, 40, 50);

		SpeedListener listener = new MySpeedListener();
		myCar.addSpeedListener(listener);
		// Add more listeners if you want

		myCar.speedUp(50); // fires SpeedEvent
		myCar.speedUp(50); // fires SpeedEvent
		myCar.slowDown(70);
		myCar.slowDown(70); // fires SpeedEvent
}

The inner class MySpeedListener defines custom concrete handler(s) as follow:

Java
// Inner class
private static class MySpeedListener implements SpeedListener {
    @Override
    public void speedExceeded(SpeedEvent e) {
        if (e.getCurrentSpeed() > e.getMaxSpeed()) {
            System.out.println("Alert! You have exceeded " + (e.getCurrentSpeed() - e.getMaxSpeed() + " MPH!"));
        }
    }

    @Override
    public void speedGoneBelow(SpeedEvent e) {
        if (e.getCurrentSpeed() < e.getMinSpeed()) {
            System.out.println("Uhm... you are driving " + e.getCurrentSpeed() + " MPH. Speed up!");
        }
    }
}

Or if you don't want to use inner class, you can use anonymous class instead:

Java
public static void main(String[] args) {
		Car myCar = new Car(60, 40, 50);

		SpeedListener listener = new MySpeedListener();
		myCar.addSpeedListener(listener);
		// Add more listeners if you want

		// Anonymous inner class
		myCar.addSpeedListener(new SpeedListener() {

			@Override
			public void speedExceeded(SpeedEvent e) {
				// Code
			}

			@Override
			public void speedGoneBelow(SpeedEvent e) {
				// Code
			}
		});

		myCar.speedUp(50); // fires SpeedEvent
		myCar.speedUp(50); // fires SpeedEvent
		myCar.slowDown(70);
		myCar.slowDown(70); // fires SpeedEvent
	}

Result output will be:

Alert! You have exceeded 40 MPH!
Alert! You have exceeded 90 MPH!
Uhm... you are driving 10 MPH. Speed up!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)