令人感到非常讨厌的Back键

最近好长时间没有写东西,不是没有东西可以写,而是最近整天在观看一些东西,看完之后就不想写了。请容许我先写写与主题无直接关系的东西吧。呵呵,大家不要想歪,我说的东西是指 NASA里面 介绍宇宙发现的地方,还有 哈勃望远镜的官方网站上面的 桌面背景。每一次“遨游”太空之后,就有有如下症状:
1、觉得实在有太多我们所不知道的东西了,极度兴奋;
2、觉得我们不过是一颗灰尘,如果宇宙从诞生到目前为止算作一年,那么我的一生也就只有5纳秒,着实让人沮丧;
3、看看黑洞,现在的理论(经过诸如牛顿、爱因斯坦还有霍金等巨牛的人千锤百炼)都没有办法解释里面发生了什么事情,换句话说现在都没有能够认为真正正确的物理学理论。想想自己对Programming知识的认知,肯定有很多漏洞和贻笑大方的地方,真担心一些就错,牛人看完会摇头说我误人子弟……
4、其实很多东西可能没有正确和错误之分,唯一能够肯定的是他存在,至少存在于我的感官当中。因此像XP或者别的什么方法,应该怎么样在Blog上面写呢?
令人感到非常讨厌的Back键


让我们回到这一次的主题——“令人感到非常讨厌的Back键”。这个“Back”键指的是WindowsMobile上面的一个返回键,如果大家对用.NET CF进行WindowsMobile的开发没有经验,并且也毫无兴趣的话,那就不要浪费时间了。

首先给没有摸过WindowsMobile的朋友解释一下这个Back键。我们在使用手机的时候,经常会有进入-返回这样的操作,有一些手机并没有专门用来返回的按键,但是至少也会在每一个窗口有一个按键用于返回上一层。在WindowsMobile的机器上面就把这一个功能独立安置在一个按键上面,叫做Back键。这一个Back键还承担了另外一个功能,就是当用户进行文本输入的时候,用于删除上一个字符,相当于Backspace。按照MS的规范,要求设计出来的应用程序必须能够在用户按了Back键之后,做出如上所述的正确响应。而实际上如果没有进行任何特殊处理的话,操作系统就会“帮”你完成相应的响应。这个规范的意思也许就是说“大家最好不要对这个键用自己的方法进行特殊处理”。一切看起来还像没有什么问题,甚至不应该成为问题——多么美好啊,操作系统已经处理了,我啥也不用做了。

然而很不幸的是,这个处理却不见得完全正确。当你只有一个窗口的时候,确实很愉快,该怎么样就是怎么样的。但是当整个程序有N个窗口的时候,随着N的增加,你会发现越来越多的问题,并且越来越棘手。

