Fighting with Windows Message
Posted by Tim under 技术日志 | Permalink | | Leave A Comment | No Comments
已经转轨到新项目有些日子了,这阵子主要focus在新项目的代码Debug上。Windows Message的一个问题着实让我这低手花了些时间。
首先得说明几点Windows在messages上的默认行为:
1. 鼠标位于NCArea内时,如果左键不按下,移动时触发的是WM_NCMOUSEMOVE;如果左键按下,移动时触发WM_MOUSEMOVE。从按下到松开,鼠标移动的消息从MOUSEMOVE变成了NCMOUSEMOVE,这个转变会触发TrackMouseEvent被调用。
2. TrackMouseEvent是一个很有意思的函数,又一次深 刻的意识到Platform SDK的函数还真是给我们很“方便”使用的(一句废话,“方便”之所以加引号是因为MSDN总是只给我们很少的几个关键句,总是在我兜了一大圈之后回头看MSDN,才感到这几句话的重要)。如果有人想订制诸如WM_MOUSELEAVE或WM_NCMOUSELEAVE消息时时,他需要handle WM_MOUSEMOVE或WM_NCMOUSEMOVE或WM_MOUSEHOVER消息,在消息中调用TrackMouseEvent或者_TrackMouseEvent函数(后者在comctl32.dll中)。设定好TrackMouseEvent的传入参数后,就可以安心等待接收Leave消息了。
一切看似平常关键是,什么时候发Leave消息?MSDN说了,当鼠标正在离开当前窗口,或者在一定范围内停留一定时间时发出。并且,只发出一次。
问:那么如果我鼠标左键在最小化按钮内按下会怎样?答:只要鼠标不移动位置,不会怎么样。
问:那么,如果我鼠标按住左键不放,移动鼠标会怎样?答:此时消息会从WM_NCMOUSEMOVE变为WM_MOUSEMOVE。
问:那如果此时我还只是在最小化按钮内移动,会怎样?答:那么对不起,一个不该发出的WM_NCMOUSELEAVE消息就发出来了,接着,因为你订制了最小化按钮的外观,那么它的样子就会从Pushed变为Normal,因为它“认为”鼠标已经移出了它的区域。
问:为什么会这样?答:因为在NCArea,鼠标按下与不按下时MOUSEMOVE消息是不一样的,这会误导TrackMouseEvent函数,当消息队列里下一个消息不再是上一个NCMOUSEMOVE,而是MOUSEMOVE时(事实上,任意一个不是NCMOUSEMOVE的消息都一样),TrackMouseEvent函数认为:恩,是时候发出MOUSELEAVE消息了(至于发出的是WM_MOUSELEAVE还是WM_NCMOUSELEAVE,完全取决于你的订制)。于是,杯具就不可避免的发生了,最小化按钮有了它不该有的显示状态。。。
到此为止,本文结束。不过有两个问题遗留了下来:
1. 这样的行为能够100%确定吗?不能。因为其中很关键的一点:鼠标事件从NCMOUSEMOVE变为MOUSEMOVE时,TrackMouseEvent真的认为是离开了区域并且发消息吗?不得而知。
2. 除了用系统(或者IE)默认的行为,或者不使用MOUSELEAVE,就没有办法解决这种情况了吗?有待确定。
OK. Time to go to bed. Tomorrow is another day.