问题
今天我们来聊一聊Windows里比较少用到的两个消息:
WM_UPDATEUISTATE和WM_CHANGEUISTATE。
写这篇文章的原因,也是因为在开发中碰到一个界面绘制的问题,后来发现这个问题和这两个消息有关,当然了,这个问题在这里就不详细展开了,我可能会在另一篇文章里再写。
在讲述这两个消息之前,我先介绍一个概念:keyboard cues。我就先将它翻译成”键盘指示器”吧。
我们打开电脑的计算器程序,然后按下键盘的Alt按键,可以观察到计算器程序的菜单栏出现了两个变化:
1) 当前活动的菜单项被一个矩形框所包围
2) 所有菜单项文字中括号里的字母出现了下划线标识。
如下图所示:
那么,这里出现的矩形框和下划线,就是所谓的keyboard cues了,也叫做:keyboard indicator。
MSDN的解释
我们先来看看这两个消息的WPARAM参数在MSDN中的解释:
这个32位的WPARAM被分为两部分:Low-order word 和 High-order word。
这里你会感觉有一种双重否定的味道:
如果希望显示矩形框,则需要设置WPARAM为:UIS_CLEAR + UISF_HIDEFOCUS。
如果希望隐藏下划线,则需要设置WPARAM为:UIS_SET + UISF_HIDEACCEL。
WM_QUERYUISTATE
这里再提一下WM_QUERYUISTATE这个消息,因为每个窗口结构会有一个内部成员记录着keyboard indicator的显示状态,所以我们可以使用这个消息来获取指定窗口的keyboard indicator的显示状态。
WM_CHANGEUISTATE消息
当一个窗口希望能改变和它在同一个窗口级别的所有子窗口的keyboard indicator的显示状态时(这句话有点长,需要好好理解),这个窗口可以向它自身或者它的父窗口发出此消息。WM_CHANGEUISTATE消息将由子窗口向父窗口层层向上传递,父窗口会查看消息里的WPARAM,如果窗口当前的状态和WAPRAM参数要求的是一致的时候,消息传递中止。如果发现不一致,则父窗口会继续向上传递,直至消息到达top-level窗口。
当top-level窗口收到此消息时,表明当前窗口的状态确实需要更新,所以top-level窗口会首先向它自身发出WM_UPDATEUISTATE消息,系统会完成要求的界面状态变更,然后将WM_UPDATEUISTATE消息层层向下传播至所有的子窗口。
WM_UPDATEUISTATE消息
当一个窗口收到WM_UPDATEUISTATE消息时,表明此时窗口需要进行界面更新了,系统会根据WPARAM的要求完成keyboard indicator的显示或者隐藏,值得注意的是,此消息在本窗口完成处理后,还会被广播到此窗口的所有子窗口。这样,就可以保持各层级窗口的界面显示一致性。
实例讲解
我们使用一个简单的例子来讲解以上的内容。设想有三个窗口,一个父窗口P,它有两个子窗口C1和C2。在初始状态下,所有窗口的keyboard indicator都是隐藏状态,也即UISF_HIDEACCEL和UISF_HIDEFOCUS都为SET状态(想想双重否定),如下图所示:
当用户按下Alt按键,用户预期窗口F的菜单项显示矩形框,如果此时窗口C1拥有输入焦点,则它会向它自身发出WM_CHANGEUISTATE消息,消息的WPARAM参数会设置为:UIS_CLEAR + UISF_HIDEFOCUS。
窗口C1收到WM_CHANGEUISTATE消息后,发现当前窗口状态和WPARAM要求的不一致,它会继续向它的父窗口(F)发送WM_CHANGEUISTATE消息。
当父窗口F收到WM_CHANGEUISTATE消息时,因为它已经是top-level窗口了,所以它不再向上传递此消息。它会将收到WM_CHANGEUISTATE消息转化为WM_UPDATEUISTATE消息,并首先发送给自身。当收到WM_UPDATEUISTATE消息后,父窗口F完成界面更新(显示矩形框)并继续将WM_UPDATEUISTATE消息向下传递至它的两个子窗口(C1和C2)。C1和C2收到WM_UPDATEUISTATE消息后,也会分别完成各自的界面更新,因为它们都没有子窗口了,至此WM_UPDATEUISTATE消息处理完毕。状态变化如下图所示:
通过以上一系列的消息路由过程,我们可以通过发送WM_CHANGEUISTATE消息的方式来向整个窗口树广播我们的意图(显示或隐藏keyboard indicator),进行实现统一的界面展示效果。
总结
1) 目前,随着Windows 10的推出,我们已经越来越少用到keyboard indicator这个特性了,因为大部分时候,鼠标操作比键盘操作更方便。但也存在一些比较特殊的情况(你的鼠标被人拿了),那么使用键盘的Alt按键激活keyboard indicator,就可以实现菜单的选择以及输入焦点的切换了。
2) 探究WM_UPDATEUISTATE和WM_CHANGEUISTATE这两个消息,有助于我们理解Windows图形界面的绘制原理和底层设计意图。虽然有点绕弯,但是还是挺值得了解一下的。