实际上当你按下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等系统函数来维护这么一个消息循环的,我们自己来写一个这样的东西也并不困难。具体的代码我就不在这里罗嗦了,如果有什么疑问,欢迎讨论。