本文中的例子源代码请到这里下载:
Smartphone上用.NET CF截获Back键的演示程序
当然,这个程序本身并非一定要用截获Back键这样的方式来设计,甚至应该有更好的设计方式。这个例子并不时为了给大家演示Back键如何出问题的,而是为了演示如何截获Back键,并进行自己的逻辑设计。代码并不时非常完善,凑合着看看吧。
现在我们假设有这么一个任务:设计一个“实时”在线系统,为天文学家提供一个宇宙重大事件预报提醒功能。这些重大事件从发生到消失可能很短,也许只有一天的事件可以观察,所以希望不要因为不小心碰到哪一个按钮错过了这个事情。另外程序启动之后希望能够尽量避免窗体的创建和销毁,因为也许这个窗体上面有很多的控件,比较浪费时间。(这里并没有太多的控件)
根据这个任务的要求,我们可能希望这个窗口在按了Back键之后:
1、提醒天文学家这个现象很快就会结束,是否真的要等一会儿再来处理这件事情等等。
2、即使天文学家真的希望该窗口在他眼前消失,我们也不希望这个窗口被关闭。
实际上当你按下Back键之后,操作系统会给你的窗口发送WM_HOTKEY。在一个Form接受到这个消息之后,如果没有任何控件处理该消息,那就会把当前窗口的ZOrder改变到后台,这个是用VC2k5写SmartPhone程序测试的结果。也许.NET CF内部还有他的黑箱操作,比如会把这个消息翻译成WM_KEYPRESS,该消息会引发KeyPress事件,最终我们可以接受到一个Keys.Escape。如果我们这个时候把e.Handled设为true,系统默认行为就不会发生。但这个事件在.NET CF里面只会在有焦点的控件上面引发,这一点实际上在某种情况下是有一些缺陷的。就像当我们希望截获Back键这一个情况,如果我们期望通过KEYPRESS事件来得到,并进行处理,我们不得不为所有可能有焦点的空间都绑上委派。这个方法也许不是很美观,因为要么每一个new出来的form都通过一个函数进行遍历和绑定,要么每一个窗口类型都是派生自同一个窗口类型。无论如何,对于构造来说就可能会产生效率问题。而实际上操作系统只是想应用程序发消息,应用程序接到消息之后会向Form转发。Form接受到了HOTKEY消息之后,会查找是否有用有焦点的控件,有就会向他转发该消息。如果控件不表示处理该消息,则Form就会试图改变ZOrder。
很明显,如果我们截留了发送给Form的消息,则Form就不会做任何事情。当然我们也可以让程序进行判断,或者让用户来选择是否执行默认的行为。可是并不是将这个发送给Form的消息直接进行处理就完事了,如果那样的话我们会发现TextBox没有办法删除字符了。因此我们在接受到发送给Form的消息时,需要首先判断是否有用有焦点的控件,这些控件是否处理Back键。如果一个控件表示会处理KEYDOWN(0x1b Escape)这个消息,则继续处理HOTKEY消息,否则才进行你设计的其他逻辑。
为什么这里选择了KEYDOWN消息而不是HOTKEY呢?因为只有KEYDOWN和KEYPRESS消息才会返回“是否被处理”,选择KEYDOWN来进行测试也许副作用要少一点。那为什么不干脆直接发送一套KEYDOWN/KEYPRESS/KEYUP消息来模拟一个删除操作呢?在某些情况下发送backspace的keydown等消息是能够起到删除前一个字符的作用,看起来跟Back键的效果一样。但是在至少已种情况下会出现异常:正在用中文输入法进行输入的时候,比如刚输入了拼音“b”,发现不对想删掉修改成“p”,这个时候就会出现“异常情况”,具体如何大家可以自己试验一下。
Oh, wait! 上一篇文章里面不是说无法通过WndProc/MessageFilter来截获消息吗?没错,所以我们需要参照OpennetCf上面的ApplicationEx内部的代码。那一个ApplicationEx竟然实现了诸如MessageFilter的功能,说实话却是足够强大了,但是在我看来代价有点高昂。因为暂时来说我只是希望处理一下这个Back键的功能,为了这么简单的一件事情而需要装载整个OpennetCF得不偿失。所以我决定写这么一个简单的AppRunner来处理这一个问题。其实Application就是简单的通过PeekMessage/GetMessage/TranslateMessage/DispatchMessage等系统函数来维护这么一个消息循环的,我们自己来写一个这样的东西也并不困难。具体的代码我就不在这里罗嗦了,如果有什么疑问,欢迎讨论。