当前位置:C++技术网 > 资讯 > SetCapture捕捉鼠标全面深入详解

SetCapture捕捉鼠标全面深入详解

更新时间:2016-01-12 21:53:09浏览次数:1+次

    到底什么是捕捉鼠标?捕捉鼠标后,到底做了些什么事情?哪些情况可以捕捉鼠标?本文就鼠标捕获来做一个全面的讨论。
    平时我们知道,在鼠标移动的时候,使用捕获鼠标,似乎感觉这样比较妥。然而到底捕获鼠标是什么意思,为什么要捕获鼠标,捕获鼠标之后会怎么样,或者说,捕获鼠标的工作机制到底如何?
    大家几乎全然不知道,也并不关心这些细节。就是因为不知道这些细节,所以在使用上,经常有点滥用。或者用了之后就测试一下行不行,不行就撤掉,行就留着。至于为什么,who cares!
    然而这样来写程序,对于自身的水平的提高是很不利的。久而久之,你就形成了一个习惯,即能用就行。无形中,你就走入了平庸程序员的行列了。
    通过研究发现,鼠标捕获的使用,有很多不知道的细节需要我们来学习。也只有深入的了解了这些细节,我们才好灵活的运用鼠标捕获。
    直接处理鼠标移动消息,在鼠标移出客户区时,就接受不到鼠标移动消息了,也就无法处理了。然而,我们这里用上了鼠标捕获,才得以实现。
    捕获鼠标,实际上还是对消息的一种重定向。说来说去,Windows是基于消息的,捕获鼠标,只是改变了鼠标的消息流动方向。一个系统也就一个鼠标,鼠标时公共的。一次也只有一个活动窗口,或者叫做前景窗口。
    本来在没有任何窗口捕获鼠标的时候,鼠标是自由的。鼠标经过哪个窗口,鼠标就归谁所有。然而,当窗口要抢夺鼠标时,即捕获鼠标,那么鼠标就成为了一个稀缺的资源。捕获到鼠标的窗口,才能够单独占有鼠标的消息。

    我们先来看看鼠标捕获的消息流向图:

正常情况下和鼠标捕获后的鼠标消息流向示意图

【正常情况下和鼠标捕获后的鼠标消息流向示意图】
    当鼠标被窗口2捕获后,即使鼠标在窗口1上方,然而鼠标的消息还会流向窗口2。
    SetCapture捕捉鼠标到指定的窗口。只要传入窗口的句柄即可。
    只有在以下两种情况才能正确捕捉到鼠标:
    1.当鼠标光标正在窗口上。当在窗口上时,捕捉了鼠标看上去并没有其他的用处,因为消息本来就是流向当前窗口的。
    2.当鼠标光标移出窗口时,只要鼠标上有任何一个按键按下时。当鼠标超出了当前捕获鼠标的窗口范围时,鼠标未捕获时,鼠标的消息则流向了其他窗口。然而捕获了之后,消息仍然会流向捕获鼠标的窗口。这个由系统来完成。你所要做的就是,让鼠标的左键右键和滚轮的中键有一个按下。只有一个按钮被按下,系统才认为你当前还需要捕获鼠标。否则,即使之前捕获了鼠标还没有释放,系统依然将消息投给鼠标下面的窗口。这个和通常大家认为的“捕获鼠标之后,始终都是流向捕获窗口”的想法不一样。
    一次只有一个窗口可以捕捉光标。多个窗口可以争夺光标。在win10中测试,多个进程的多个窗口相互竞争捕获鼠标,会让对方的捕获取消。这是一个竞争关系,鼠标被没有被某一个窗口锁定,是可以被抢夺的。
    只有前景窗口才能够捕获鼠标。如果后台窗口想要捕获鼠标,则需要在光标热点落在窗口的可见部分。所谓的后台,就是未激活的窗口,未活动的窗口。当鼠标被捕获,只有按着鼠标不放,才会让捕获鼠标的窗口一直持有。然而,如果松开了鼠标,就解除了捕获状态。也就是可以将消息流向鼠标下面的窗口了。这样,鼠标就恢复自由了,也就可以将后台窗口激活,切换为前台活动的窗口。这也就是,尽管前景窗口捕获了鼠标,用户还是可以使用鼠标单击其他窗口,将其他窗口带入前景。
    捕获鼠标后,一定要记得释放。如果一个程序不释放捕获的鼠标,会导致程序自己不能正常运作。因为一旦鼠标被捕获后,程序的菜单的热键和键盘加速键都无法使用了。
    我们可以通过程序来测试。我们响应鼠标的左击、右击和中间按键的单击消息。只在左键单击时捕获鼠标。然后其他的消息都是空的。我们捕获后不释放鼠标,然后在鼠标移动的时候更新当前的光标的坐标。在客户区内移动光标,可以发现坐标在变化。然后如果不按下鼠标任何一个键,移出客户区后,坐标不再变化了。虽然当前已经处于捕获的状态,但是还是出了窗口不起作用了。而当你使用任何一个鼠标键按下不放,然后移动到窗口外面,鼠标光标的坐标依然在变化。
    为了让显示更新的坐标更加方便,我们就直接在窗口的标题更新了。使用SetWindowText函数即可完成。

    总结一下,捕获鼠标就是让鼠标消息重定向,在按下鼠标按键的同时,移动到了其他窗口上,依然可以保持鼠标的消息流向捕获鼠标的窗口,包括在窗口外释放窗口的鼠标弹起消息也会流向捕获鼠标的窗口。鼠标弹起之后,就不再被捕获了。而只有鼠标在窗口上或者离开窗口时鼠标按键有一个按键是被按下的,这两种情况才能捕获。

    下面是捕获鼠标后,可以在鼠标离开窗口时检测鼠标的客户区坐标,出现了负数坐标。