switch(N)
{
case 0:
case 1:

这个时候没有什么问题


break;

case 2:


当一个程序除了主窗口之外还有另外一个窗口,最简单的办法就是在需要显示出来的地方new一个出来。很不幸的是,如果你打算这样的话,back键是不会替你“关闭”这个窗口的。因此内存当中的窗口数量就会越来越多,也算是一种内存泄露。更糟糕的是,Form1->new Form2->Back(Form1)->new Form2->Back(Form1)->Back(Desktop),这样就会造成“严重”的错误:你再通过运行这个程序去激活已经存在于内存的程序的时候就会发现,显示出来的不是Form1,而是Form2,更严重的是,再按一个Back键,就会回到了桌面,而不是你以为的Form1。
问题一:同一个类型的Form拥有超过一个实例,会破坏系统维护的显示顺序。

这个时候大家一定已经想到用类似Singleton或者monostate之类的方式,去维护不同类型的窗口。这确实是一个不错的解决方式,甚至我怀疑MS就是期望大家这么设计SmartPhone上面的应用程序的。但是如果我们仔细思考“问题一”,就会发现即使其他所有窗口都是只有唯一一个实例,但有一个类型的窗口是例外的话,问题仍然可能产生。尽管这是一个很简单的窗口,有时候要达到这个要求是比较困难的。举一个简单的例子,frmMain用于记录当前测量到的宇宙背景温度,同时希望能够有一个frmLast显示上一次的记录情况,当然,也许会有一些自动分析出来的其他数据图表显示在这个窗口上面。很明显,frmMain和frmLast都可以通过一个Form1类型来实现。这个时候问题一会不会出现呢?

其实还有更多的特殊情况,比如说:也许你的Form2比较占用资源(比如10MB的内存,用来记录两个超巨黑洞发生碰撞时泄漏出来的信息),并且使用的机率比较低(仙女座星系撞上银河系的时候可能会用上一次),所以你想每一次显示的都是new Form2,然后Form2里面有一个OK菜单项用于确定当前的输入,Close,返回到Form1上面去,另外还有一个Cancel菜单项除了放弃当前输入之外,一切都和OK一样。写好代码运行,很好,没啥毛病……哎呀,不小心碰到了Back键……也没有毛病啊,还是挺好的。如果处理得比较好,比如用Singleton/Monostate之类的方式进行管理,是不会再第二次进入的时候又产生一个实例。但是不要高兴得太早,因为那个Form2现在正占着茅坑不拉屎呢。实际不要说10兆了,8兆都不一定吃得消,如果运行另外一个什么程序,就可能因为你的程序占用着最大的内存而不得不被枪毙掉。更要命的是所有运行在同一个AppDomain下面的.NET CF程序并不会互相之间排挤,或者说操作系统并不会在启动netcfApp2并发现内存不够的时候把netcfApp1给关掉,而是抛出一个OutOfMemoryException。虽然实际上是那个占着茅坑不拉屎的netcfApp1造成了这个问题,但是用户只会把不工作的netcfApp2给卸载掉,并永远不再使用。

问题二:我希望窗口消失的时候要注销掉,包括用户按Back键的时候。

break;

case 3:

如果说在两个窗口的时候,很有可能一个是用于显示信息的主窗口,另外一个是用于进行信息录入的辅助窗口。那么在三个窗口的时候很可能会有这么一种模式:第一个窗口还是显示信息的主窗口,第二个窗口是输入简单信息的地方,比如输入在黄道经纬什么地方发现了一个正在爆发的超新星,而第三个窗口则用于输入详细的信息,比如这个超新星的质量、X射线强度、是否处于双星系统、尘埃温度多少等等。也就是说第二、三个窗口之间是有附属关系的,这个时候如果用户恰好安装了一个进程管理的小工具,就会看到这三个窗口。也许用户会在第三个窗口输入的时候,通过进程管理器“跳转”到第二个窗口并修改一通,再跳回第三个窗口“确定”。为了让程序在这样的操作下绝对正确,需要额外的一些代码来维护。比如可以在第三个窗口“确定”的时候检查第二个窗口是否正确或者是否被修改过,或者让Enable变成False。前者不够简洁,后者还是让用户感到困惑。也许有第三种方法,比如Visible=false。说到了Visible=false这个看似简单有效的方法,就会牵扯出更多的问题出来。

如果我们一共只让一个窗口显示出来,是最能够让用户理解和接收的,但这个方法却让开发人员非常不好受。首先我们必须保证每一个窗口在消失之前让其它窗口显示出来,否则你就会发现该显示出来的没有显示出来。其次就是“可爱的”Back键给我们造成的麻烦(也有.NET CF的功劳),只要用户一按Back就会走系统的逻辑——当前窗口消失并把上一个Visible=true的窗口BringToFront,也就是说不会走开发人员写的代码。这就严重了,因为当前只有一个窗口是Visible=true的,程序一下子就“退出”了。“别急,我们可以截获Deactivated、KeyPress等等一系列事件,或者考虑一下WndProc、Filter或者其它什么截获消息的东西……”
Deactivated方法却是可以知道自己这个窗口被“换出”了,但是有两个非常主要的缺点:1、无法自动区别是开发人员的代码在起作用还是用户按下Back键在起作用;2、这个事件发生的时候,窗口已经消失了,如果当前窗口已经消失了,是不可能通过.Net Cf框架内的解决方案来显示另外一个窗口的。
KeyPress事件里面如果收到Escape这个键,就表示用户按了Back键,这个时候e.Handled = true就可以阻止系统行为。但是 如果窗口上面有一个获得了焦点但不处于输入状态的ComboBox,这个事件就发生在这个ComboBox上面而不是Form上面。如果在一个窗口上面有多个ComboBox,那么每一个都需要截获该事件。还有一些我没有验证过,但是觉得很可能会引起问题的情况:比如这个ComboBox处于输入状态,那么这个Escape就不能够Handled,因为这个时候相当于删除上一个字符;再比如会不会有其它的有焦点空间也会抢在窗口之前接收这个消息呢?
WndProc和MessageFilter在CF里面都被咔嚓掉了,虽然WndProc这个函数还是存在的,但是它是internal virtual的,因此我无能为力。MessageFilter则干脆就没有。当然还有一个似乎有点关系的MessageWindow类,它可以发消息出去,也可以接收到他自己的消息,但是很明显他是不可能接收某一个Form的消息的,更不可能截获整个程序的消息。

问题三:我希望用户按Back键的时候能够知道发生了一件事情,并且有我来提供相应的逻辑,而不是操作系统自己去决定。

break;

default:

当窗口越来越多的时候,也就是逻辑越来越复杂的时候,很可能你需要控制整个状态流程,甚至是通过配置文件进行动态定制,于是你想要有一个状态机。但是你自己写的状态机必然会跟那个“可爱的”Back键产生冲突,比如已经界面已经退到上一层了,但是你的状态机还是在原来的位置,或者一些其它的奇异情况(视你的状态机的设计方式而定)。最近我参与的项目就是这种情况,挺麻烦的,至少比前面的几个情况都麻烦。

问题四:能不能够干脆让我来控制整个消息循环?

break;

}


办法当然是有的,不过看起来还是有点复杂……
欲知后事如何,且听下回分解。

你可能感兴趣的:(c)