第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);
}
原文地址:https://www.cnblogs.com/5iedu/p/4658843.html