《Windows游戏编程大师技巧》学习笔记(七)

《Windows游戏编程大师技巧》学习笔记(七)

窗口事件

与窗口有关的消息简介:
窗口事件消息.png

WM_ACTIVATE消息

bActive = LOWORD(wParam);           // 激活标志
bMinimized = (BOOL)HIWORD(wParam);  // 最小化标志
hwndPrevious = (HWND)lParam;        // 窗口句柄

上述代码中bActive可能的取值如下:
激活标志.png
bMinimized表示窗口是否已最小化;
hwndPrevious指将被激活或被取消激活的句柄,具体含义由bActive的值决定:如果bActive的值为WA_ACTIVEWA_CLICKACTIVEhwndPrevious是被取消激活的窗口的句柄,该句柄可能为NULL

case WM_ACTIVATE:
    if (LOWORD(wparam) != WA_INACTIVE)
    {
        // 窗口被激活时的逻辑
    }
    else
    {
        // 窗口取消激活时的逻辑
    }
    return 0;

WM_CLOSE消息

WM_CLOSE会在WM_DESTROYWM_QUIT之前被发送,也就是说此消息说明用户正在试图关闭窗口;
一个常见的窗口关闭处理逻辑如下,当用户尝试关闭窗口时弹出消息框询问是否关闭窗口:
WM_CLSOE逻辑流程图.png

case WM_CLOSE:
{
    int result = MessageBox(hwnd,
    "Are you sure you want to close this application?",
    "Title Here", MB_YESNO | MB_ICONQUESTION);
    
    if (result == IDYES)
    {
        // 如果用户确认关闭窗口,则将消息传递出去,调用默认的处理方式
        return DefWindowProc(hwnd, msg, wparam, lparam);
    }
    else
        // 否则忽略该消息
        return 0;
}

WM_SIZE消息

fwSizeType = wParam;        // 窗口尺寸改变标志
nWidth = LOWORD(lParam);    // 窗口宽度
nHeight = HIWORD(lParam);   // 窗口高度

fwSizeType表示刚发生的尺寸变动是那种改变,可能的值如下:
WM_SIZE消息标志.png

WM_MOVE消息

xPos = (int)LOWORD(lParam);     // 移动后的窗口横坐标
yPos = (int) HIWORD(lParam);    // 移动后的窗口纵坐标

注意,WM_MOVEWM_SIZE消息类似,只有当窗口移动结束(或尺寸改变结束)时才会被发送,如果想要实时跟踪窗口的移动(或尺寸改变),那么就需要用对应的-ING事件;

键盘事件

当用户按下键盘某个键时,会产生两个数据:扫描码和ASCII码
Windows下处理键盘消息有三种途径:

  1. 通过WM_CHAR消息,传递ASCII码,ASCII码是人为形成的数据,是用户通过按键(组合)具体输入的字符;
  2. 通过WM_KEYDOWNWM_KEYUP消息,传递扫描码,扫描码是唯一指定给键盘上每一个键的编码,与是否按下Shift键等无关;
  3. 通过调用GetAsyncKeyState函数,该函数可以查询某键的最后状态;

WM_CHAR消息

int ascii_code = wparam;    // 所按键的ASCII码
int key_state = lparam;     // 按键编码的状态

key_state描述可能被按下的特殊按键,其按位编码如下表:
键盘状态矢量的按位编码.png
如下代码测试Alt键是否被按下:

#define ALT_STATE_BIT 0x20000000

if (key_state & ALT_STATE_BIT)
{
    // 处理Alt按下逻辑
}

WM_KEY*消息

WM_KEYDOWNWM_KEYUP这类消息,传递的数据是未经处理的,为该键的虚拟扫描码;
虚拟扫描码由Windows翻译键盘产生的标准扫描码翻译得到,屏蔽了不同厂商不同型号键盘标准扫描码不同的场景,方便开发者编程;

int virtual_code = (int)wparam;
int key_state = (int)lparam;

switch (virtual_code)
{
    case VK_RIGHT:  break;
    case VK_LEFT:   break;
    case VK_UP:     break;
    case VK_DOWN:   break;
    // ...
}

