65.9K
CodeProject 正在变化。 阅读更多。
Home

在 Java 中定义自定义源-事件监听器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.87/5 (10投票s)

2013 年 11 月 3 日

CPOL

5分钟阅读

viewsIcon

75183

downloadIcon

802

事件驱动编程模型

* 注意:已更新新源代码(SpeedEvent 不需要扩展 EventObject,我的错!)

下载
源代码 [新]
源代码 [旧]

背景

* 如果您已经熟悉标准的 Java GUI 事件监听器对,可以跳过此部分,直接查看 定义自定义源、事件和监听器。

在 Java(尤其是 GUI)程序中,用户可能会与按钮、复选框、汽车或银行账户进行交互。程序决定是忽略还是响应这些情况。例如,如果我们想在用户单击按钮时执行某些操作,那么我们需要确定三件事:**(1)** 按钮本身,**(2)** 按钮被单击时的事件,以及 **(3)** 对按钮单击事件感兴趣的响应代码(称为“监听器”)。

在 Java 中,按钮被单击时的事件称为 **ActionEvent**。当这种事件发生时,对该 ActionEvent 感兴趣的监听器(称为 **ActionListener**)会包含一个要调用的方法(称为“处理程序”)。同样,Java 预先将此处理程序方法定义为 **actionPerformed(ActionEvent e)**。这是可能的,因为 ActionListener 已经被按钮“注册”以“监听”ActionEvent。

Java 中的事件驱动编程模型包含三种类型的对象

  1. 源代码
  2. 事件
  3. 监听器

 

源对象(例如,JCheckbox)在用户选中或取消选中复选框时会触发一个事件对象(例如,ItemEvent 类型)。

事件对象包含有关其源对象的信息。

监听器对象是一个接口,必须由源对象注册为“监听器”,以便在事件对象被触发时能够响应,并调用监听器对象的方法处理程序。

**注意**:一个源对象可以触发一个或多个事件,一个源对象可以注册一个或多个监听器。此外,一个监听器可以声明一个或多个处理程序。例如,Java 已经提供了标准模型
- 当用户选中或取消选中复选框时,**JCheckBox** 可以触发 **ActionEvent** 和 **ItemEvent**。相应的监听器接口是 **ActionListener**(包含处理程序方法 **actionPerformed(ActionEvent e)**),以及 **ItemListener**(包含处理程序 **itemStateChanged(ItemEvent e)**)。
- 当用户在组件上按下、单击、移动、移出鼠标,或在该组件上按下或键入键盘按键时,组件(JTextArea 或 JLabel)可以触发 **MouseEvent** 或 **KeyEvent**。相应的监听器接口是 **MouseLister**(定义了多个处理程序,如 **mousePressed(MouseEvent e)** 和 **mouseEntered(MouseEvent e)**),以及 **KeyListener**(包含预定义的处理程序)。

自定义源、事件和监听器

Java 已经提供了许多带有事件监听器对的标准组件。但我们希望定义自己的,因为它更适合我们的应用程序。例如,当客户从银行账户中提取过多金额时,银行账户可以触发自定义事件 **BalanceEvent**。为了让我们的自定义 **BalanceListener** 的处理程序 **balanceViolated(BalanceEvent e)** 做出适当响应,它应该能够知道当前余额是多少、账户持有人需要维持的最低金额,以及该尝试交易的日期和时间。这意味着 BalanceEvent 包含了处理程序所需的数据。这在使用预定义的 Java 事件监听器对时是无法实现的。

在定义自己的事件监听器对时,我们应该严格遵循命名约定
**XEvent - XListener**(其中 X 可以是 'Action'、'Mouse'、'Account'、'Balance'、'Speed' 等)

自定义 XEvent

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
	}
	
}

**注意**:自定义 XEvent 的构造函数:这是源对象将数据传递给触发事件的方式。

自定义 XListener

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);
}

源代码

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) 
}

正如我们所见,源对象可以维护一个监听器列表。当发生某种情况时(例如银行账户余额不足,或某个生物学课程已满无法再招收学生),源对象会调用其监听器上的处理程序方法。由于我们将 XEvent 对象传递给这些处理程序,因此处理程序可以做出适当响应。例如,通知客户端代码当前的余额、尝试提取的金额以及日期。

客户端代码(带 main 方法)

// 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
		}		
	}	
}

示例

现在,我们正在模拟一个简单的汽车对象,它可以触发 **SpeedEvent**。监听器是 **SpeedListener** 接口。最高速度暂时设置为 60MPH,最低速度 40,默认值为 50,当汽车在高速公路上行驶时。

当汽车行驶速度过快或过慢时,它会触发 SpeedEvent。SpeedEvent 定义如下:

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
}

相应的 **SpeedListener** 声明了 2 个处理程序

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

	public void speedGoneBelow(SpeedEvent e);
}

回到 Car,它维护一个 SpeedListeners 列表

private ArrayList speedListenerList = new ArrayList();

SpeedListener 的注册方法

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

当汽车加速时

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

我们看到,当当前速度超过最高速度时,会创建一个 EventSpeed 对象,其中包含相关信息:源、最高速度和当前速度。然后触发该 EventSpeed 对象

private void processSpeedEvent(SpeedEvent speedEvent) {
		ArrayList tempSpeedListenerList;

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

		for (SpeedListener listener : tempSpeedListenerList) {
			listener.speedExceeded(speedEvent);
			listener.speedGoneBelow(speedEvent);
		}
	}

这个 **processSpeedEvent(SpeedEvent e)** 方法将在 SpeedEvent 被触发时执行。它会调用 speedListenerList 中每个 SpeedListener 对象上的每个处理程序,以通知并处理该事件。

我们已经创建并触发了事件,并将处理委托给了监听器。由客户端代码来实现具体的处理程序。

**注意**:使用“synchronized”和“clone”是因为在 processSpeedEvent() 方法运行时,可能会向 speedListenerList 添加新的监听器,或者从 speedListenerList 中删除当前监听器。这会导致 speedListenerList 损坏。

似乎一切都完成了。现在是客户端代码(带 main 方法)来利用上述自定义 Car 和 SpeedEvent-SpeedListener 对。

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
}

内部类 MySpeedListener 定义了自定义的具体处理程序如下:

    // 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!");
			}
		}
	}

或者,如果您不想使用内部类,也可以使用匿名类代替

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
	}

结果输出将是:

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