* 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.
The Event-Driving Programming Model in Java consists of 3 type objects:
- The source
- The event
- 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
class XEvent {
String customer;
int balance;
Date dateWidthraw;
public XEvent( String customer, int balance, Date date,...) {
}
}
* Note: the constructor of the custom XEvent: it's how the source object pass the data to the fired event.
Custom XListener
interface XListener {
void handlerMethod1(XEvent e);
void handlerMethod2(XEvent e);
}
The Source
class Source {
private XListener listener;
void addXListener(XListener myListener)
XEvent myEvent = new XEvent(this, param1, param2,...)
listener.handlerMethod(myEvent)
}
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)
public class Program {
Source source = new Source();
XListener listener = new ClassThatImplementsXListener();
source.addXListener(listener);
source.widthDraw(9000);
source.speedUp(100);
private class ClassThatImplementsXListener implements XListener {
@Override handler(s)
void handlerMethod1(XEvent e) {
}
void handlerMethod2(XEvent e) {
}
}
}
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:
public class SpeedEvent {
private int maxSpeed;
private int minSpeed;
private int currentSpeed;
public SpeedEvent( int maxSpeed, int minSpeed, int currentSpeed) {
this.maxSpeed = maxSpeed;
this.minSpeed = minSpeed;
this.currentSpeed = currentSpeed;
}
}
Corresponding SpeedListener declares 2 handlers:
public interface SpeedListener {
public void speedExceeded(SpeedEvent e);
public void speedGoneBelow(SpeedEvent e);
}
Come back to the Car, it maintains a list of SpeedListeners:
private ArrayList<speedlistener> speedListenerList = new ArrayList<speedlistener>();
</speedlistener></speedlistener>
The register method for the SpeedListener:
public synchronized void addSpeedListener(SpeedListener listener) {
if (!speedListenerList.contains(listener)) {
speedListenerList.add(listener);
}
}
When the car speeds up:
public void speedUp(int increment) {
this.currentSpeed += increment;
if (this.currentSpeed > this.maxSpeed) {
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:
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.
public static void main(String[] args) {
Car myCar = new Car(60, 40, 50);
SpeedListener listener = new MySpeedListener();
myCar.addSpeedListener(listener);
myCar.speedUp(50);
myCar.speedUp(50);
myCar.slowDown(70);
myCar.slowDown(70);
}
The inner class MySpeedListener defines custom concrete handler(s) as follow:
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:
public static void main(String[] args) {
Car myCar = new Car(60, 40, 50);
SpeedListener listener = new MySpeedListener();
myCar.addSpeedListener(listener);
myCar.addSpeedListener(new SpeedListener() {
@Override
public void speedExceeded(SpeedEvent e) {
}
@Override
public void speedGoneBelow(SpeedEvent e) {
}
});
myCar.speedUp(50);
myCar.speedUp(50);
myCar.slowDown(70);
myCar.slowDown(70);
}
Result output will be:
Alert! You have exceeded 40 MPH!
Alert! You have exceeded 90 MPH!
Uhm... you are driving 10 MPH. Speed up!