Windows响应键盘鼠标事件

上篇文章中讲了如何创建一个简单的Win32的窗口,这篇文章简单讲一下如何响应鼠标和键盘事件。


1. 接收键盘的输入

当一个按键被按下时,Windows会向获得焦点得窗口所在得线程传递一个WM_KEYDOWNWM_SYSKEYDOWN 消息。当释放这个按键时,Windows会发送一个WM_KEYUPWM_SYSKEYUP 消息。WM_SYSKEYDOWNWM_SYSKEYUP 是用户敲击系统键盘时产生得消息。默认情况下应该把他们交给系统 DefWindowProc 函数来处理。

在这几个消息中, wParam 包含了按键得虚拟键盘码,lParam 包含了另外一些状态信息。

当一个WM_KEYDOWN 消息被 TranslateMessage 函数转化后会有一个 WM_CHAR 消息产生,此消息得 wParam 参数包含了按键得ANSI码。 例如当用户敲击键盘A键,窗口会一次收到下面三个消息:

  • WM_KEYDOWNlParam 中为虚拟按键码A"0x41"
  • WM_KEYCHARlParam 中为ANSI码A"0x61"
  • WM_KEYUPlParam 中为虚拟按键码“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_LBUTTONDOWNWM_LBUTTONUPWM_LBUTTONDBCLCK
中键WM_MBUTTONDOWNWM_MBUTTONUPWM_MBUTTONDBCLCK
右键WM_RBUTTONDOWNWM_RBUTTONUPWM_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_SHIFTShift键按下
  • MK_CONTROLCtrl 键按下

下面是当左键按下时,绘制显示当时鼠标位置的窗口函数代码:

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 函数取出。
不会飞的纸飞机
扫一扫二维码,了解我的更多动态。

下一篇文章:COM编程入门Part Ⅰ- 什么是COM和如何使用COM [译]