1:缘起
在传统的程序中,一个具有交互功能的应用程序的主体无疑是如下的一段循环代码:不断读取用户的输入,根据输入采取相应的动作,完成所需的功能。例如:
bool quit=false;
char ch;
while(!quit) {
ch=read_input();
switch(ch) {
case 'i':
// ... …
break;
case 'q':
quit=true; // … …
break;
default:
// ... …
} }
而基于事件驱动机制的程序则不大相同,主程序可能如下这般:
int main()
{
TMyApp myApp;
myApp.run();
return 0;
}
在整个程序中,唯一与事件有点关系的要属handleEvent函数了,它可能并不像你我想像的那样(注:以Turbo Vision中的应用程序为例,根据类的继承关系,函数的调用序列依次为:TMyApp::run() TApplication::run() TGroup::execute() TMyApp::handleEvent() 来处理事件):
void TMyApp::handleEvent(TEvent& event) {
TApplication::handleEvent(event); // act like base!
if( event.what == evCommand ) {
switch( event.message.command ){
case cmMyNewWin: // but respond to additional commands
myNewWindow(); // define action for cmMyNewWin
break;
default:
// … …
return;
}
clearEvent( event ); // clear event after handling
}
}
从这里,我们明显可以看到一份简单,一个鸿沟:没有了令人头疼的循环,程序中的对象只需要处理自己收到的事件,程序的结构变得异常简单清晰;但也正是这份简单,让本来清楚明白的流程变得神秘起来,没有了读取用户输入,摆放在面前的已经是送到的事件,这多少有点让人措手不及,前面的路是谁铺就的?一切的一切,都缘起于事件驱动机制:读取了用户的输入,并把其包装成事件,然后按照一定的规则分发给不同的对象;正是它,造就了简单,也成就了神秘;有了它,用户才可以把所有的精力集中于事件的处理上,而不用分心其他琐事。例如:在一个非活动窗口上按下鼠标左键,用户只需确保该窗口在接收到该消息时,能够把自己调到前台,正确地显示即可。在鼠标轻点与窗口响应之间,系统在幕后默默地做了许多事情:读取鼠标的动作并形成的事件,按照一定的规则把事件发送给该非活动窗口。
系统替我们完成的这许多事情,给事件驱动蒙上几分神秘、几分诱惑。不论是好奇心、还是求知欲,都使人有揭开面纱,一探究竟的冲动。最直接的方法是去看源码,借用一句话:“源码之前,了无秘密”。源码之中,会清楚地显示系统是如何收集用户的输入,又是如何把用户的输入打包成事件,分发给程序中的各个对象的。追踪到源码级别,相信已经足以理解事件驱动机制。然而,去哪儿找到这样的源码呢?Windows操作系统是基于事件驱动的,但哪儿有其源码?Linux操作系统倒是有源码,然而,面对其浩如烟海的文件,如何简洁、干练地抽出事件驱动的源码而不至于陷于泥沼无法自拔?因此,最好是能够有一个基于事件驱动的、微型的应用程序框架,既能把事件驱动机制讲个清楚明白又不至于吓退登山者。Turbo Vision无疑是最佳人选。
Turbo Vision是一个应用程序框架,为基于DOS的应用程序开发提供了全新的方法,可以为用户生成完整的界面,包括窗口、对话框、菜单、鼠标以及简单的编辑器等。Borland C++3.1中就附带有Turbo Vision的源码,网上也可以找到Turbo Vision的源码。
Turbo Vision应用程序的框架如图:
TMyApp-->TApplication-->TProgram-->TGroup-->TView-->TObject
-->TProgInit -->TStreamable
图1:Turbo Vision应用程序框架图
一个Turbo Vision应用程序是视图、事件及哑对象的组合。下面,分别对视图和哑对象作一简单的介绍,至于事件,后面会有详尽的介绍。
视图是屏幕上可见的程序元素对象。在Turbo Vision程序中,凡可见的,都是视图。标题、窗口边框、滚动条、菜单条等都是视图。视图也可以组合,形成复杂的程序元素,如对话框。这些视图的集合称为组。组作为特殊的视图,由其它称为其子视图的元素组成,而组自身也可以作为单一的视图与其他视图形成更复杂的元素,这样形成了一个复杂的视图链。
哑对象是程序中非视图的对象,在屏幕上不显示,无法与用户交互,故称为其哑对象。它们完成计算、与外围设备的通讯等后台任务,当其需要显示信息时,必须通过视图来进行。
这是只对Turbo Vision做一简单的介绍,在后面用到某些概念时,会略加注释,帮助读者理解。
2:事件的介绍
事件是一个无法再细分的信息包,描述了应用程序必须响应的具体事件。每一次击键、每一个鼠标动作以及程序的某一部分引起的某种特定情况都构成一个单独的事件。因此,用户输入一个单词不是一个事件,而是一系列独立的击键事件的集合。
不同的事件具有不同的性质,具有不同的结构,因此,有必要先了解不同种类的事件。事件的结构如下:
struct TEvent{
ushort what;
union{
MouseEventType mouse;
KeyDownEvent keyDown;
MessageEvent message;
};
void getMouseEvent();
void getKeyEvent();
};
由事件TEvent的结构可以清楚地看到,从本质上讲,有四类事件:鼠标事件,键盘事件,消息事件和空事件,对应于TEvent::what成员的四个可能取值,evMouse,evKeyboard,evMessage,evNothing。根据TEvent::what的值,TEvent对象可以决定自身的类型,使用相应的成员函数getMouseEvent()或getKeyEvent()来获得事件。
鼠标事件包括四种:鼠标键的按下与松开导致的evMouseDown、evMouseUp事件,移动鼠标产生的evMouseMove事件,保持按下某一键产生的evMouseAuto事件等,故鼠标事件