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

窗口事件

与窗口有关的消息简介:

窗口事件消息

WM_ACTIVATE消息

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

上述代码中bActive可能的取值如下:

激活标志

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

1
2
3
4
5
6
7
8
9
10
case WM_ACTIVATE:
if (LOWORD(wparam) != WA_INACTIVE)
{
// 窗口被激活时的逻辑
}
else
{
// 窗口取消激活时的逻辑
}
return 0;

WM_CLOSE消息

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

WM_CLSOE逻辑流程图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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消息

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

fwSizeType表示刚发生的尺寸变动是那种改变,可能的值如下:

WM_SIZE消息标志

WM_MOVE消息

1
2
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消息

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

key_state描述可能被按下的特殊按键,其按位编码如下表:

键盘状态矢量的按位编码

1
2
3
4
5
6
#define ALT_STATE_BIT 0x20000000

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

WM_KEY*消息

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

1
2
3
4
5
6
7
8
9
10
11
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消息中的一样,描述可能被按下的特殊控制键;
虚拟键编码如下表所示:

虚拟键编码

GetAsyncKeyState函数

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

1
SHORT WINAPI GetAsyncKeyState(int vKey);

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

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

鼠标事件

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

鼠标坐标总览

WM_MOUSEMOVE消息

1
2
3
4
int mouse_x = (int)LOWORD(lParam);
int mouse_y = (int)HIWORD(lParam);

int buttons = (int)wParam;

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

WM_MOUSEMOVE按键编码

1
2
3
4
5
6
7
8
9
case WM_MOUSEMOVE:
{
int buttons = (int)wParam;
if (buttons & MK_LBUTTON)
{
// 处理鼠标移动时左键按下逻辑
}
}
return 0;

WM_*BUTTON*消息

由于WM_MOUSEMOVE消息只会在鼠标移动时才会发送,所以当我们想要独立处理鼠标按键事件时,就需要将逻辑放置到如下的按键消息下:

鼠标按键消息

当按键消息触发时,鼠标当前的坐标位置信息也被存储到了lParam中,如下的代码可以在鼠标左键被双击时获取当前的鼠标坐标:

1
2
3
4
5
6
7
8
case WM_LBUTTONDBLCLK:
{
int mouse_x = (int)LOWORD(lParam);
int mouse_y = (int)HIWORD(lParam);

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

用户自定义事件

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

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#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;
作者

Voidmatrix

发布于

2022-02-02

更新于

2022-03-14

许可协议

评论