电池|处理WM_KILLFOCUS消息时需要注意的地方

电池|处理WM_KILLFOCUS消息时需要注意的地方

文章图片


之前我在一篇文章中曾经提过 , 不应该利用 WM_KILLFOCUS 消息中对表单的字段进行有效性校验 。
今天的文章 , 我将介绍另外一个反面例子 , 来表现当使用 WM_KILLFOCUS 消息处理焦点相关的问题时所带来的混乱 。
假设 , 有一个编辑框控件使用了气球提示来显示反馈信息 。 举个例子 , 对于一个密码输入控件 , 当键盘上的 CapsLock 按键按下时 , 它会提示用户 , 以防止用户输入错误的密码 。 作为开发者 , 你可能希望 , 当用户将输入焦点从密码输入框移动到另外一个控件时 , 将气球提示移除 , 这是合情合理的 。 因为向用户给出一个他完全不会使用到的控件提示 , 会让用户感到莫名其妙 。 可以通过对输入控件子类化来实现上述的需求 , 如下图所示:

使用上面的代码进行初步测试 , 一切如预期般正常工作 , 只是有一个问题:当用户点击气球提示时 , 编辑框的输入光标会意外的消失 , 这是为啥?
发生的事情是 , 你通过破坏焦点要去的窗口来破坏焦点变化过程!焦点更改过程如下所示:
> 将焦点移动至新的窗口 。
> 发送 WM_KILLFOCUS 消息给丢失焦点的窗口(如果有的话)
> 发送 WM_SETFOCUS 消息给新的焦点窗口(如果有的话)
但是在上面的第二步 , 我们销毁了新的窗口 。 当焦点窗口被销毁后 , Windows 窗口管理器会尝试找另外一个焦点窗口 , 最终它将输入焦点设置到了输入框控件本身 。 这将启动一个递归式的焦点更改过程 , 告知编辑控件它现在再次具有输入焦点 。
让我们看一下当用户单击工具提示窗口时焦点变化流程 。
> 设置焦点到工具提示 。
> 发送 WM_KILLFOCUS 消息到输入框 。
> EditSubclass 销毁了工具提示窗口 。
> Windows 窗口管理器将焦点设置到输入框 。
> WM_KILLFOCUS 不会发送给任何窗口 。
> 发送 WM_SETFOCUS 给输入框 。
> EditSubclass 将 WM_SETFOCUS 消息传递给原始窗口过程 。
> EditSubclass 将 WM_KILLFOCUS 传递给原始窗口过程 。
你看到这里面的问题了吗?
下面是到达原始编辑框窗口过程的消息流程:
> WM_SETFOCUS(源自嵌套的焦点变化过程)
> WM_KILLFOCUS(源自原始的焦点变化过程)
【电池|处理WM_KILLFOCUS消息时需要注意的地方】就编辑控件而言 , 它获得了焦点 , 然后失去了焦点 。 因此 , 它不会显示输入光标 , 因为编辑控件仅在具有焦点时才显示光标 , 并且递归焦点变化会导致编辑控件认为它没有焦点 , 即使它有焦点 。
摆脱这种混乱的方法有很多 。
首先 , 请注意 , 你不需要对编辑控件进行子类化 , 你可以对 EN_KILLFOCUS 通知做出反应 。 其次 , 你还可以通过向自己发布消息并在收到该发布消息后销毁工具提示来响应 EN_KILLFOCUS 。 通过发布的消息执行此操作 , 你可以避免递归焦点变化 , 因为你的工作现在正在焦点更改周期之外完成的 。
总结请不要忽视这些细枝末节的问题 , 资深用户会感受到你的程序里的各种细节 。
如果是好的细节 , 你的程序会加分 , 万一是不那么好的细节 , 那就不是一件好玩的事儿了 , 你的用户会慢慢离你而去 。
最后Raymond Chen的《The Old New Thing》是我非常喜欢的博客之一 , 里面有很多关于Windows的小知识 , 对于广大Windows平台开发者来说 , 确实十分有帮助 。