2009年9月28日星期一

Delegate 代理和事件驱动 Event Driven

按下键盘上的一个建或者左击鼠标都会产生一个事件(Event),事件发生时的处理称作事件句柄(Event Handler)。GUI的程序都是针对用户做出的事件做相应的处理,这样的处理过程称作事件驱动(Event Driven)。C#中为了简化这种事件驱动程序的开发,提供了event关键字。event 本质上是属性机制的代理(delegate)版本。

先给一个事件驱动机制的简单例子。每1秒钟显示一次当前时刻,同时响应键盘的输入,做停止显示,显示方式改变,退出程序等处理。

using System;
using System.Threading;
class TestEvent
{
// 时间的显示形式
const string FULL = "yyyy/dd/MM hh:mm:ss\n";
const string DATE = "yyyy/dd/MM\n";
const string TIME = "hh:mm:ss\n";

static bool isAlive = true; // 程序的执行/终了标志
static bool isSuspended = true; // 程序暂停标志
static string timeFormat = TIME; //时间的表示形式

static void Main()
{
Thread thread = new Thread(new ThreadStart(EventLoop));
thread.Start();

while(isAlive)
{
if(!isSuspended)
{
// 每过1秒,显示一次当前时刻。
DateTime t = DateTime.Now;
Console.Write(t.ToString(timeFormat));
}
Thread.Sleep(1000);
}

thread.Join();
}

static void EventLoop()
{
// 事件循环
while(isAlive)
{
string line = Console.ReadLine();
char eventCode = line.Length == 0 ? '\0' : line[0];

// 事件处理
switch(eventCode)
{
case 'r': // run
isSuspended = false;
break;
case 's': // suspend
isSuspended = true;
break;
case 'f': // full
timeFormat = FULL;
break;
case 'd': // date
timeFormat = DATE;
break;
case 't': // time
timeFormat = TIME;
break;
case 'q': // quit
isAlive = false;
break;
default: // ヘルプ
Console.Write(
"操作方法\n" +
"r (run) : 开始时间表示。\n" +
"s (suspend): 暂停时间表示。\n" +
"f (full) : 日期,时间表示。\n" +
"d (date) : 只表示日期。\n" +
"t (time) : 只表示时间。\n" +
"q (quit) : 程序终止。\n"
);
break;
}
}//while(isAlive)
}//Main
}

上面的例子中的EventLoop,这个事件循环处理,可以分成事件接收(Event Loop)和事件处理(Event Handler)两个部分。虽然事件处理每个程序都不一样,但是事件循环接收(Event Loop)却每个程序都一样,所以我们希望将这个部分独立出来,做成一个单独的类。

 
// 事件处理用代理
delegate void KeyboadEventHandler(char eventCode);

///
/// 等待键盘输入事件的类
///

class KeyboardEventLoop
{
KeyboadEventHandler onKeyDown;
Thread thread;

public KeyboardEventLoop(KeyboadEventHandler onKeyDown)
{
this.onKeyDown = onKeyDown;
}

///
/// 等待事件开始。
///

public void Start()
{
this.thread = new Thread(new ThreadStart(this.EventLoop));
this.thread.Start();
}

///
/// 等待事件修了
///

public void End()
{
this.thread.Abort();
}

///
/// 本线程的状态。
///

public bool IsAlive
{
get{return this.thread.IsAlive;}
}

///
/// 循环等待事件的到来。
///

void EventLoop()
{
try
{
//无限循环
while(true)
{
// 读入文字
string line = Console.ReadLine();
char eventCode = (line == null || line.Length == 0) ? '\0' : line[0];

// 事件处理通过代理,让具体的事件句柄去处理。
this.onKeyDown(eventCode);
}
}
catch(ThreadAbortException){}
}
}

这个类中从事件循环等待中独立出来的okKeyDown这个代理,就是事件处理句柄(Event Handler)。用上面的这个类重新改写开始的例子程序。
 
class TestEvent
{
// 时刻的表示形式
const string FULL = "yyyy/dd/MM hh:mm:ss\n";
const string DATE = "yyyy/dd/MM\n";
const string TIME = "hh:mm:ss\n";

static KeyboardEventLoop eventLoop;
static bool isSuspended = true; // 程序的暂停标志
static string timeFormat = TIME; // 时刻的表示形式。

static void Main()
{
eventLoop = new KeyboardEventLoop(new KeyboadEventHandler(OnKeyDown));
eventLoop.Start();

while(eventLoop.IsAlive)
{
if(!isSuspended)
{
// 每过1秒,显示一次当前时刻。
DateTime t = DateTime.Now;
Console.Write(t.ToString(timeFormat));
}
Thread.Sleep(1000);
}
}
///
/// 事件处理部分。
///

static void OnKeyDown(char eventCode)
{
// 事件处理
switch(eventCode)
{
case 'r': // run
isSuspended = false;
break;
case 's': // suspend
Console.Write("\n暂停\n");
isSuspended = true;
break;
case 'f': // full
timeFormat = FULL;
break;
case 'd': // date
timeFormat = DATE;
break;
case 't': // time
timeFormat = TIME;
break;
case 'q': // quit
eventLoop.End();
break;
default: // ヘルプ
Console.Write(
"操作方法\n" +
"r (run) : 开始时间表示。\n" +
"s (suspend): 暂停时间表示。\n" +
"f (full) : 日期,时间表示。\n" +
"d (date) : 只表示日期。\n" +
"t (time) : 只表示时间。\n" +
"q (quit) : 程序终止。\n"
);
break;
}
}
}

再进一步,我们不但把事件循环接受的机制独立出来,同时还想能自由的追加和删除事件处理句柄(event handler),我们首先想到的是将这个代理变量KeyboadEventHandler onKeyDown;变成Pulic型,但是这样破坏了类的封装机制(外部操作内部),或者想通过属性机制(get/set)的方式来实现,但是这样也不行。因为我们希望对代理的调用,只能局限在类内部,而从类外部,只能对代理变量进行追加/消除操作。为了实现这个机制,C#中专门提供了event 关键字。在声明delegate型变数的时候,只要在最前面加上event关键字,这样的delegate变数就具备上面的功能,内部可以像普通的代理变量一样使用,而类的外部这只能使用-=,+=。
用event 关键字重新改写上面的例子,只是在KeyboadEventHandler OnKeyDown;前面加上pulic event,其他基本一样。

using System;
using System.Threading;
// 事件处理用代理
delegate void KeyboadEventHandler(char eventCode);

///
/// 等待键盘输入事件。
///

class KeyboardEventLoop
{
Thread thread;

public KeyboardEventLoop() : this(null){}

public KeyboardEventLoop(KeyboadEventHandler onKeyDown)
{
this.OnKeyDown += onKeyDown;
}

///
/// 键盘输入处理句柄。
///

public event KeyboadEventHandler OnKeyDown;

///
/// 循环等待开始。
///

public void Start()
{
this.thread = new Thread(new ThreadStart(this.EventLoop));
this.thread.Start();
}

///
/// 循环等待终了。
///

public void End()
{
this.thread.Abort();
}

///
/// 本线程的状态。
///

public bool IsAlive
{
get{return this.thread.IsAlive;}
}
///
/// 事件等待循环。
///

void EventLoop()
{
try
{
while(true)
{
// 等待键盘输入
string line = Console.ReadLine();
char eventCode = (line == null || line.Length == 0) ? '\0' : line[0];

// 事件的处理通过代理专递给其他具体的函数。
if(this.OnKeyDown != null)
this.OnKeyDown(eventCode);
}
}
catch(ThreadAbortException)
{
}
}
}

没有评论:

发表评论