2012-7-8
cswuyg
不要在WM_ACTIVATE里调用可以触发活动转移的函数
一、来源
WM_ACTIVATE的问题6月19号发现了,当时发现窗口会收到两次WM_ACTIVATE消息,很是奇怪,但找不到入手点,就没有深入追究。前几天同事发现有个按钮点击了没反应,是因为WM_LBUTTONDOW消息丢失,导致按钮事件没有生成,这才发现是WM_ACTIVATE有问题,总算找到原因了。但这是为什么呢?这就需要详细分析了。
二、详细分析
避免在WM_ACTIVATE里调用可能触发活动转移的函数(如:ShowWindow( SW_HIDE ));
有一个窗口A显示着,点击了跟窗口A使用同一个消息线程的B窗口,让A窗口因为失去活动而隐藏,如果在A窗口的WM_ACTIVATE失活动消息的响应里,调用本窗口ShowWindow(SW_HIDE),那么可能导致B窗口没有收到WM_LBUTTONDOWN。所以,千万不要在WM_ACTIVATE里调用ShowWindow(SW_HIDE)。而应该PostMessage,把ShowWindow放在其它地方做。
这种情况是在用窗口模拟实现菜单,这个菜单有开关效果,还有失活动隐藏效果的时候出现的。
假设菜单窗口为MenuWnd,跟菜单窗口有同一个UI线程的主窗口MainWnd。
错误的过程如下:
菜单窗口MenuWnd在显示的情况下,点击主窗口MainWnd的客户区。这个时候,菜单窗口收到WM_ACTIVATE,WM_ACTIVATE消息里做了菜单窗口的隐藏ShowWIndow( SW_HIDE ),而这个时刻,活动还没有设置到主窗口上(因为菜单窗口跟主窗口使用同一个消息队列,菜单窗口的处理阻塞了(参考MSDN中的WM_ACTIVATE)),此时活动的还是菜单窗口,它隐藏后又触发了WM_ACTIVATE,于是菜单窗口第二次收到WM_ACTIVATE,这时候的ShowWindow( SW_HIDE )不会执行(因为已经隐藏了),所以不会出现死循环,接着主窗口处理WM_ACTIVATE获得活动,ShowWIndow( SW_HIDE )执行完毕返回,继续执行第一次WM_ACTIVATE的处理,然后返回。
消息流是这样的:
菜单窗口失活动WM_ACTIVATE-->菜单窗口隐藏-->菜单窗口失活动WM_ACTIVATE-->主窗口获活动WM_ACTIVATE-->菜单窗口失活动响应返回-->主窗口收到WM_LBUTTONUP.
本来以为菜单窗口失活动响应返回后还会有一次主窗口获取活动消息,一次菜单窗口失活动响应返回,因为A窗口失活动消息、A窗口失活动返回、B窗口获取活动这三个是配对存在的,但用spy++抓取,没发现。我只关注主窗口:主窗口的WM_ACTIVATE获活动消息丢失了,而且主窗口的WM_LBUTTONDOWN也丢失了。
如果点击的不是主窗口区域,而是在不同UI线程里的其他进程窗口,那么菜单窗口ShowWindow(SW_HIDE)不会再触发WM_ACTIVATE,那就不会有问题。
可以猜测,为什么系统不再给主窗口发出WM_ACTIVATE呢?也许是因为发现已经发过一次比这次WM_ACTIVATE更新的消息了,那么这个消息就被丢弃,连带它的WM_LBUTTONDOWN也被丢弃。猜测:当发生WM_ACTIVATE重新设置时,会导致还没派发的鼠标点击消息丢失。
附图:spy++抓取,在错误的代码里,点击主窗口的一个按钮,活动如何从菜单窗口转到主窗口(按钮窗口),主窗口(按钮窗口)没收到WM_LBUTTONDOW消息
另外,通过syp++查看测试小程序,系统发送消息的顺序,可能是这样子的(点击的是非客户区):WM_NCLBUTTONDOWN --> WM_ACTIVATE --> WM_LBUTTONUP这样的顺序产生的,又或者是这样子(点击的是客户区):WM_ACTIVATE --> WM_LBUTTONDOWN --> WM_LBUTTONUP。WM_NCLBUTTONDOWN消息早于WM_ACTIVATE。
附上简单的验证代码
http://files.cnblogs.com/cswuyg/%E6%B5%8B%E8%AF%95%E4%BB%A3%E7%A0%81%EF%BC%8CActivateTest.rar