鼠标离开窗口时检测鼠标的客户区坐标

【鼠标离开窗口时检测鼠标的客户区坐标】

    自己运行代码体验吧!下面是完整的测试代码:
#include "windows.h"
#include <tchar.h>
TCHAR tip[100]=_T("");
TCHAR txt[100]=_T("C++技术网www.cjjjs.com提示:标题栏显示当前光标的坐标值");
// - 项目是Unicode字符集
LRESULT CALLBACK WinProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam)
{
    HDC hdc;
    PAINTSTRUCT ps;
    switch (message)
    {
    case WM_LBUTTONDOWN:
        SetCapture(hwnd);//捕获鼠标
        return 0;
    case WM_LBUTTONUP:
        return 0;
    case WM_RBUTTONDOWN:
        return 0;
    case  WM_RBUTTONUP:
        return 0;
    case WM_MBUTTONDOWN:
        return 0;
    case  WM_MBUTTONUP:
        return 0;
    case WM_MOUSEMOVE:
        hdc = GetDC(hwnd);
        wsprintf(tip,_T("X:%d Y:%d"),(short)LOWORD(lParam),(short)HIWORD(lParam));
        SetWindowText(hwnd,tip);
        ReleaseDC(hwnd,hdc);
        return 0;
    case WM_PAINT:
        hdc = BeginPaint(hwnd,&ps);
        TextOut(hdc,2,10,txt,lstrlen(txt));
        EndPaint(hwnd,&ps);
        return 0;
        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;
        default:
        break;//跳出到默认处理
    }
    return DefWindowProc(hwnd, message, wParam, lParam);
}

int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrev,LPSTR lpCmd,int iShow)
{
    TCHAR ClassName[] = _T("MyClass");
    TCHAR title1[] = _T("C++技术网http://www.cjjjs.com");
    WNDCLASS wndClass;
    wndClass.cbClsExtra=0;
    wndClass.cbWndExtra=0;
    wndClass.hbrBackground= (HBRUSH)GetStockObject(WHITE_BRUSH);
    wndClass.hCursor=LoadCursor(NULL,IDC_ARROW);
    wndClass.hIcon=LoadIcon(NULL,IDI_APPLICATION);
    wndClass.hInstance = hInstance;
    wndClass.lpfnWndProc = WinProc;
    wndClass.lpszClassName = ClassName;
    wndClass.lpszMenuName=NULL;
    wndClass.style=CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS;

    if(!RegisterClass(&wndClass))return 0;
    HWND hwnd = CreateWindow(ClassName,title1,WS_OVERLAPPEDWINDOW,0,0,350,400,NULL,NULL,hInstance,NULL);
    ShowWindow(hwnd,SW_SHOWNORMAL);

    MSG msg;
    while (GetMessage(&msg,NULL,0,0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return msg.wParam;
}