处理系统队列中的鼠标事件
下面是处理鼠标事件的步骤。
1. 首先,将计算该事件屏幕坐标的相应窗体。此计算(调用窗体点击测试)以桌面窗体开始,从头至尾的扫描细统中的每一个窗体(包括子窗体),直到找到一个包含这个鼠标坐标点的窗体,并且这个窗体没有任何同样包含这个坐标点的子窗体。
图2. 鼠标事件的窗体点击测试
例如:如果图2中的箭头代表当前的鼠标位置,任何的鼠标行为,像单击鼠标键,将生成一个会在B窗体中产生消息的事件。
2. 如果一个窗体使用SetCapture捕获鼠标,那么“系统队列扫描”代码将通过普通的点击测试,并将所有的鼠标消息返回到捕获的窗体。例如:如果在图2 中的A窗体调用了SetCapture,则在箭头所指位置的所有鼠标行为,将产生窗体A中的消息,而不是窗体B。
3. 如果这个被处理的事件是一个“鼠标键按下”事件(任何一个鼠标键),代码会检测这个事件是否会转化为双击事件。你可以在微软开发者网络(译者注:MSDN)CD(技术文章,Ask Dr. GUI)中的“Ask Dr. GUI #5”中找到关于双击转化的描述。实质上,如果在两次鼠标键按下事件中,时间和距离的增量在允许的范围之中,该事件将会生成一个双击消息,否则它将生成一个标准的“按下”事件。所有的鼠标事件都将生成标准的鼠标消息,而双击测试只在鼠标事件指定的,包含CS_DBLCLKS类型的窗体中进行。
4. 一个消息从鼠标事件中构造出来。
5. 如果鼠标点击测试确定该事件发生在一个窗体的非客户区,如边框或标题栏,那么该构造出的消息映射到它相应的非客户区消息中。例如:一个WM_MOUSEMOVE事件会被映谢为WM_NCMOUSEMOVE消息。
6. 与所有指定的消息过滤器进行对照,核查此消息。(请参阅下面的“消息范围过滤和窗体句柄过滤”)如果该消息不匹配过滤器,则重新从头开始“系统队列扫描”代码,查看队列中的下一个消息。
7. 如 果鼠标消息需要前往与当前任务不同的另一个任务的相关窗体,事件会被留在系统队列中,并且如果那个将会处理这个消息的任务在休眠之中,会被唤醒。这个新近 被唤醒的任务不会在此刻立即运行,只会标记为准备运行。如果消息前往了其它任务,并且在系统队列中没有要处理的事件被发现,“系统队列扫描”会代码返回到GetMessage/PeekMessage主代码。请参阅下面的“让步与休眠的不同”以获得更多的信息。
8. 如果安装了鼠标钩子,它将在此刻被调用。如果鼠标钩子返回了一个非零值,那么该鼠标事件被忽略,并从系统队列中被删除,然后重新从头开始“系统队列扫描”代码。如果钩子返回零,则继续处理。
9. 如果消息是一个“鼠标键按下”消息,“系统队列扫描”则会在返回此消息之前,按照下面的方法激活窗体。
?它沿着父链一直向上寻找该窗体的“最终顶层父窗体”,直到相遇。
?它用SendMessage向该窗体的“最终顶层父窗体”发送一个WM_MOUSEACTIVATE消息。
?从WM_MOUSEACTVATE返回的值将在下面被测试:
a) 如果返回的值为空、MA_ACTIVATE或者MA_ACTIVATEANDEAT,ActivateWindow函数将被调用去激活那个“最终顶层父窗体”。
b) 如果返回的值是MA_NOACTIVATE或者MA_NOACTIVATEANDEAT,窗体则不被激活。
注意:MA_ACTIVATEANDEAT和MA_NOACTIVATEANDEAT会导致“鼠标键按下”事件从系统队列中被删除,而不会生成一个鼠标按下消息。
c) 最终,一个WM_SETCURSOR消息被发送到窗体,充许窗体设置指针的轮廓。
10. 如果鼠标钩子被调用,并且当前的鼠标事件从系统队列中被删除了,则检查“基于计算机训练”(CBT)的钩子。如果安装有有一个CBT钩子,将会携带HCBT_CLICKSKIPPED钩子码代调用它。
11. 按键状态表包含了三个用于跟踪鼠标按键状态的入口。这些按键被分配予虚拟键代码(VK_LBUTTON,VK_RUTTON和VC_MBUTTON),它们和GetKeyState一起始用去确事实上鼠标键是弹起还是按下。在返回鼠标消息之前,“系统队列扫描”代码会(为弹起或按下消息)设置按键状态表并且从系统队列中删除消息。如果PeekMessage被调用时携带PM_NOREMOVE,则按键状态表不会被修改。
处理系统队列中的键盘事件
1. 检查是否Ctrl键被按下和当前的事件是否ESC键按。如果是,该用户——直接窗体——会显示任务管理器窗体。一个WM_SYSCOMMAND消息将被发送到激活的窗体,并且参数wParam为SC_TASKLIT。然后键盘按下事件从系统队列中被删除,“系统队列扫描”代码又将重新从头开始。如果此激活的窗体是一个系统模块或者是一个被显示出来的“硬”系统模块消息框(比如一个“INT”24小时系统错误消息框,或一个使用MB_ICONHAND和MB_SYSTEMMODAL参数的MessageBox函数)的事件,将会被抛弃。
2. 下一步,试着去查看当前的事件是不是一个Print Screen键的按下事件。如果是,任意一个激活的窗体或整个桌面将被做为一个位图快照,保存到剪贴板中。如果Alt键被按下,一幅激活窗体的图像被复制到剪贴板中;如果没有,则是整个桌面被复制。然后Print Screen键按下事件从系统队列中被删除,“系统队列扫描”代码又将重新从头开始。如果显示了一个“硬”系统模块消息框,则此操作被忽略。
3. 下一步检测热键。使用程序管理器,用户可以定义用来运行一个应用程序的击键事件。这些击键被称为热键。如果当前的事件是一个按键事件,将会被测试是否与定义过的热键匹配。如果发现匹配,一个WM_SYSCOMMAND消息将被发送到激活的窗体,并且参数wParam为SC_HOTKEY。然后键盘按下事件从系统队列中被删除,“系统队列扫描”代码又将重新从头开始。如果此激活的窗体是一个系统模块或者是一个被显示出来的“硬”系统模块消息框,该测试被跳过。
4. 一般情况下,所有的键盘消息(如WM_KEYDOWN、WM_CHAR等等)前往具有输入焦点的窗体。如果这个具有输入焦点的窗体与另一个当前执行的任务相关联,那么该事件会被留在系统队列中,并且那个拥有“有焦点的窗体”的任务会被唤醒(如果休眠了)。“系统队列扫描”代码会像没要发现任何要处理的事件一样,返回到主GetMessage/PeekMessage代码。请参阅下面的“让步与休眠的不同”和“应用程序如何被唤醒”以获得更多的信息。
5. 如果遇到了没有任何一个窗体具有输入焦点的情形,键盘消息会直接前往当前激活的窗体,而不会被翻译成为系统键消息(如WM_SYSKEYDOW,WM_SYSCHAR,等等)。
6. 与所有指定的消息过滤器进行对照,核查此消息。(请参阅下面的“消息范围过滤和窗体句柄过滤”)如果该消息不匹配过滤器,则重新从头开始“系统队列扫描”代码,查看队列中的下一个消息。
7. 如果事件被返到了当前的任务,它将从系统队列中被删除掉,除非PeekMessage被指定为PM_NOREMOVE标记。请参阅下面的“PeekMessage的PM_NOREMOVE标记”以了解更多的关于不从队列中删除事件的信息。
8. 如果安装有键盘钩子,将在此刻被调用。如果事件从系统队列中被删除了,钩子的调用将伴随HC_ACTION属性;如果事件未被从系统队列中删除,钩子的调用将具有HC_NOREM属性。
9. 如果键盘钩子被调用,并且当前的按键事件从系统队列中被删除了,则检查现存的CBT钩子。如果安装有CBT钩子,将调用它并携带HCBT_KEYSKIPPED钩子码。
10. 最后,消息被返加到主GetMessage/PeekMessage代码。
PeekMessage与PM_NOREMOVE
默认情况下,每一个消息被返回到应用程序后,PeekMessage和 GetMessage都会把消息和事件从系统队列中删除。然而有些时候,某个应用程序可能需要扫描队列中现存的消息而并不删除它们。例如,某个应用程序在做一些处理过程,这些处理过程期望“一但发现有可用的消息,就尽快终止”。