上篇文章中讲了如何创建一个简单的Win32的窗口,这篇文章简单讲一下如何响应鼠标和键盘事件。
1. 接收键盘的输入
当一个按键被按下时,Windows会向获得焦点得窗口所在得线程传递一个WM_KEYDOWN 或 WM_SYSKEYDOWN 消息。当释放这个按键时,Windows会发送一个WM_KEYUP 或 WM_SYSKEYUP 消息。WM_SYSKEYDOWN 和 WM_SYSKEYUP 是用户敲击系统键盘时产生得消息。默认情况下应该把他们交给系统 DefWindowProc 函数来处理。
在这几个消息中, wParam 包含了按键得虚拟键盘码,lParam 包含了另外一些状态信息。
当一个WM_KEYDOWN 消息被 TranslateMessage 函数转化后会有一个 WM_CHAR 消息产生,此消息得 wParam 参数包含了按键得ANSI码。例如当用户敲击键盘“A”键,窗口会一次收到下面三个消息:
- WM_KEYDOWN: lParam 中为虚拟按键码A"0x41"
- WM_KEYCHAR: lParam 中为ANSI码A"0x61"
- WM_KEYUP: lParam 中为虚拟按键码“0x41”
下面是窗口函数处理消息 WM_KEYCHAR 将按下得键绘制到窗口上得代码:
LRESULT CALLBACK MainWindProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static std::wstring str;
switch (message)
{
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = ::BeginPaint(hwnd, &ps);
::TextOut(hdc, 0, 0, str.c_str(), str.size());
::EndPaint(hwnd, &ps);
return 0;
}
case WM_DESTROY:
::PostQuitMessage(0);
return 0;
case WM_CHAR:
str += char(wParam);
::InvalidateRect(hwnd, nullptr, 0);
return 0;
}
return ::DefWindowProc(hwnd, message, wParam, lParam);
}
函数 InvalidateRect 会使客户区无效,迫使Windows再发送 WM_PAINT 消息,该函数原型为:
BOOL InvalidateRect (HWND hwnd, CONST RECT *lpRect, BOOL bErase)
- hwnd: 为窗口句柄
- lpRect: 指定无效区域得范围,如果为nullptr则为整个客户区
- bErase: 更新区域时,背景是否擦处。
BeginPaint 函数得第二个参数为一个指向 PAINTSTRUCT 结构得指针,此结构包含重画客户区时所需要得信息。
typedef struct tagPAINTSTRUCT {
HDC hdc; // 设备环境句柄
BOOL fErase; // 指定背景是否删除
RECT rcPaint; // 要求重画的区域
BOOL fRestore;
BOOL fIncUpdate;
BYTE rgbReserved[32];
} PAINTSTRUCT, *PPAINTSTRUCT, *NPPAINTSTRUCT, *LPPAINTSTRUCT;
2. 接收鼠标输入
当鼠标被按下或者移动等操作,windows会发送消息给窗口,下面是不同的操作发送不同的消息表格:
按下 | 弹起 | 双击 | |
---|---|---|---|
左键 | WM_LBUTTONDOWN | WM_LBUTTONUP | WM_LBUTTONDBCLCK |
中键 | WM_MBUTTONDOWN | WM_MBUTTONUP | WM_MBUTTONDBCLCK |
右键 | WM_RBUTTONDOWN | WM_RBUTTONUP | WM_RBUTTONDBCLCK |
发送消息时, lParam 参数包含了鼠标的坐标
xPos = LOWORD(lParam);
yPos = HIWORD(lParam);
客户区坐标和屏幕坐标的相互转化可以使用下面的函数:
- BOOL ClientToScreen (HWND hWnd, LPPOINT lpPoint)
- BOOL ScreenToClient (HWND hWnd, LPPOINT lpPoint)
wParam 参数包含鼠标的按钮的状态,这些都是以MK_ 前缀,意为 mouse key
- MK_LBUTTON : 左键按下
- MK_MBUTTON : 中键按下
- MK_RBUTTON: 右键按下
- MK_SHIFT : 《Shift》键按下
- MK_CONTROL : 《Ctrl》 键按下
下面是当左键按下时,绘制显示当时鼠标位置的窗口函数代码:
LRESULT CALLBACK MainWindProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static std::wstring str;
switch (message)
{
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = ::BeginPaint(hwnd, &ps);
::TextOut(hdc, 0, 0, str.c_str(), str.size());
::EndPaint(hwnd, &ps);
return 0;
}
case WM_DESTROY:
::PostQuitMessage(0);
return 0;
case WM_LBUTTONDOWN:
WCHAR message[60];
memset(message, 0, sizeof(message));
wsprintf(message, L"X=%d, Y=%d", LOWORD(lParam), HIWORD(lParam));
str = message;
::InvalidateRect(hwnd, nullptr, FALSE);
return 0;
}
return ::DefWindowProc(hwnd, message, wParam, lParam);
}
如果想设置文字的背景色和文本颜色可以使用下面的函数:
SetTextColor(hdc, RGB(255, 0, 0)); // 文本颜色设置为红色
SetBkColor(hdc, RGB(0, 0, 255)); // 背景颜色设置为蓝色
3. SendMessage和PostMessage
- 由 SendMessage 函数发送的消息不进入消息队列等待 GetMessage 函数而出,而是直接传给窗口函数 MainWndProc ,并等待 MainWndProc 函数返回时再返回,其返回值也就是 MainWndProc 函数的返回值。
- 由 PostMessage 函数发送消息后会马上返回,不等待消息的运行结果。他不把消息发送给消息的窗口函数 MainWndProc ,而是把消息投放给窗口所在线程的消息队列中等待GetMessage 函数取出。