早期程序使用输入-操作-输出的机制,整个流程完全由程序员事先设定好。
事件驱动机制是指程序按照事件发生的次序随机执行而不是按照编程时就定义好的顺序执行当某个事件发生时程序将找到相应的事件处理程序来处理事件。所以具有顺序结构的编程显然不具有事件驱动的先决条件。
面向对象程序设计当中采用的就是事件驱动机制。比如说鼠标左击、双击都是具体事件,根据这些事件启用预先设置的相应动作就是事件驱动机制。注意这些事件是随机触发的,并没有预先决定它们的发生顺序,预先设计好的是它们的处理方法(事件handler)
当前,事件机制是组件和交互式开发所必须的一种技术。
事件机制例子:当用户输入一个字符或者是单击一次鼠标,一个事件就发生了。任何一个程序中的对象都可能被通知到有某事件发生,只要它实现了一些相应的接口或者是事先在一个具备事件监听功能的对象那里注册好了,告诉它“有什么事件时通知我一下”。被通知后,对象可以做出不同的响应,以实现各自的功能。
事件触发机制相对于简单的轮询机制来说,优点如下:
图形用户界面就是利用这种事件响应来实现接收用户操作的。那么到底是怎么实现的呢。当前各种语言、类库、平台大都会提供事件机制,至于实现方式便有很多种。
C# 委托方式
C#的委托事件机制非常优雅的解决了对象间通信机制。
首先可以将委托delegate看成是函数指针类型的抽象:
public delegate void delegatesome(type1 para1, type2 para2, type3 para3, ...);
//EventHandler是一种委托,看成是函数指针类型
事件event是一种带特殊签名的委托的实例,且其原型声明必须是如下形式:
public delegate void EventHandler(object sender ,MyEventArgs e) ;
事件定义:
public event EventHandler ActionEvent;
控件类(或其他可以触发事件的类):
class Control{public event EventHandler SomeEvent;//声明可以触发的事件
//...protected OnSomeEvent(EventArgs e) //触发事件
{if(SomeEvent)//判断是否有注册处理函数
{SomeEvent(this, e);//这里才是触发事件的实质部分!!
}
}
}
因为一般控件的触发都是由操作系统检测然后自动调用protected的成员OnSomeEvent(EventArg e)函数,既然是protected,意味着我没有办法直接通过程序模拟触发事件的发生。当然你可以自定义事件,然后决定何时触发。
下面是处理类:
class Consumer{private Control ctl0 = new Control();
//... public Consumer() { //..this.ctl0.SomeEvent += new System.EventHandler(this.ctl0_SomeEvent);
}
private ctl0_SomeEvent(object sender, EventArgs e)
{ //do things}
}
Java 注册-监听方式(观察者模式)
Java中要实现事件处理,通常要进行以下三个步骤:
1. 事件处理者要么实现一个监听器接口,要么继承一个已经实现监听器接口的类;
2. 在事件接收器中注册该事件处理器中的实例,是事件接收器在发生事件时通知该对象
3. 在事件处理器中定义某些方法,该方法是事件监听器接口的唯一方法,在这个方法中,定义事件处理器在事件发生之后要做的事情
事件机制的一般模式
主要涉及三个对象——事件源,事件类,事件监听器
1. 事件源——它应该具有两个功能:必须提供添加或者删除与用户定义事件对应的事件监听器;必须提供生成用户定义事件和发送事件到任意已经注册事件监听器的手段
2. 事件类——与事件发生有关的状态信息一般都封装在一个事件状态对象中,这种对象是java.util.EventObject的子类。每一个事件对象类型携带了不同的信息,它们都包含了事件的来源信息。
3. 事件监听器——在该对象中,操纵事件状态对象,对事件进行相应的处理。事件操纵方法都被定义在继承了java.util.EventListener类的EventListener接口中,按规定,EventListener接口的命名要以Listener结尾。
例如:一个程序想要在某个按扭被单击之后做处理,在这个例子中,按扭就是事件源,该程序首先要定义一个实现了AactionListener接口的对象,然后将这个对象作为监听器在这个按钮中进行注册,即使用addAactionListener()方法。当用户在这个按钮上单击时,按钮就会激发一个新的事件,随后便会将这个事件的发生通知给事件处理器(也就是实施了事件监听器的类),实际上通知的动作就是调用事件处理器中的actionPerformed()方法。这个方法的唯一参数就是这个事件,即一个ActionEvent对象,它带有与事件相关的所有信息。
这里还介绍事件处理中辅助类:监听器适配器类和事件支持类
1. 事件支持类:这个类能够为对象管理监听器列表和提供在列表中增加、删除监听器的方法,其中还包括判断这个监听器是否已经在列表中的逻辑。该对象也必须实现用以触发事件的方法并且将其发送到列表中所有的listener中。
2. 监听器适配类:监听器适配器方便程序员编程。当我们通过实现监听器接口直接创建事件监听器时,就必须实现接口中的各个方法,不管这些方法是否能够用上。通常不急需的方法实现为桩基模块(只有方法,没有实体)。监听器适配器类为我们提供了桩基模块。要使用适配器类创建时间监听器,只要简单的写一个适配器类的子类,然后重载所需的监听器方法即可。
C++ 函数指针
Heron-Centric: Ruminations of a Language Designer
Event Driven Programming using Template Specializations in C++
by Christopher Diggins
December 18, 2004
Summary
C++ template specializations provide an elegant mechanism for implementing event-driven frameworks. This is the technique that I am using for defining semantic actions in the YARD parser. This is a reprint of an article I just posted to CodeProject.com.
Advertisement
Introduction
Template specializations provide a mechanism by which we can create an event-driven framework, instead of using function pointers.
Template specializations are alternative definitions of a template for specific parameter values. They are sometimes known as user-defined specializations. For more information I recommend reading The C++ Programming Language, 3rd Edition by Bjarne Stroustrup.
There are two advantages of this approach, first no function pointer registration step is needed like in typical event-driven frameworks, and secondly the compiler can do a far better job of optimizing the call.
Event-Driven Programming
Event-driven code is useful when writing a library and we want the library to provide default behaviour in response to certain events, but to allow library users to provide custom behaviour to one or more events. This occurs frequently in the implementation of GUI libraries. A message loop has to dispatch events to user-defined functions. This is typically done either through virtual-functions or through function-pointers. Using template specializations is an easy to use and efficient alternative.
The Code
Here is a sample program, which demonstrates the usage of template specializations for callbacks:
const int RED_PILL = 0;
const int BLUE_PILL = 1;
template<int T>struct EventHandler {static void Event() { };
};
template<typename Dummy_T = void>struct Dispatcher {static bool EventDispatch(char ch) {
switch (ch) {case 'a': {
EventHandler<RED_PILL>::Event();
return true;
}
case 'b': {
EventHandler<BLUE_PILL>::Event();
return true;
}
default : {return false;
}
}
}
};
template<>
struct EventHandler<BLUE_PILL> {static void Event() { puts("Welcome to the matrix!"); };
};
int main() { puts("press a for the red-pill, b for the blue-pill"); char ch = getchar();Dispatcher<>::EventDispatch(ch);
return 0;}
Explanation
This code represents how an event-driven library could be created. The basic EventHandler and Dispatcher classes represent what would be found in the library. The specializations and the main() function represent what would be defined by the user of the library,
The first EventHandler class is a template which contains empty functions defintions. This is the class which is specialized. The other EventHandler class is are the template specializations defined by the user of a library. The programmer simply needs to provide implementations of the Event() function and can do whatever else they want inside of it.
The Dispatcher class houses a Dispatch() function which triggers the appropriate user-defined event. The template parameter is ignored, but is provided to make sure that the class is constructed by the compiler after the specializations are defined by the programmer.
Notice that there is no explicit specialization EventHandler<RED_PILL>. The default handler is called in this case, which does nothing. The call should be entirely removed by the optimizer.
Why is EventDispatch inside of a template?
This is a drawback of the technique due to compilation order dependencies. Typically in an event-driven framework we will want to define the specializations in code after the code where the specialization is used. As soon as the compiler sees a usage of the template it is too late for us to define a specialization. What I do then is define the usage within a template. This allows me to put off compilation of the usage until after the definition of the specializations.
另外请参考《Callback设计模式》
Win32/MFC 消息发送与回调函数机制
消息驱动机制
1、消息驱动与消息循环
“消息”是windows运行机制中一个基本而又重要的概念。消息是一个报告事件发生的通知,消息驱动是围绕消息的产生与处理展开的,并依靠消息循环机制来实现。
从程序设计的观点看,某条消息可被视为某个事件的发生,比如点击鼠标。事件即可以由用户引发,也可以由应用程序产生,当然Windows本身也能发出消息。Windows应用程序的消息来源有4种:输入消息,控制消息,系统消息,用户消息。
Windows是一个多任务操作系统,所以没有哪一个程序能够独占系统的资源,资源都是由Windows统一管理的。那么某个程序是如何获得用户的信息呢?事实上,Windows在时刻监视着用户的每个举动,并分析用户的动作与哪一个程序相关,然后将动作以消息的形式发送给当前的应用程序。相反,应用程序也在时时等着消息的到来,一旦发现它的消息队列中有未处理的信息,就获取并分析该消息,并根据消息所包含的内容采取适当的动作来响应。这里我们引出另一个概念“消息驱动”。比如当你单击file菜单的时候,首先这个动作被windows所捕获,而不是应用程序。经分析windows知道该动作该由哪个应用程序处理,然后windows就发送WM_COMMAND消息给该应用程序,它告诉应用程序,你单击了file菜单。应用程序得知这一消息后,便采取相应的动作来响应它,进行“消息处理”。Windows为每个线程维护了相应的消息队列,应用程序的任务就是不停地从特定的消息队列中获取消息、分析消息并处理消息,直到消息(WM_QUIT)为止。这个过程的程序结构称为“消息循环”。
2、消息传送
发送消息和寄送消息
发送一个消息时,系统直接调用窗口进程。通信是即时的。直到窗口进程为调用函数返回一个结果后,应用程序才能继续。
寄送一个消息时,系统把消息发送到拥有该窗口的应用程序消息队列中。消息队列是系统定义的一个内存块,用于临时存储消息,或是把消息直接直接发给窗口过程。每个窗口维护自己的消息队列,从中取出消息,利用窗口函数进行处理。一有空闲,应用程序就搜索消息队列,并在消息队列中处理消息,即从队列中删除他们。调用函数发送消息后就立即返回,但结果只是表示消息寄送成功与否,而不表示被调用窗口进程的结果。通常鼠标和键盘消息是寄送的。
3、消息处理
Windows程序在处理消息时使用了“回掉函数”的特殊函数。这个函数由应用程序定义,但并不由应用程序来调用,而是共操作系统或者其子系统来调用的。这种调用通常在某一事件发生,或者在窗口或字体被枚举时发生。Windows向程序员所能发送的消息多达百种,但是,对于一般的应用程序来说,只是其中的一部分有意义。
4、Windows对消息驱动机制的支持
Windows操作系统包括3个内核基本模块:
GDI:负责在屏幕上绘制象素、打印硬考贝输出,绘制用户界面
KERNEL:支持与操作系统密切相关的功能。如进程加载,系统调用
USER:为所有的用户界面对象提供支持,它用于接收和管理所有输入消息、系统消息,并把他们发给相应的窗口的消息队列。
上述GDI、KERNEL和USESR模块中的库函数可被应用程序调用,也可被其他程序模块调用。Windows把包含库函数的模块称为EXPORT,在WINDOWS提供的一种新的EXE文件中有一个入口表用于指明模块内每个输出函数的地址。
从应用程序方面,用到的库函数被认为是IMPORT函数。应用程序对一个入口函数发出的远程调用可用不同的重定位表来确定。几乎所有的应用程序都至少包含一个入口库函数或者称为被外部调用的函数。该windows库函数一般来自某个程序模块,用于从WINDOWS接收消息,该函数的使用标志必须是EXPORT,这才能使WINDOWS允许它被一个外部模块正常调用。
Javascript 无
由于javascript并不是真正意义上“面向对象”的语言,所以,在实现事件驱动模型的时候,总是会遇到一些困难。
当然这里指的事件驱动并不是指javascript固有的事件处理机制或者DOM的事件模型,而是指类似C#或者Java的那种事件模型。
javascript在处理事件驱动的时候最大的问题在于"this指针"困惑。
例如:<input type="button" onclick=function(){alert(this.value);} value="Hello World!"/>
这里的this指针毫无疑问是没有问题的
但是
<input type="button" id="button1"/>
var objBtn = document.getElementById("button1");
objBtn.attachEvent('onclick', EventHandler);function EventHandler(){ alert(this.value);}
运行这个脚本很吃惊地发现得到的是完全错误的结果
因为这里的this并不是objBtn这个input对象!而是全局的window对象!
类似的问题在javascript中还会经常出现
再比如一个更复杂的应用:
function mainForm(form){ this.form = form; this.button1 = form.buttonOK; this.button2 = form.buttonCancel;this.button1.onclick = Button1_Click; //为按钮注册单击事件
this.button2.onclick = Button2_Click; mainForm.prototype. Button1_Click = function() { alert(this.button1.value);}
mainForm.prototype.Button2_Click = function() {this.form.style.display = 'none';
}
}
本来一段挺“漂亮”的代码,却不能正确运行,为什么呢?原来问题还是在this上,button1_Clicked和button2_Clicked两个函数虽然是mainForm类的成员函数,但是他们是被this.button1.onclick和this.button2.onclick调用(不完全准确的说法)的,所以函数中的this指得是button1和button2两个对象!这很令人困扰,不是吗?
为了解决这个问题,必须要保证函数调用者是mainForm对象而不是button或者其他的什么成员,为此,我思考出一种事件注册的机制,将上面的代码写成:
this.button1.ClickEventSender = this;
this.button1.onclick = function(){this.ClickEventSender.Button1_Click(this.ClickEventSender,self.event);}
mainForm.prototype.Button1_Click = function(object, sender)
{ alert(this.button1.value);}
就得到了正确结果。
于是,一种完全的基于“后台代码”的“事件驱动”模型慢慢成型,下面是一段简短的Demo代码:
//后台代码//事件驱动框架(演示)//2004-11-16 Created//2004-11-17 Modified//ControlDemo.jsfunction ControlDemo(page){ //初始化Pageif (page == null)
{page = self;
}
if (page != self) { //Do sth. here...}
this.page = page; //Properties this.keyPressed = 0; //Controlable Entities //PageBody 注册PageLoad事件this.body1 = page.document.getElementById("main");
this.page.PageLoadEventSender = this;
this.body1.onload = function(){this.PageLoadEventSender.PageLoad(this.PageLoadEventSender,page.event);}
//Button1 注册Click事件 this.button1 = page.button1;this.button1.value = "确定";
this.button1.ClickEventSender = this;
this.button1.onclick = function(){this.ClickEventSender.Button1_Click(this.ClickEventSender,page.event);}
//Button2 注册Click事件 this.button2 = page.button2;this.button2.value = "取消";
this.button2.ClickEventSender = this;
this.button2.onclick = function(){this.ClickEventSender.Button2_Click(this.ClickEventSender,page.event);}
//Textbox1 注册KeyUp、MouseMove事件 this.textbox1 = page.textbox1;this.textbox1.style.width = "100%";
this.textbox1.rows = 10;this.textbox1.KeyUpSender = this;
this.textbox1.onkeyup = function(){this.KeyUpSender.Textbox1_KeyUp(this.KeyUpSender,page.event);}
this.textbox1.MouseMoveSender = this;
this.textbox1.onmousemove = function(){this.MouseMoveSender.Textbox1_MouseMove(this.MouseMoveSender, page.event);}
//Labelsthis.label1 = page.document.getElementById("label1");
this.label2 = page.document.getElementById("label2");
this.label3 = page.document.getElementById("label3");
//EventHandlers 事件处理函数ControlDemo.prototype.PageLoad = function(sender,event)
{this.page.defaultStatus = "事件驱动框架演示~~";
this.page.resizeTo(600,400);}
ControlDemo.prototype.Button1_Click = function(sender,event)
{ alert("Hello ^_^");}
ControlDemo.prototype.Button2_Click = function(sender,event)
{if (page.opener == null)
{page.opener = page;
}
page.close();
}
ControlDemo.prototype.Textbox1_KeyUp = function(sender, event)
{ this.keyPressed++;this.label1.innerText = this.keyPressed;
this.label2.innerText = this.textbox1.value.length;
this.label3.innerText = event.keyCode;
}
ControlDemo.prototype.Textbox1_MouseMove = function(sender, event)
{this.page.status = "鼠标位置:x="+event.x+" y="+event.y;
}
}
<!--前台页面-->
<html>
<head>
<title>事件驱动框架演示</title>
</head>
<body id = "main"><div align="center" id="div1">
<textarea name="textbox1" id="textbox1"></textarea><br/>
键盘点击次数为<span id="label1">0</span>次,共键入<span id="label2">0</span>个字符,最后输入的字符码为
<span id="label3">0</span><br/>
<input type="button" name="button1" id="button1"></input>
<input type="button" name="button2" id="button2"></input>
</div>
</body>
</html>
<script language="javascript" type="text/javascript" src="ControlDemo.js"></script>
<script language="javascript" type="text/javascript">
var demo = new ControlDemo();
</script>
是不是感觉页面上的代码很简洁?
几乎只是放置对象和构造类,其他什么也不做。
而编写后台代码是不是让用asp.net的朋友有一种“似曾相识”的感觉?
虽然不敢说一定好,但,这就是javascript的另类力量
Qt signal/slot
请参考http://www.ibm.com/developerworks/cn/linux/guitoolkit/qt/signal-slot/

浙公网安备 33010602011771号