zoukankan      html  css  js  c++  java
  • 第7章 鼠标_7.1-7.4 鼠标基础知识和鼠标消息

    7.1 鼠标的基础知识

    功能

    GetSysMetrics的

    参数

    返回值

    判断是否安装鼠标

    SM_MOUSEPRESENT

    WINNT以上:TRUE己安装。0未安装

    Windows98:总是TRUE。

    鼠标按钮个数

    SM_CMOUSEBUTTONS

    WINNT以上:0为未安装鼠标

    Windows98:有安装鼠标返回按钮个数,没安装鼠标返回2。

    鼠标按钮是否被切换

    SM_SWAPBUTTON

    设定鼠标其他参数(如双击):用SystemParametrsInfo函数获取或设定

    (2)鼠标指针:wndClass.hCursor = LoadCursor(NULL,IDC_ARROW)。

    7.2 客户区的鼠标消息

    (1)客户区鼠标消息(10个)

    事件

    消息

    鼠标经过

    WM_MOUSEMOVE

    左键

    WM_LBUTTONDOWN、WM_LBUTTONUP、WM_LBUTTONDBLCLK(双击)

    中键

    WM_MBUTTONDOWN、WM_MBUTTONUP、WM_MBUTTONDBLCLK(第二次按下)

    右键

    WM_RBUTTONDOWN、WM_RBUTTONUP、WM_RBUTTONDBLCLK

      说明:①双击事件的处理只有窗口类定义接收时,才起作用:即

    差异

    没包含双击事件

    包含双击事件

    wndClass.style

    CS_HREDRAW| CS_VREDRAW

    CS_HREDRAW | CS_VREDRAW|CS_DBLCLKS

    消息顺序

    WM_LBUTTONDOWN

    WM_LBUTTONUP

    WM_LBUTTONDOWN

    WM_LBUTTONUP

    WM_LBUTTONDOWN

    WM_LBUTTONUP

    WM_LBUTTONDBLCLK

    WM_LBUTTONUP

            ②MBUTTON只对三键鼠标起作用。单键鼠标只处理LBUTTON消息。

    ③WM_MOUSEMOVE:消息的个数取决于鼠标硬件和窗口处理的速度。Windows不会为

    经过的每个像素位置都产生一个WM_MOUSEMOVE消息。

            ④WM_LBUTTONDOWN和WM_LBUTTONUP并不一定会成对出现。可能只收到其中的一种

    消息。

    ⑤键盘消息发送到当前具有输入焦点的窗口。鼠标消息发送给被单击的窗或鼠标经

    过的窗口,即使该窗口处于非活动或不带输入焦点。两种例外:A、“捕获鼠标”

    时,鼠标离开窗口仍继续接收鼠标消息;B、模式对话框处于活动状态时,其他

    程序不能接收鼠标消息。

     (2)鼠标消息的参数

    wParam(按钮、Shift、Ctrl键状态)

    lParam(鼠标位置)

    MK_LBUTTON(0x0001):按下左键

    MK_RBUTTON(0x0002):按下右键

    MK_SHIFT  (0x0004):按下Shift

    MK_CONTROL(0x0008):按下Ctrl

    MK_MBUTTON(0x0010):按下中键

    ★或运算结果

    x = LOWORD(lParam);

    y = HIWORD(lParam);

           eg.当WM_LBUTTONDOWN时,是否同时按下Shift键:wParam & MK_SHIFT。

    7.2.1 简单的鼠标处理示例

    【Connect程序】

    (1)操作方法:

    ①第一种——在客户区按下左键,略微移动,再松开左键

    ②第二种——在客户区按下左键,快速移动鼠标。

    (2)己知的问题:在客户区外释放左键,Connnect不会连接这些点,因为没收到WM_LBUTTONUP消息。

    (3)该程序较耗时,绘制时,鼠标变沙漏形,处理WM_PAINT完后回原来的状态。用SetCursor来切换鼠标。ShowCursor隐藏或显示鼠标指针。

     【效果图】
     
     
    /*------------------------------------------------------------
    CONNECT.C -- Connect-the-Dots Mouse Demo program
    (c) Charles Petzold, 1998
    ------------------------------------------------------------*/
    #define MAXPOINTS 1000
    #include <windows.h>
    LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
    int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
        PSTR szCmdLine, int iCmdShow)
    {
        static TCHAR szAppName[] = TEXT("Connect");
        HWND         hwnd;
        MSG          msg;
        WNDCLASS     wndclass;
        wndclass.style = CS_HREDRAW | CS_VREDRAW;
        wndclass.lpfnWndProc = WndProc;
        wndclass.cbClsExtra = 0;
        wndclass.cbWndExtra = 0;
        wndclass.hInstance = hInstance;
        wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
        wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
        wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
        wndclass.lpszMenuName = NULL;
        wndclass.lpszClassName = szAppName;
        if (!RegisterClass(&wndclass))
        {
            MessageBox(NULL, TEXT("This program requires Windows NT!"),
                szAppName, MB_ICONERROR);
            return 0;
        }
    
        hwnd = CreateWindow(szAppName,                  // window class name
            TEXT("Connect-the-Points Mouse Demo"), // window caption
            WS_OVERLAPPEDWINDOW,        // window style
            CW_USEDEFAULT,              // initial x position
            CW_USEDEFAULT,              // initial y position
            CW_USEDEFAULT,              // initial x size
            CW_USEDEFAULT,              // initial y size
            NULL,                       // parent window handle
            NULL,                       // window menu handle
            hInstance,                  // program instance handle
            NULL);                     // creation parameters
    
        ShowWindow(hwnd, iCmdShow);
        UpdateWindow(hwnd);
    
        while (GetMessage(&msg, NULL, 0, 0))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        return msg.wParam;
    }
    LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
        HDC         hdc;
        PAINTSTRUCT ps;
        static POINT pt[MAXPOINTS];
        static int iCount = 0;
        switch (message)
        {
        case WM_LBUTTONDOWN:
            iCount = 0;
            InvalidateRect(hwnd, NULL, TRUE); //每次按下左键时,先消屏
            return 0;
        case WM_MOUSEMOVE:  //验证移动时,并不是每个像素都产生WM_MOUSEMOVE消息。
            if (wParam & MK_LBUTTON && iCount<MAXPOINTS) //只有按下左键+鼠标移动时,才画点
            {
                pt[iCount].x = LOWORD(lParam);
                pt[iCount++].y = HIWORD(lParam);
    
                //画点
                hdc = GetDC(hwnd);
                SetPixel(hdc, LOWORD(lParam), HIWORD(lParam), 0);
                ReleaseDC(hwnd, hdc);
            }
            return 0;
        case WM_LBUTTONUP:  //鼠标放开时,开始连线
            InvalidateRect(hwnd, NULL, FALSE); //FALSE,不擦除背景,可以保留WM_MOUSEMOVE里画下的点。
            return 0;
    
        case WM_PAINT:
            hdc = BeginPaint(hwnd, &ps);
            SetCursor(LoadCursor(NULL, IDC_WAIT));
            ShowCursor(TRUE); //鼠标指针显示为等待状态
            for (int i = 0; i < iCount; i++)   //每一点而其他各点的连线
            for (int j = i + 1; j < iCount; j++)
            {
                MoveToEx(hdc, pt[i].x, pt[i].y, NULL);
                LineTo(hdc, pt[j].x, pt[j].y);
            }
            ShowCursor(FALSE);
            SetCursor(LoadCursor(NULL, IDC_ARROW));
            EndPaint(hwnd, &ps);
            return 0;
    
        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;
        }
        return DefWindowProc(hwnd, message, wParam, lParam);
    }

    7.2.2 处理Shift键

    处理过程依赖Shift和Ctrl键的逻辑处理

    单键鼠标模拟双键鼠标

    if (wParam & MK_SHIFT)  //按下Shift

    {

        if (wParam & MK_CONTROL)

        {

            [按下Shift + Ctrl键];

         }

        else{

            [只按下Shift键];

        }

    }else{                  //未按Shift

        if (wParam & MK_CONTROL)

        {

            [只按下Ctrl键];

        }else{

            [Shift和Ctrl都没被按下];

        }

    }

    case WM_LBUTTONDOWN:

    //未按Shift时,直接处理左键

        if (!(wParam & MK_SHIFT))

        {

            [这里处理左键];

            return 0;

         }   //注意,这里没有return。

        //用户按下了鼠标左键+Shift,执行以下代码,模拟右键。

    case WM_RBUTTONDOWN: 

        [这里处理右键];

        return 0;

    ★注意:双键鼠标也是可以正常处理的。单键鼠标可以通过按住鼠标左键+Shift,来模拟鼠标右键的功能。

    注意:GetKeyState可以通过VK_LBUTTON、VK_RBUTTON、VK_SHIFT、VK_CONTROL等获取鼠标当前状态。但鼠标或键盘未被按下的键不能使用GetKeyState。只有被按下时才会报告其按下状态。(while(GetKeyState(VK_LBUTTON)>=0))是错误的代码。

    7.3 非客户区的鼠标消息

    (1)非客户区鼠标消息(11个)

    事件

    消息

    鼠标经过

    WM_NCMOUSEMOVE

    击中测试

    WM_NCHITTEST

    左键

    WM_NCLBUTTONDOWN、WM_NCLBUTTONUP、WM_NCLBUTTONDBLCLK(双击)

    中键

    WM_NCMBUTTONDOWN、WM_NCMBUTTONUP、WM_NCMBUTTONDBLCLK(第二次按下)

    右键

    WM_NCRBUTTONDOWN、WM_NCRBUTTONUP、WM_NCRBUTTONDBLCLK

     (2)非客户区鼠标消息的参数

    wParam(窗口的哪个部位)

    (20多个位置,见MSDN)

    lParam(鼠标屏幕坐标)

    HTCLINET  客户区

    HTNOWHERE 不在任何窗口

    HTRANSPARENT 被另一个窗口覆盖

    HTERROR  使函数DefWindowProc产生警示声

    ……

    Pt.x =LOWORD(lParam);

    Pt.y =HIWORD(lParam);

    //屏幕坐标与客户区坐标转换

    ScreenToClient(hwnd,&pt);

    ClientToScreen(hwnd,&pt);

    7.3.1 击中测试消息:WM_NCHITTEST

    (1)消息优先级高于其他鼠标消息

    (2)wParam参数如上表所示。

    (3)DefWindowProc在处理WM_NCHITTEST后,如果返回HTCLIENT,则会把屏幕坐标转成客户区坐标,并产生一个客户区鼠标消息。

    (4)使鼠标按钮操作失效——阻止系统向窗口发送所有客户区和非客户区鼠标消息:

           case WM_NCHITTEST:return (LRESULT)HTNOWHERE;

    7.3.2 消息引发的消息

    (1)Windows利用WM_NCHITTEST来产生其他所有的鼠标消息。

    (2)举例:双击系统图标来关闭窗口,由WM_NCHITTEST引发的消息

    顺序

    当前消息

    wParam

    返回

    1、双击系统图标,产生WM_NCHITTEST,由DefWindowProc处理后,产生下一条消息。

    WM_NCHITTEST

    Not used

    HTSYSMENU

    2、默认处理后,再产生下条消息

    WM_NCLBUTTONDBLCLK

    HTSYSMENU

    3、默认处理后,产生下条消息

    WM_SYSCOMMAND

    SC_CLOSE

    4、DefWindowProc调用DestroyWindow处理这条消息,并产生下条消息

    WM_CLOSE

    5、默认处理或用户处理,并销毁。

    WM_DESTROY

    7.4 程序中的击中测试

    7.4.1 一个简单的程序

    (1)矩形宽度为cxBlock,高度为cyBlock

    (2)WM_LBUTTONDOWN判断鼠标落在哪个矩形内

    (3)如果客户区宽度和长度不能被5整除,会在左边和底部出现一个空白区。

    (4)点击鼠标时产生一个无效区(矩形),并发送WM_PAINT重新绘制该无效区。
    【效果图】

     

    /*------------------------------------------------------------
    CHECKER1.C -- Mouse Hit-Test demo Program No.2
    (c) Charles Petzold, 1998
    ------------------------------------------------------------*/
    #include <windows.h>
    #define  DIVISIONS 5
    LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
    int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
        PSTR szCmdLine, int iCmdShow)
    {
        static TCHAR szAppName[] = TEXT("Checker1");
        HWND         hwnd;
        MSG          msg;
        WNDCLASS     wndclass;
        wndclass.style = CS_HREDRAW | CS_VREDRAW;
        wndclass.lpfnWndProc = WndProc;
        wndclass.cbClsExtra = 0;
        wndclass.cbWndExtra = 0;
        wndclass.hInstance = hInstance;
        wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
        wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
        wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
        wndclass.lpszMenuName = NULL;
        wndclass.lpszClassName = szAppName;
        if (!RegisterClass(&wndclass))
        {
            MessageBox(NULL, TEXT("This program requires Windows NT!"),
                szAppName, MB_ICONERROR);
            return 0;
        }
    
        hwnd = CreateWindow(szAppName,                  // window class name
            TEXT("Checker1 Mouse Hit-Test Demo"), // window caption
            WS_OVERLAPPEDWINDOW,        // window style
            CW_USEDEFAULT,              // initial x position
            CW_USEDEFAULT,              // initial y position
            CW_USEDEFAULT,              // initial x size
            CW_USEDEFAULT,              // initial y size
            NULL,                       // parent window handle
            NULL,                       // window menu handle
            hInstance,                  // program instance handle
            NULL);                     // creation parameters
    
        ShowWindow(hwnd, iCmdShow);
        UpdateWindow(hwnd);
    
        while (GetMessage(&msg, NULL, 0, 0))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        return msg.wParam;
    }
    LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
        HDC         hdc;
        PAINTSTRUCT ps;
        RECT        rect;
        static cxBlock, cyBlock;
        static BOOL fState[DIVISIONS][DIVISIONS];
        int x, y;
        switch (message)
        {
        case WM_CREATE:
    
            return 0;
        case WM_SIZE:
            cxBlock = LOWORD(lParam) / DIVISIONS;
            cyBlock = HIWORD(lParam) / DIVISIONS;
            return 0;
        case WM_LBUTTONDOWN:
            //单击的矩形索引
            x = LOWORD(lParam) / cxBlock;
            y = HIWORD(lParam) / cyBlock;
            if (x < DIVISIONS && y < DIVISIONS)
            {
                fState[x][y] ^= 1; //即fState[x][y] =!fState[x][y];
                rect.top = y*cyBlock;
                rect.bottom = (y + 1)*cyBlock;
                rect.left = x*cxBlock;
                rect.right = (x + 1)*cxBlock;
                InvalidateRect(hwnd, &rect, TRUE); //因用透明绘制,所以要清除背景
            }
            else{
                //当宽度和长度不是5个倍数时,会在左或底侧出现个空白区,让其发出错误的声音。
                MessageBeep(0);
            }
            return 0;
        case WM_PAINT:
            hdc = BeginPaint(hwnd, &ps);
            RECT tmpRect;
            SelectObject(hdc, GetStockObject(NULL_BRUSH));//透明绘制
            for (int x = 0; x < DIVISIONS; x++)
            for (int y = 0; y < DIVISIONS; y++)
            {
                rect.top = y*cyBlock;
                rect.bottom = (y + 1)*cyBlock;
                rect.left = x*cxBlock;
                rect.right = (x + 1)*cxBlock;
                IntersectRect(&tmpRect, &rect, &ps.rcPaint);
                if (IsRectEmpty(&tmpRect)) continue;//判断当前矩形区是否在无效区内
    
                Rectangle(hdc, rect.left, rect.top, rect.right, rect.bottom);
    
                if (fState[x][y])
                {
                    MoveToEx(hdc, x*cxBlock, y*cyBlock, NULL);
                    LineTo(hdc, (x + 1)*cxBlock, (y + 1)*cyBlock);
                    MoveToEx(hdc, x*cxBlock, (y + 1)*cyBlock, NULL);
                    LineTo(hdc, (x + 1)*cxBlock, y*cyBlock);
                }
            }
    
            EndPaint(hwnd, &ps);
            return 0;
    
        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;
        }
        return DefWindowProc(hwnd, message, wParam, lParam);
    }

    7.4.2 使用键盘模仿鼠标操作

    (1)显示或隐藏鼠标

       ShowCursor(TRUE);//增加鼠标显示计数,默认0,只有在>=0时,才显示。

       ShowCursor(FALSE);//减少鼠标计数,只有在<0时才隐藏。

    (2)获取和设置鼠标位置

       ①Get、Set鼠标位置

          GetCursorPos(&pt);//屏幕坐标(因为没有hwnd参数),可与客户区坐标转换

          SetCursorPos(&pt);

       ②GetCursorPos与鼠标消息的参数lParam的差别。

          A、GetCursorPos坐标并转成客户区坐标,返回的鼠标当前位置

          B、lParam包含的坐标是指产生消息的那一刻的鼠标位置。

     (3)在CHECKER中增加键盘接口

        ①用空格与Enter键模拟鼠标按钮;方向键每按一次切换到另一个矩形。

        ②Home键回到左上角的矩形,End键鼠标落到右下角矩形。

     【Checker2程序】

    /*------------------------------------------------------------
    CHECKER2.C -- Mouse Hit-Test demo Program No.2
    (c) Charles Petzold, 1998
    ------------------------------------------------------------*/
    #include <windows.h>
    #define  DIVISIONS 5
    LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
    int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
        PSTR szCmdLine, int iCmdShow)
    {
        static TCHAR szAppName[] = TEXT("Checker2");
        HWND         hwnd;
        MSG          msg;
        WNDCLASS     wndclass;
        wndclass.style = CS_HREDRAW | CS_VREDRAW;
        wndclass.lpfnWndProc = WndProc;
        wndclass.cbClsExtra = 0;
        wndclass.cbWndExtra = 0;
        wndclass.hInstance = hInstance;
        wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
        wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
        wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
        wndclass.lpszMenuName = NULL;
        wndclass.lpszClassName = szAppName;
        if (!RegisterClass(&wndclass))
        {
            MessageBox(NULL, TEXT("This program requires Windows NT!"),
                szAppName, MB_ICONERROR);
            return 0;
        }
    
        hwnd = CreateWindow(szAppName,                  // window class name
            TEXT("Checker2 Mouse Hit-Test Demo"), // window caption
            WS_OVERLAPPEDWINDOW,        // window style
            CW_USEDEFAULT,              // initial x position
            CW_USEDEFAULT,              // initial y position
            CW_USEDEFAULT,              // initial x size
            CW_USEDEFAULT,              // initial y size
            NULL,                       // parent window handle
            NULL,                       // window menu handle
            hInstance,                  // program instance handle
            NULL);                     // creation parameters
    
        ShowWindow(hwnd, iCmdShow);
        UpdateWindow(hwnd);
    
        while (GetMessage(&msg, NULL, 0, 0))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        return msg.wParam;
    }
    LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
        HDC         hdc;
        PAINTSTRUCT ps;
        RECT        rect;
        static cxBlock, cyBlock;
        static BOOL fState[DIVISIONS][DIVISIONS];
        int x, y;
        POINT pt;
        switch (message)
        {
        case WM_CREATE:
    
            return 0;
        case WM_SIZE:
            cxBlock = LOWORD(lParam) / DIVISIONS;
            cyBlock = HIWORD(lParam) / DIVISIONS;
            return 0;
        case WM_SETFOCUS:
            ShowCursor(TRUE); //显示鼠标,现在都安装了鼠标,可以省略
            return 0;
        case WM_KILLFOCUS:
            ShowCursor(FALSE);
            return 0;
        case WM_KEYDOWN:
    
            GetCursorPos(&pt); //因wParam为虚拟键,lParam为击键的6个字段,没鼠标坐标。
            ScreenToClient(hwnd, &pt);
    
            //当前选中的矩形
            x = max(0, min(DIVISIONS - 1, pt.x / cxBlock));    //限定在[0-4]之间
            y = max(0, min(DIVISIONS - 1, pt.y / cyBlock));
            switch (wParam)
            {
            case VK_UP:
                y--;
                break;
            case VK_DOWN:
                y++;
                break;
            case VK_LEFT:
                x--;
                break;
            case VK_RIGHT:
                x++;
                break;
            case VK_HOME:
                x = y = 0;
                break;
            case VK_END:
                x = y = DIVISIONS - 1;
            case VK_SPACE:
            case VK_RETURN:
                SendMessage(hwnd, WM_LBUTTONDOWN, MK_LBUTTON, MAKELONG(x*cxBlock, y*cyBlock));
                return 0;//也可以break,执行下面代码,将鼠标设置到当前矩形中央位置。
            }
            x = (x + DIVISIONS) % DIVISIONS; //x原区间为[0,4],移到[5,9]区间,防止x--后出现负数区间。
            y = (y + DIVISIONS) % DIVISIONS;
    
            //设置鼠标位置到矩形中央位置
            pt.x = x*cxBlock + cxBlock / 2;
            pt.y = y*cyBlock + cyBlock / 2;
            ClientToScreen(hwnd, &pt); //客户区坐标转成屏幕坐标
            SetCursorPos(pt.x, pt.y);
            return 0;
        case WM_LBUTTONDOWN:
            //单击的矩形索引
            x = LOWORD(lParam) / cxBlock;
            y = HIWORD(lParam) / cyBlock;
            if (x < DIVISIONS && y < DIVISIONS)
            {
                fState[x][y] ^= 1; //即fState[x][y] =!fState[x][y];
                rect.top = y*cyBlock;
                rect.bottom = (y + 1)*cyBlock;
                rect.left = x*cxBlock;
                rect.right = (x + 1)*cxBlock;
                InvalidateRect(hwnd, &rect, TRUE); //因用透明绘制,所以要清除背景
            }
            else{
                //当宽度和长度不是5个倍数时,会在左或底侧出现个空白区,让其发出错误的声音。
                MessageBeep(0);
            }
            return 0;
        case WM_PAINT:
            hdc = BeginPaint(hwnd, &ps);
            RECT tmpRect;
            SelectObject(hdc, GetStockObject(NULL_BRUSH));//透明绘制
            for (int x = 0; x < DIVISIONS; x++)
            for (int y = 0; y < DIVISIONS; y++)
            {
                rect.top = y*cyBlock;
                rect.bottom = (y + 1)*cyBlock;
                rect.left = x*cxBlock;
                rect.right = (x + 1)*cxBlock;
                IntersectRect(&tmpRect, &rect, &ps.rcPaint);
                if (IsRectEmpty(&tmpRect)) continue;//判断当前矩形区是否在无效区内
    
                Rectangle(hdc, rect.left, rect.top, rect.right, rect.bottom);
    
                if (fState[x][y])
                {
                    MoveToEx(hdc, x*cxBlock, y*cyBlock, NULL);
                    LineTo(hdc, (x + 1)*cxBlock, (y + 1)*cyBlock);
                    MoveToEx(hdc, x*cxBlock, (y + 1)*cyBlock, NULL);
                    LineTo(hdc, (x + 1)*cxBlock, y*cyBlock);
                }
            }
    
            EndPaint(hwnd, &ps);
            return 0;
    
        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;
        }
        return DefWindowProc(hwnd, message, wParam, lParam);
    }
    7.4.3 在击中测试中使用子窗口

    (1)包含25个子窗口,两个窗口过程:WndProc和ChildWndProc

    (2)子窗口类的cbWndExtra设置4个字节,用来保存该窗口绘画状态,打“X”用1表示。

    (3)WndProc中创建子窗口时须调用GetWindowsLong,从注册的窗口结构中提取hInstance.

    (4)hwndChild数组为每个子窗口保存了窗口句柄,MoveWindows函数中要用到。

    (5)CreateWindows主窗口与子窗口参数的比较

    参数

    主窗口

    子窗口

    窗口类

    “Checker3”

    “Checker3_Child”

    窗口标题

    “Checker3…”

    NULL

    窗口风格

    WS_OVERLAPPEDWINDOW

    WS_CHILDWINDOW|WS_VISIBLE

    水平位置

    CW_USEDEFAULT

    0

    垂直位置

    CW_USEDEFAULT

    0

    宽度

    CW_USEDEFAULT

    0

    高度

    CW_USEDEFAULT

    0

    父窗口句柄

    NULL

    hwnd

    菜单句柄/子ID

    NULL

    (HMENU)(y<<8 | x)(唯一标识子窗口的数值)

    实例句柄

    hInstance

    (HINSTANCE)GetWindowLong(hwnd,GWL_INSTANCE)

    额外参数

    NULL

    NULL

    【Checker3程序】

    /*------------------------------------------------------------
    CHECKER3.C -- Mouse Hit-Test demo Program No.3
    (c) Charles Petzold, 1998
    ------------------------------------------------------------*/
    #include <windows.h>
    #define  DIVISIONS 5
    LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); //主窗口过程
    LRESULT CALLBACK ChildWndProc(HWND, UINT, WPARAM, LPARAM);  //子窗口的窗口过程
    TCHAR szChildClass[] = TEXT("Checker_Child"); //须定义为全局变量,因为WinMain和WndProc中都要用到。
    int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
        PSTR szCmdLine, int iCmdShow)
    {
        static TCHAR szAppName[] = TEXT("Checker3");
        HWND         hwnd;
        MSG          msg;
        WNDCLASS     wndclass;
        //主窗体
        wndclass.style = CS_HREDRAW | CS_VREDRAW;
        wndclass.lpfnWndProc = WndProc;
        wndclass.cbClsExtra = 0;
        wndclass.cbWndExtra = 0;
        wndclass.hInstance = hInstance;
        wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
        wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
        wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
        wndclass.lpszMenuName = NULL;
        wndclass.lpszClassName = szAppName;
        if (!RegisterClass(&wndclass))
        {
            MessageBox(NULL, TEXT("This program requires Windows NT!"),
                szAppName, MB_ICONERROR);
            return 0;
        }
    
        //子窗口类
        wndclass.lpfnWndProc = ChildWndProc;
        wndclass.cbWndExtra = sizeof(long); //也可以为设置,直接保存在窗体的GWL_USERDATA中
        wndclass.hIcon = NULL;
        wndclass.lpszClassName = szChildClass;
        RegisterClass(&wndclass); //己经是NT了,不需要再检查了。
        hwnd = CreateWindow(szAppName,                  // window class name
            TEXT("Checker3 Mouse Hit-Test demo"), // window caption
            WS_OVERLAPPEDWINDOW,        // window style
            CW_USEDEFAULT,              // initial x position
            CW_USEDEFAULT,              // initial y position
            CW_USEDEFAULT,              // initial x size
            CW_USEDEFAULT,              // initial y size
            NULL,                       // parent window handle
            NULL,                       // window menu handle
            hInstance,                  // program instance handle
            NULL);                     // creation parameters
    
        ShowWindow(hwnd, iCmdShow);
        UpdateWindow(hwnd);
    
        while (GetMessage(&msg, NULL, 0, 0))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        return msg.wParam;
    }
    LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
        static HWND hwndChild[DIVISIONS][DIVISIONS];
        static int cxBlock, cyBlock;
        switch (message)
        {
        case WM_CREATE:
            for (int x = 0; x < DIVISIONS; x++)
            for (int y = 0; y < DIVISIONS; y++)
            {
                hwndChild[x][y] = CreateWindow(szChildClass, NULL,
                    WS_CHILD | WS_VISIBLE, //没WS_VISIBLE时,创建好窗口,要自己ShowWindow
                    0, 0, 10, 10,
                    hwnd,
                    (HMENU)(y << 8 | x),
                    (HINSTANCE)GetWindowLong(hwnd, GWL_HINSTANCE), //从主窗体获得hInstance 
                    NULL);
            }
            return 0;
        case WM_SIZE:
            cxBlock = LOWORD(lParam) / DIVISIONS;
            cyBlock = HIWORD(lParam) / DIVISIONS;
            for (int x = 0; x < DIVISIONS; x++)
            for (int y = 0; y < DIVISIONS; y++)
            {
                MoveWindow(hwndChild[x][y], x*cxBlock, y*cyBlock, cxBlock, cyBlock, TRUE);
            }
            return 0;
        case WM_LBUTTONDOWN:
            MessageBeep(0);
            return 0;
        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;
        }
        return DefWindowProc(hwnd, message, wParam, lParam);
    }
    LRESULT CALLBACK ChildWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
        HDC         hdc;
        PAINTSTRUCT ps;
        RECT        rect;
        switch (message)
        {
        case WM_CREATE:
            SetWindowLong(hwnd, 0, 0);//开/关标记,保存cbWndExtra空间
            //SetWindowLong(hwnd, GWL_USERDATA, 0);//保存在USERDATA中
            return 0;
        case WM_LBUTTONDOWN:
            SetWindowLong(hwnd, 0, 1 ^ GetWindowLong(hwnd, 0));
            //SetWindowLong(hwnd, GWL_USERDATA, 1 ^ GetWindowLong(hwnd, GWL_USERDATA));
            InvalidateRect(hwnd, NULL, FALSE); //因为矩形默认是填充白色的,会覆盖之前绘画,所以用False
            return 0;
        case WM_PAINT:
            hdc = BeginPaint(hwnd, &ps);
            GetClientRect(hwnd, &rect);
            Rectangle(hdc, rect.left, rect.top, rect.right, rect.bottom);
            if (GetWindowLong(hwnd, 0)) //if (GetWindowLong(hwnd, GWL_USERDATA))
            {
                MoveToEx(hdc, rect.left, rect.top, NULL);
                LineTo(hdc, rect.right, rect.bottom);
                MoveToEx(hdc, rect.left, rect.bottom, NULL);
                LineTo(hdc, rect.right, rect.top);
            }
            EndPaint(hwnd, &ps);
            return 0;
        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;
        }
        return DefWindowProc(hwnd, message, wParam, lParam);
    }
    7.4.4 子窗口和键盘

    (1)单击子窗口时,得到输入焦点的是父窗口,而不是子窗口。(记这点很重要)。鼠标但键盘消息只给被具有焦点的窗口接收。(父子窗口如何共享键盘消息呢?如,当按空格或回车键时,接收消息的是子窗口,要反转选中状态。按方向键时,接收消息的是父窗口,要将焦点移到另一个子窗口上?方法:父窗口直接将焦点转移给子窗口,让子窗口接管键盘消息,子窗体先处理回车键和空格键等键盘消息。但遇到方向键时,将消息通过SendMessage返给父窗口,注意这个过程不必将焦点还给父窗口。整个过程,即父窗口将键盘消息让给子窗口去接管,子窗口处理自己兴趣的消息,把不处理的消息重新分发给父窗口)

    (2)父窗口中操作子窗口

      操作

    函数

    获取子窗口ID的方法

    idChild = GetWindowLong(hwndChild,GWL_ID);

    idChild = GetDlgCtrlID(hwndChild)

    获取子窗口的句柄

    hwndChild = GetDlgItem(hwndParent,idChild)

    设置子窗口为焦点窗口

    SetFocus(GetDlgItem(hwnd,idFocus))

    (3)子窗口得到焦点时:WM_SETFOCUS中画虚线框表示
    【Checker4】 

    /*------------------------------------------------------------
    CHECKER4.C -- Mouse Hit-Test demo Program No.3
    (c) Charles Petzold, 1998
    ------------------------------------------------------------*/
    #include <windows.h>
    #define  DIVISIONS 5
    LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); //主窗口过程
    LRESULT CALLBACK ChildWndProc(HWND, UINT, WPARAM, LPARAM);  //子窗口的窗口过程
    TCHAR szChildClass[] = TEXT("Checker_Child"); //须定义为全局变量,因为WinMain和WndProc中都要用到。
    int idFocus; //当前选中的矩形(用子窗口ID来标识)
    int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
        PSTR szCmdLine, int iCmdShow)
    {
        static TCHAR szAppName[] = TEXT("Checker4");
        HWND         hwnd;
        MSG          msg;
        WNDCLASS     wndclass;
        //主窗体
        wndclass.style = CS_HREDRAW | CS_VREDRAW;
        wndclass.lpfnWndProc = WndProc;
        wndclass.cbClsExtra = 0;
        wndclass.cbWndExtra = 0;
        wndclass.hInstance = hInstance;
        wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
        wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
        wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
        wndclass.lpszMenuName = NULL;
        wndclass.lpszClassName = szAppName;
        if (!RegisterClass(&wndclass))
        {
            MessageBox(NULL, TEXT("This program requires Windows NT!"),
                szAppName, MB_ICONERROR);
            return 0;
        }
    
        //子窗口类
        wndclass.lpfnWndProc = ChildWndProc;
        wndclass.cbWndExtra = sizeof(long);
        wndclass.hIcon = NULL;
        wndclass.lpszClassName = szChildClass;
        RegisterClass(&wndclass); //己经是NT了,不需要再检查了。
        hwnd = CreateWindow(szAppName,                  // window class name
            TEXT("Checker4 Mouse Hit-Test demo"), // window caption
            WS_OVERLAPPEDWINDOW,        // window style
            CW_USEDEFAULT,              // initial x position
            CW_USEDEFAULT,              // initial y position
            CW_USEDEFAULT,              // initial x size
            CW_USEDEFAULT,              // initial y size
            NULL,                       // parent window handle
            NULL,                       // window menu handle
            hInstance,                  // program instance handle
            NULL);                     // creation parameters
    
        ShowWindow(hwnd, iCmdShow);
        UpdateWindow(hwnd);
    
        while (GetMessage(&msg, NULL, 0, 0))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        return msg.wParam;
    }
    LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
        static HWND hwndChild[DIVISIONS][DIVISIONS];
        static int cxBlock, cyBlock;
        int x, y;
        switch (message)
        {
        case WM_CREATE:
            for (int x = 0; x < DIVISIONS; x++)
            for (int y = 0; y < DIVISIONS; y++)
            {
                hwndChild[x][y] = CreateWindow(szChildClass, NULL,
                    WS_CHILD | WS_VISIBLE, //没WS_VISIBLE时,创建好窗口,要自己ShowWindow
                    0, 0, 10, 10,
                    hwnd,
                    (HMENU)(y << 8 | x),
                    (HINSTANCE)GetWindowLong(hwnd, GWL_HINSTANCE), //从主窗体获得hInstance 
                    NULL);
            }
            return 0;
        case WM_SIZE:
            cxBlock = LOWORD(lParam) / DIVISIONS;
            cyBlock = HIWORD(lParam) / DIVISIONS;
            for (int x = 0; x < DIVISIONS; x++)
            for (int y = 0; y < DIVISIONS; y++)
            {
                MoveWindow(hwndChild[x][y], x*cxBlock, y*cyBlock, cxBlock, cyBlock, TRUE);
            }
            return 0;
        case WM_SETFOCUS:
            SetFocus(GetDlgItem(hwnd, idFocus)); //将子窗体设置为窗口窗体,以便接管键盘消息。
            return 0;
        case WM_KEYDOWN:
            x = idFocus & 0xFF;
            y = idFocus >> 8;
            switch (wParam)
            {
            case VK_UP:      y--; break;
            case VK_DOWN:    y++; break;
            case VK_LEFT:    x--; break;
            case VK_RIGHT:   x++; break;
            case VK_HOME:    x = y = 0; break;
            case VK_END:     x = y = DIVISIONS - 1; break;
            default: return 0; //其它按键不处理,直接返回
            }
    
            x = (x + DIVISIONS) % DIVISIONS;
            y = (y + DIVISIONS) % DIVISIONS;
            idFocus = y << 8 | x;
            SetFocus(GetDlgItem(hwnd, idFocus));
            return 0;
        case WM_LBUTTONDOWN:
            MessageBeep(0);
            return 0;
        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;
        }
        return DefWindowProc(hwnd, message, wParam, lParam);
    }
    LRESULT CALLBACK ChildWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
        HDC         hdc;
        PAINTSTRUCT ps;
        RECT        rect;
        switch (message)
        {
        case WM_CREATE:
            SetWindowLong(hwnd, 0, 0);//开/关标记,保存cbWndExtra空间
            return 0;
        case WM_KEYDOWN:
            //将绝大部分的键盘消息还给父窗口处理
            if (wParam != VK_RETURN && wParam != VK_SPACE)
            {
                SendMessage(GetParent(hwnd), message, wParam, lParam);
                return 0;
            }
            //继续执行下去,处理回车和空格键
        case WM_LBUTTONDOWN:
            SetWindowLong(hwnd, 0, 1 ^ GetWindowLong(hwnd, 0));
            SetFocus(hwnd);
            InvalidateRect(hwnd, NULL, FALSE); //因为矩形默认是填充白色的,会覆盖之前绘画,所以用False
            return 0;
        case WM_SETFOCUS:
            idFocus = GetWindowLong(hwnd, GWL_ID);
            //继续执行下去,用虚线框表示焦点窗口
    
        case WM_KILLFOCUS:
            InvalidateRect(hwnd, NULL, TRUE);
            return 0;
        case WM_PAINT:
            hdc = BeginPaint(hwnd, &ps);
            GetClientRect(hwnd, &rect);
            Rectangle(hdc, rect.left, rect.top, rect.right, rect.bottom);
            if (GetWindowLong(hwnd, 0))
            {
                MoveToEx(hdc, rect.left, rect.top, NULL);
                LineTo(hdc, rect.right, rect.bottom);
                MoveToEx(hdc, rect.left, rect.bottom, NULL);
                LineTo(hdc, rect.right, rect.top);
            }
    
            if (hwnd == GetFocus())
            {
                rect.left += rect.right / 20;
                rect.right -= rect.left;
                rect.top += rect.bottom / 20;
                rect.bottom -= rect.top;
                SelectObject(hdc, GetStockObject(NULL_BRUSH));
                SelectObject(hdc, CreatePen(PS_DOT, 0, 0));
                Rectangle(hdc, rect.left, rect.top, rect.right, rect.bottom);
                DeleteObject(SelectObject(hdc, GetStockObject(BLACK_PEN)));
            }
            EndPaint(hwnd, &ps);
            return 0;
        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;
        }
        return DefWindowProc(hwnd, message, wParam, lParam);
    }
  • 相关阅读:
    C#自定义控件在添加引用后不显示在工具箱的解决方法
    DevExpress中SearchLookUpEdit用法总结
    div(固定宽度和不固定宽度)居中显示的方法总结
    php面向对象学习笔记
    在php中使用Memcache
    memcache的安装及管理
    使用Quartz.NET cron-like表达式
    SQL CHECK 约束&Case when 的使用方法
    IP地址地区信息查询API接口调用方法
    ASP使用webRequest实现跨域请求
  • 原文地址:https://www.cnblogs.com/5iedu/p/4658843.html
Copyright © 2011-2022 走看看