不要在WM_ACTIVATE里调用可以触发活动转移的函数

在WM_ACTIVATE中调用ShowWindow(SW_HIDE)会导致窗口失去焦点时出现问题,包括消息丢失等。应避免在此时直接隐藏窗口,推荐使用PostMessage进行异步处理。

不要在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

WM_HWIN WM_CreateWindowAsChild( int x0, int y0, int width, int height ,WM_HWIN hParent, U32 Style, WM_CALLBACK * cb ,int NumExtraBytes) { WM_Obj * pWin; WM_Obj * pParent; WM_HWIN hWin; //WM_ASSERT_NOT_IN_PAINT(); // May be included again in later versions to avoid application errors WM_LOCK(); Style |= WM__CreateFlags; // // Default parent is Desktop 0 // if (!hParent) { if (WM__NumWindows) { #if GUI_NUM_LAYERS == 1 hParent = WM__ahDesktopWin[0]; #else hParent = WM__ahDesktopWin[GUI_pContext->SelLayer]; #endif } } if (hParent == WM_UNATTACHED) { hParent = WM_HWIN_NULL; } if (hParent) { pParent = WM_LOCK_H(hParent); x0 += pParent->Rect.x0; y0 += pParent->Rect.y0; if (width==0) { width = pParent->Rect.x1 - pParent->Rect.x0+1; } if (height==0) { height = pParent->Rect.y1 - pParent->Rect.y0+1; } GUI_UNLOCK_H(pParent); } if ((hWin = (WM_HWIN) GUI_ALLOC_AllocZero(NumExtraBytes + sizeof(WM_Obj))) == 0) { GUI_DEBUG_ERROROUT("WM.c:\nNo memory to create window in WM_CreateWindowAsChild()"); } else { WM__NumWindows++; pWin = WM_LOCK_H(hWin); pWin->Rect.x0 = x0; pWin->Rect.y0 = y0; pWin->Rect.x1 = x0 + width - 1; pWin->Rect.y1 = y0 + height - 1; pWin->cb = cb; // // Copy the flags which can simply be accepted // pWin->Status |= (Style & (U32)(WM_CF_SHOW | WM_SF_MEMDEV | WM_CF_MEMDEV_ON_REDRAW | WM_SF_STAYONTOP | WM_CF_DISABLED | WM_SF_CONST_OUTLINE | WM_SF_HASTRANS | WM_CF_ANCHOR_RIGHT | WM_CF_ANCHOR_BOTTOM | WM_CF_ANCHOR_LEFT | WM_CF_ANCHOR_TOP | WM_CF_LATE_CLIP | WM_CF_STATIC | WM_CF_MOTION_X | WM_CF_MOTION_Y | WM_CF_GESTURE | WM_CF_MOTION_R | WM_CF_ZOOM)); GUI_UNLOCK_H(pWin); // // Add to linked lists // _AddToLinList(hWin); WM__InsertWindowIntoList(hWin, hParent); // // Activate window if WM_CF_ACTIVATE is specified // if (Style & WM_CF_ACTIVATE) { WM_SelectWindow(hWin); // This is not needed if callbacks are being used, but it does not cost a lot and makes life easier ... } // // Handle the Style flags, one at a time // #if WM_SUPPORT_TRANSPARENCY if (Style & WM_SF_HASTRANS) { WM__TransWindowCnt++; // Increment counter for transparency windows } #endif if (Style & WM_CF_BGND) { WM_BringToBottom(hWin); } if (Style & WM_CF_SHOW) { pWin = WM_LOCK_H(hWin); pWin->Status |= WM_SF_ISVIS; // Set Visibility flag GUI_UNLOCK_H(pWin); WM_InvalidateWindow(hWin); // Mark content as invalid } #if WM_SUPPORT_NOTIFY_VIS_CHANGED if (hParent) { WM__SendMsgNoData(hParent, WM_NOTIFY_VIS_CHANGED); // Notify window that visibility may have changed } #endif WM__SendMsgNoData(hWin, WM_CREATE); } WM_UNLOCK(); return hWin; }
最新发布
03-25
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值