每次收到鼠标消息后,通过消息参数 WPARAM 和 LPARAM,我们可以进一步获取到鼠标指针位置和一个组合键状态,参考WinProcs.c中注释。
本例中介绍了以WM_L开头的几个消息,是鼠标左键动作消息,同理WM_R开头的为鼠标右键动作消息;本例中介绍的鼠标消息均为“客户区”鼠标消息,还有WM_NC开头的消息,是系统区(标题栏,框架边框等区域)鼠标消息,大家可以自行学习。
这里我们要注意一点,即消息处理和绘图之间的关系。
对于 Win32 应用程序来说,绘制本身也是一个消息 (WM_PAINT),窗体只有接收到这个消息,才能进行绘图。例如,我们收到一个鼠标键按下消息(WM_LBUTTONDOWN),我们写了一个处理该消息的函数,那能不能直接在该函数中对窗体进行绘图呢?答案是否定的。我们只能在这个函数中想办法记录一些变量,然后等到 WM_PAINT 消息来到时,再根据这些变量的值进行绘图。
比如说老师在讲台上讲课,这时一个学生提问,老师就得针对学生提问这一消息进行处理,并输出处理结果,如果老师需要进一步在黑板上书写,那也是老师根据这次提问消息的结果决定要在黑板上输出,和提问消息本身没有关系。
总之牢记一点,在一般情况下,除了绘图外的任何其他消息,都不应该直接操作绘图设备上下文句柄。
当我们用鼠标在Windows中滑动,点击的时候,硬件驱动会向 Windows 报告鼠标指针的位置和按键点击情况,如果鼠标指针的位置恰好处于我们自行开发的窗体应用程序之上,则我们创建的窗口就会收到若干条消息,如:
- WM_MOUSEMOVE
- WM_LBUTTONDOWN
- WM_LBUTTONUP
- WM_LBUTTONDBLCLK
等,并通过消息参数进一步获取到鼠标的指针位置和状态。
程序清单:
h文件 | c文件 | 说明 |
无 | Main.c | 包含主函数,启动主窗体 |
WinClasses.h | WinClasses.c | 包含注册窗体类的代码 |
WinProcs.h | WinProcs.c | 包含窗体消息处理函数代码 |
WinMainMsgProc.h | WinMainMsgProc.c | 包含消息处理代码 |
1、Main.c
- #include <tchar.h>
- #include <stdio.h>
- #include <windows.h>
- #include "WinClasses.h"
- int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
- {
- TCHAR szError[BUFSIZ] = _T("");
- HWND hWnd = NULL;
- MSG msg;
- ZeroMemory(&msg, sizeof(msg));
- if (RegistMainWinClass(hInstance) == 0)
- {
- _stprintf_s(szError, BUFSIZ,
- _T("出现错误,无法注册窗口,错误代码:%d。"), GetLastError());
- MessageBox(NULL, szError, _T("错误"), MB_OK | MB_ICONERROR);
- }
- else
- {
- hWnd = CreateWindowEx(0, MAIN_CLASSNAME,
- _T("Hello World"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,
- 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL
- );
- if (hWnd == NULL)
- {
- _stprintf_s(szError, BUFSIZ, _T("出现错误,无法创建窗口,错误代码:%d。"), GetLastError());
- MessageBox(NULL, szError, _T("错误"), MB_OK | MB_ICONERROR);
- }
- else
- {
- ShowWindow(hWnd, nCmdShow);
- UpdateWindow(hWnd);
- while (GetMessage(&msg, NULL, 0, 0))
- {
- TranslateMessage(&msg);
- DispatchMessage(&msg);
- }
- }
- }
- return msg.wParam;
- }
2.1 WinClasses.h
- #pragma once
- #include <windows.h>
- #define MAIN_CLASSNAME _T("KeyMessages")
- ATOM WINAPI RegistMainWinClass(HINSTANCE hIns);
2.2 WinClasses.c
- #include <tchar.h>
- #include "WinClasses.h"
- #include "WinProcs.h"
- ATOM WINAPI RegistMainWinClass(HINSTANCE hIns)
- {
- WNDCLASSEX wcex;
- ZeroMemory(&wcex, sizeof(wcex));
- wcex.cbSize = sizeof(wcex);
- // 加上 CS_DBLCLKS 样式,窗体可以接收鼠标双击消息
- wcex.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
- wcex.lpfnWndProc = WndMainProc;
- wcex.hInstance = hIns;
- wcex.hIcon = LoadIcon(hIns, MAKEINTRESOURCE(IDI_APPLICATION));
- wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
- wcex.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
- wcex.lpszClassName = MAIN_CLASSNAME;
- wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_APPLICATION));
- return RegisterClassEx(&wcex);
- }
3.1 WinProcs.h
- #pragma once
- #include <windows.h>
- LRESULT CALLBACK WndMainProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
3.2 WinProcs.c
- #include <tchar.h>
- #include "WinProcs.h"
- #include "WinMainMsgProc.h"
- LRESULT CALLBACK WndMainProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
- {
- HDC hDC = NULL;
- PAINTSTRUCT ps;
- LRESULT lReturn = 0L;
- switch (message) {
- case WM_CREATE:
- RedrawWindow(hWnd, NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW | RDW_ERASE);
- break;
- case WM_PAINT:
- hDC = BeginPaint(hWnd, &ps);
- OnRedrawWindow(hDC);
- EndPaint(hWnd, &ps);
- break;
- case WM_MOUSEMOVE: // 处理鼠标移动消息
- // 可以看到,lParam的低4位(一个short int)为鼠标指针的x坐标,高4位为y坐标
- // wParam为鼠标事件发生时的组合键状态
- OnMouseMove(hWnd, LOWORD(lParam), HIWORD(lParam), (int)wParam);
- break;
- case WM_LBUTTONDOWN: // 处理鼠标左键按下消息
- OnMouseLButtonDown(hWnd, LOWORD(lParam), HIWORD(lParam), (int)wParam);
- break;
- case WM_LBUTTONUP: // 处理鼠标左键抬起消息
- OnMouseLButtonUp(hWnd, LOWORD(lParam), HIWORD(lParam), (int)wParam);
- break;
- case WM_LBUTTONDBLCLK: // 处理鼠标左键双击消息
- OnMouseLButtonDoubleClick(hWnd, LOWORD(lParam), HIWORD(lParam), (int)wParam);
- break;
- case WM_CLOSE:
- if (MessageBox(hWnd, _T("是否要退出应用程序?"), _T("提示"), MB_YESNO | MB_ICONQUESTION) == IDYES)
- DestroyWindow(hWnd);
- break;
- case WM_DESTROY:
- PostQuitMessage(0);
- break;
- default:
- lReturn = DefWindowProc(hWnd, message, wParam, lParam);
- }
- return lReturn;
- }
4.1 WinMainMsgProc.h
- #pragma once
- #include <Windows.h>
- // 处理绘图消息
- void WINAPI OnRedrawWindow(HDC hDC);
- // 处理鼠标指针移动消息
- void WINAPI OnMouseMove(HWND hWnd, int x, int y, int nMark);
- // 处理鼠标左键按下消息
- void WINAPI OnMouseLButtonDown(HWND hWnd, int x, int y, int nMark);
- // 处理鼠标左键抬起消息
- void WINAPI OnMouseLButtonUp(HWND hWnd, int x, int y, int nMark);
- // 处理鼠标左键双击消息
- void WINAPI OnMouseLButtonDoubleClick(HWND hWnd, int x, int y, int nMark);
4.2 WinMainMsgProc.c
- #include <tchar.h>
- #include <stdio.h>
- #include "WinMainMsgProc.h"
- #define LEFT_SIDE 20
- #define TOP_SIDE 10
- // 标识鼠标状态的枚举量
- typedef enum
- {
- Nothing = 0, // 鼠标无状态
- BtnDown = 1, // 鼠标键被按下
- BtnUp = 2, // 鼠标键被释放
- BtnDBL = 3 // 鼠标键被双击
- } BUTON_STATUS;
- // 保存鼠标位置的结构体, 有x, y两个分量域
- static POINT g_pntMouse = {0, 0};
- // 保存指针移动时, 组合键的标识,
- // 组合键有
- //
- // MK_CONTROL(Ctrl键)
- // MK_SHIFT(Shift键),
- // MK_LBUTTON(鼠标左键),
- // MB_RBUTTON(鼠标右键)
- static int g_nMark = 0;
- // 保存在操作鼠标按键时, 鼠标指针的位置
- static POINT g_pntL = {0, 0};
- // 保存操作鼠标按键时, 组合键的标识
- // 组合键有
- //
- // MK_CONTROL(Ctrl键)
- // MK_SHIFT(Shift键)
- static int g_nMarkL = 0;
- // 保存鼠标操作状态
- static BUTON_STATUS g_bsL = Nothing;
- /**
- * 判断字符串是否为空。
- * 参数:lpszString指向要判断的字符串指针
- * 返回:TRUE, 参数所指向的字符串为空;FALSE, 参数所指向的字符串不为空
- *
- * __inline的作用, 在很多系统中, __inline可以作为声明“内联”函数使用
- */
- __inline BOOL IsStringEmpty(CONST LPCTSTR lpszString)
- {
- return lpszString[0] == _T(‘\0‘);
- }
- /**
- * 绘制窗体函数
- * 参数:hDC, 绘图设备上下文句柄
- */
- void WINAPI OnRedrawWindow(HDC hDC)
- {
- int nTop = TOP_SIDE;
- int nLen = 0;
- TCHAR szBuffer[BUFSIZ] = _T("");
- HFONT hOldFont = NULL;
- COLORREF cOldFont = 0;
- // 设置字体为系统默认字体
- hOldFont = SelectObject(hDC, GetStockObject(DEFAULT_GUI_FONT));
- // 设置字体颜色
- cOldFont = SetTextColor(hDC, RGB(220, 10, 10));
- // 输出鼠标指针移动位置
- nLen = _stprintf_s(szBuffer, BUFSIZ, _T("鼠标 X 坐标 %d;Y 坐标 %d。"), g_pntMouse.x, g_pntMouse.y);
- // 输出文字
- TextOut(
- hDC, // 要输出文字的绘图设备上下文句柄
- LEFT_SIDE, // 文字输出位置的x坐标
- nTop, // 文字输出位置的y坐标
- szBuffer, // 指向要输出文字的指针
- nLen // 要输出文字的长度
- );
- // 输出鼠标指针移动时, 组合键状态
- if (g_nMark != 0)
- {
- // 输出坐标下移 20 像素
- nTop += 20;
- _tcscpy_s(szBuffer, BUFSIZ, _T(""));
- // 注意, 得到的组合键状态是多个整数值‘或‘算的结果, 必须使用‘与‘运算来判断
- if (g_nMark & MK_CONTROL) // Ctrl键是否按下
- _tcscat_s(szBuffer, BUFSIZ, _T("CTRL 键被按下"));
- if (g_nMark & MK_LBUTTON) // 鼠标左键是否按下
- {
- if (!IsStringEmpty(szBuffer))
- _tcscat_s(szBuffer, BUFSIZ, _T(";"));
- _tcscat_s(szBuffer, BUFSIZ, _T("鼠标 左 键被按下"));
- }
- if (g_nMark & MK_RBUTTON) // 鼠标右键是否按下
- {
- if (!IsStringEmpty(szBuffer))
- _tcscat_s(szBuffer, BUFSIZ, _T(";"));
- _tcscat_s(szBuffer, BUFSIZ, _T("鼠标 右 键被按下"));
- }
- if (g_nMark & MK_SHIFT) // Shift键是否按下
- {
- if (!IsStringEmpty(szBuffer))
- _tcscat_s(szBuffer, BUFSIZ, _T(";"));
- _tcscat_s(szBuffer, BUFSIZ, _T("SHIFT 键被按下"));
- }
- _tcscat_s(szBuffer, BUFSIZ, _T("。"));
- // 设置字体颜色后输出字体
- SetTextColor(hDC, RGB(0, 200, 0));
- TextOut(hDC, LEFT_SIDE, nTop, szBuffer, _tcslen(szBuffer));
- }
- // g_bsL的值不为Nothing, 说明鼠标按键发生了动作, 进一步处理
- if (g_bsL > Nothing)
- {
- // 根据鼠标按键状态生成字符串, 注意三元运算符嵌套用法
- _stprintf_s(szBuffer, BUFSIZ,
- g_bsL == BtnDBL ? _T("LDBL (%d, %d)") :
- (g_bsL == BtnDown ? _T("LD (%d, %d)") : _T("LU (%d, %d)")),
- g_pntL.x, g_pntL.y
- );
- // 根据鼠标组合键状态生成字符串
- if (g_nMarkL & MK_CONTROL)
- _tcscat_s(szBuffer, BUFSIZ, _T(" CTRL"));
- if (g_nMarkL & MK_SHIFT)
- _tcscat_s(szBuffer, BUFSIZ, _T(" SHIFT"));
- // 设置字体颜色, 再次使用了嵌套的三元运算符
- SetTextColor(hDC, g_bsL == BtnDBL ? RGB(0, 200, 200) : (g_bsL == BtnDown ? RGB(0, 0, 200) : RGB(200, 200, 0)));
- // 输出字体
- TextOut(hDC, g_pntL.x + 20, g_pntL.y, szBuffer, _tcslen(szBuffer));
- // 重置状态
- g_bsL = Nothing;
- }
- SelectObject(hDC, hOldFont);
- SetTextColor(hDC, cOldFont);
- }
- /**
- * 处理鼠标指针移动消息
- * 参数:hWnd, 窗口句柄
- * x, 鼠标指针位置x坐标
- * y, 鼠标指针位置y坐标
- * nMark, 组合键状态
- */
- void WINAPI OnMouseMove(HWND hWnd, int x, int y, int nMark)
- {
- g_pntMouse.x = x;
- g_pntMouse.y = y;
- g_nMark = nMark;
- RedrawWindow(hWnd, NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW | RDW_ERASE );
- }
- /**
- * 处理鼠标左键按下消息
- * 参数:hWnd, 窗口句柄
- * x, 鼠标指针位置x坐标
- * y, 鼠标指针位置y坐标
- * nMark, 组合键状态
- */
- void WINAPI OnMouseLButtonDown(HWND hWnd, int x, int y, int nMark)
- {
- g_pntL.x = x;
- g_pntL.y = y;
- g_nMarkL = nMark;
- g_bsL = BtnDown;
- RedrawWindow(hWnd, NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW | RDW_ERASE );
- }
- /**
- * 处理鼠标左键释放消息
- * 参数:hWnd, 窗口句柄
- * x, 鼠标指针位置x坐标
- * y, 鼠标指针位置y坐标
- * nMark, 组合键状态
- */
- void WINAPI OnMouseLButtonUp(HWND hWnd, int x, int y, int nMark)
- {
- g_pntL.x = x;
- g_pntL.y = y;
- g_nMarkL = nMark;
- g_bsL = BtnUp;
- RedrawWindow(hWnd, NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW | RDW_ERASE );
- }
- /**
- * 处理鼠标左键双击消息
- * 参数:hWnd, 窗口句柄
- * x, 鼠标指针位置x坐标
- * y, 鼠标指针位置y坐标
- * nMark, 组合键状态
- */
- void WINAPI OnMouseLButtonDoubleClick(HWND hWnd, int x, int y, int nMark)
- {
- g_pntL.x = x;
- g_pntL.y = y;
- g_nMarkL = nMark;
- g_bsL = BtnDBL;
- RedrawWindow(hWnd, NULL, NULL, RDW_INVALIDATE | RDW_UPDA