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

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

创建菜单

菜单的配置与前述各类资源一样,编写在.RC资源脚本中,同时,和字符串资源相似,菜单各项的标识符也必须是整数ID,而不能是名称字符串;
一个简单的菜单配置如下:

// 在头文件 RESOURCES.H 中
#define MENU_FILE_ID_OPEN 1000
#define MENU_FILE_ID_CLOSE 1001
#define MENU_FILE_ID_SAVE 1002
#define MENU_FILE_ID_EXIT 1003

#define MENU_HELP_ABOUT 2000
// 在资源脚本 xxx.RC 中
MainMenu MENU DISCARDABLE
{
    POPUP "File"
    {
        MENUITEM "Open", MENU_FILE_ID_OPEN
        MENUITEM "Close", MENU_FILE_ID_CLOSE
        MENUITEM "Save", MENU_FILE_ID_SAVE
        MENUITEM "Exit", MENU_FILE_ID_EXIT
    }

    POPUP "Help"
    {
        MENUITEM "About", MENU_HELP_ABOUT
    }
}

上述代码可以生成如下图所示的菜单栏:
带有两个下拉菜单的菜单条.png
作者在书中写道“关键字DISCARDABLE是过时了但还是必须的”,但是实测在VisualStudio2019环境下此声明是不需要的
与其他资源的定义相同,花括号可以被替换为BEGIN...END对;
如果需要设置热键或配合Alt触发的快捷键,只需要在POPUP菜单或MENUITEM字符串中将&放到想要标识的快捷键字符前面,如下:

// 使 x 成为热键
MENUITEM "E&xit", MENU_FILE_ID_EXIT

// 设置可以通过 Alt+F 触发的快捷键
POPUP "&File"

装载菜单

装载菜单的方式有如下三种:

1. 初始化Windows类时指定菜单名

前文中所定义的菜单资源,就可以通过如下方式加载:

winclass.lpszMenuName = "MainMenu";

与其他资源同理,如果"MainMenu"被定义为了整数ID时,则需要使用MAKEINTRESOURCE宏进行处理,后文不再赘述;
在初始化Windows类时指定菜单名,如果使用当前类创建了多个窗口,则这些窗口具有相同的菜单;

2. 创建窗口时指定菜单句柄

可以使用LoadMenu函数动态加载资源中的菜单,在非Unicode环境下函数原型如下:

HMENU WINAPI LoadMenuA(
    HINSTANCE hInstance,    // 应用程序句柄
    LPCSTR lpMenuName       // 菜单名称
);

函数加载返回的菜单句柄,可以传入CreateWindowEx函数中,在创建窗口时指定菜单;

3. 动态设置菜单

可以使用SetMenu函数动态设置已存在窗口的菜单,函数原型如下:

BOOL WINAPI SetMenu(
    HWND hWnd,  // 窗口句柄
    HMENU hMenu // 菜单资源句柄
);

使用此方式设置的菜单优先级是最高的,可以覆盖替换在初始化时指定的菜单;

响应菜单事件消息

当菜单项被点击时,消息依然是被事件回调函数进行处理,如下图所示:
窗口菜单选择消息流.png
参数对应关系如下:

  • msg:一定为WM_COMMAND
  • lparam:发出消息的窗口句柄;
  • wparam:选中的菜单项ID;

使用LOWORD宏从wparam中提取低位的WORD再进行处理更安全;
剩下的操作便是用switch语句来处理wparam的低位值就可以了,参考前文中定义的菜单项,一个可能的事件回调函数如下所示:

LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
	PAINTSTRUCT	ps;
	HDC			hdc;

	switch (msg)
	{
	case WM_CREATE:
		// 在此处执行初始化操作
		return 0;
	case WM_COMMAND:
		switch (LOWORD(wparam))
		{
		case MENU_FILE_ID_OPEN:
			// 在此处填写逻辑代码
			break;
		case MENU_FILE_ID_CLOSE:
			// 在此处填写逻辑代码
			break;
		case MENU_FILE_ID_SAVE:
			// 在此处填写逻辑代码
			break;
		case MENU_FILE_ID_EXIT:
			// 在此处填写逻辑代码
			break;
		case MENU_HELP_ABOUT:
			// 在此处填写逻辑代码
			break;
		default: break;
		}
		return 0;
	case WM_PAINT:
		hdc = BeginPaint(hwnd, &ps);
		// 在此处执行绘图操作
		EndPaint(hwnd, &ps);
		return 0;
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	default:
		// 调用默认的消息回调处理我们不关心的事件
		return DefWindowProc(hwnd, msg, wparam, lparam);
	}
}

上述代码仅展示了菜单项被选中时的常见处理操作,还有其他用于操作一级菜单和菜单项本身的消息,更多内容见Win32SDK - 菜单通知