virtual_code为所按键的虚拟键代码,key_stateWM_CHAR消息中的一样,描述可能被按下的特殊控制键;
虚拟键编码如下表所示:
虚拟键编码.png

GetAsyncKeyState函数

使用GetKeyboardStateGetKeyStateGetAsyncKeyState函数都可以查询键盘状态,此处重点讨论GetAsyncKeyState,函数原型如下:

SHORT WINAPI GetAsyncKeyState(int vKey);

只需要传递给函数想要检测的虚拟键代码,返回值的最高位便表示该键是否被按下,如下代码检测回车键是否被按下:

if (GetAsyncKeyState(VK_RETURN) & 0x8000)
{
    // 处理回车键按下逻辑
}

鼠标事件

鼠标坐标总览如下图所示:
鼠标坐标总览.png

WM_MOUSEMOVE消息

int mouse_x = (int)LOWORD(lParam);
int mouse_y = (int)HIWORD(lParam);

int buttons = (int)wParam;

mouse_xmouse_y意义显而易见,表示鼠标在窗口坐标系下的横纵坐标;
buttons记录按键编码,可以通过与运算检测哪些键被按下,按键编码如下:
WM_MOUSEMOVE按键编码.png
如下代码可以检测在鼠标移动时鼠标左键是否被按下:

case WM_MOUSEMOVE:
{
    int buttons = (int)wParam;
    if (buttons & MK_LBUTTON)
    {
        // 处理鼠标移动时左键按下逻辑
    }
}
return 0;

WM_*BUTTON*消息

由于WM_MOUSEMOVE消息只会在鼠标移动时才会发送,所以当我们想要独立处理鼠标按键事件时,就需要将逻辑放置到如下的按键消息下:
鼠标按键消息.png
当按键消息触发时,鼠标当前的坐标位置信息也被存储到了lParam中,如下的代码可以在鼠标左键被双击时获取当前的鼠标坐标:

case WM_LBUTTONDBLCLK:
{
    int mouse_x = (int)LOWORD(lParam);
    int mouse_y = (int)HIWORD(lParam);

    // 处理鼠标左键双击逻辑
}
return 0;

用户自定义事件

有两种方式主动向窗口发送消息:

  • SendMessage函数:优先度较高,窗口接收该消息后立即调用WinProc函数,函数返回值为对应WinProc函数的返回值,在非Unicode环境下函数原型如下:
    	LRESULT WINAPI SendMessageA(
    		HWND hWnd,      // 窗口句柄
    		UINT Msg,       // 消息类型
    		WPARAM wParam,  // 第一个消息参数
    		LPARAM lParam   // 第二个消息参数
    	);
    
  • PostMessage函数:优先度较低,只是将消息发往窗口的消息队列,而后直接返回,如果返回非零值则表示执行成功,在非Unicode环境下函数原型如下:
    	BOOL WINAPI PostMessageA(
    		HWND hWnd,      // 窗口句柄
    		UINT Msg,       // 消息类型
    		WPARAM wParam,  // 第一个消息参数
    		LPARAM lParam   // 第二个消息参数
    	);
    

如下代码可以让程序在Esc键被按下时退出:

if (GetAsyncKeyState(VK_ESCAPE) & 0x8000)
    PostMessage(hwnd, WM_CLOSE, 0, 0);

需要注意的是,由于SendMessage函数会直接调用WinProc函数对传入的事件进行处理,所以可能跳过当前已存在于事件队列中的事件,导致执行顺序被打乱,相对来说使用PostMessage函数更为安全;
发送自定义消息时可以使用WM_USER作为消息类型,可以根据需要任意设置wparamlparam的值,如下代码可以为自定义的内存管理系统创建消息:

#define ALLOC_MEM   0
#define DEALLOC_MEM 1

SendMessage(hwnd, WM_USER, ALLOC_MEM, 1000);

case WM_USER:
{
    switch(wparam)
    {
        case ALLOC_MEM; break;
        case DEALLOC_MEM; break;
        // ...
    }
}
return 0;