最近更新于 2024-05-05 14:18
环境
Windows 11 专业工作站版 22H2
Visual Studio 2022 专业版
Unicode 字符集,C17 标准编译,64 位编译
绘制菜单
添加 Windows 桌面向导,创建桌面应用程序空项目,右侧“资源文件”上右键,新建项
左侧选择“资源”,右边选“资源文件”,文件名字这里我设为“menu.rc”
下面切换到“资源视图”
在创建的资源文件上右键点“添加资源”
选 Menu,然后新建
然后就可以编辑菜单资源,编辑完成后按 Ctrl+S 保存一下
注意不管哪种将菜单资源文件添加到代码里的方式,都需要引用一下创建资源文件时生成的头文件 “resource.h”
添加方式一
可以在资源视图查看,这里创建的菜单被命名为了 IDR_MENU1
于是可以在注册窗口类的时候将菜单添加进去
#include <Windows.h>
#include "resource.h" // 菜单资源文件引入
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_PAINT:
{
PAINTSTRUCT ps = {0};
HDC hdc = BeginPaint(hWnd, &ps);
TextOut(hdc, 10, 10, L"你好,世界!", wcslen(L"你好,世界!"));
EndPaint(hWnd, &ps);
break;
}
case WM_CLOSE:
{
DestroyWindow(hWnd);
break;
}
case WM_DESTROY:
{
PostQuitMessage(0);
break;
}
default:
{
return DefWindowProc(hWnd, msg, wParam, lParam);
}
}
return 0;
}
int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPreHinstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow)
{
PCWSTR window_class_name = L"Desktop";
WNDCLASSEX wc = {0};
wc.cbSize = sizeof(WNDCLASSEX);
wc.hInstance = hInstance;
wc.lpfnWndProc = WndProc;
wc.lpszClassName = window_class_name;
wc.lpszMenuName = MAKEINTRESOURCE(IDR_MENU1); // 将菜单资源名字添加进去,并且要将数据类型转为宽字符指针(宽字符字符串 PWSTR = WCHAR * = wchar_t *)
if (!RegisterClassEx(&wc))
{
MessageBox(NULL, L"注册窗口类失败!", L"错误", MB_OK);
return 1;
}
HWND hWnd = CreateWindowEx(0, window_class_name, L"简单窗口程序", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 260, 120, NULL, NULL, hInstance, NULL);
if (!hWnd)
{
MessageBox(NULL, L"创建窗口失败!", L"错误", MB_OK);
return 1;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
MSG msg = {0};
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}
后面两种添加菜单后界面都一样,我就不贴图了
添加方式二
在创建窗口的时候,通过窗口创建函数参数添加菜单资源
#include <Windows.h>
#include "resource.h" // 菜单资源文件引入
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_PAINT:
{
PAINTSTRUCT ps = {0};
HDC hdc = BeginPaint(hWnd, &ps);
TextOut(hdc, 10, 10, L"你好,世界!", wcslen(L"你好,世界!"));
EndPaint(hWnd, &ps);
break;
}
case WM_CLOSE:
{
DestroyWindow(hWnd);
break;
}
case WM_DESTROY:
{
PostQuitMessage(0);
break;
}
default:
{
return DefWindowProc(hWnd, msg, wParam, lParam);
}
}
return 0;
}
int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPreHinstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow)
{
PCWSTR window_class_name = L"Desktop";
WNDCLASSEX wc = {0};
wc.cbSize = sizeof(WNDCLASSEX);
wc.hInstance = hInstance;
wc.lpfnWndProc = WndProc;
wc.lpszClassName = window_class_name;
if (!RegisterClassEx(&wc))
{
MessageBox(NULL, L"注册窗口类失败!", L"错误", MB_OK);
return 1;
}
HMENU hMenu = LoadMenu(hInstance, MAKEINTRESOURCE(IDR_MENU1)); // 加载菜单资源
HWND hWnd = CreateWindowEx(0, window_class_name, L"简单窗口程序", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 260, 120, NULL, hMenu, hInstance, NULL); // 第十个参数传入菜单
if (!hWnd)
{
MessageBox(NULL, L"创建窗口失败!", L"错误", MB_OK);
return 1;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
MSG msg = {0};
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}
添加方式三
在创建窗口后,显示窗口前,发送 WM_CREATE 消息时设置菜单
#include <Windows.h>
#include "resource.h" // 菜单资源文件引入
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_CREATE:
{
// 通过窗口创建函数传入加载好的菜单资源
CREATESTRUCT *pCreate = (CREATESTRUCT *)lParam;
HMENU hMenu = *(HMENU *)(pCreate->lpCreateParams);
// 设置菜单
SetMenu(hWnd, hMenu);
}
case WM_PAINT:
{
PAINTSTRUCT ps = {0};
HDC hdc = BeginPaint(hWnd, &ps);
TextOut(hdc, 10, 10, L"你好,世界!", wcslen(L"你好,世界!"));
EndPaint(hWnd, &ps);
break;
}
case WM_CLOSE:
{
DestroyWindow(hWnd);
break;
}
case WM_DESTROY:
{
PostQuitMessage(0);
break;
}
default:
{
return DefWindowProc(hWnd, msg, wParam, lParam);
}
}
return 0;
}
int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPreHinstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow)
{
PCWSTR window_class_name = L"Desktop";
WNDCLASSEX wc = {0};
wc.cbSize = sizeof(WNDCLASSEX);
wc.hInstance = hInstance;
wc.lpfnWndProc = WndProc;
wc.lpszClassName = window_class_name;
if (!RegisterClassEx(&wc))
{
MessageBox(NULL, L"注册窗口类失败!", L"错误", MB_OK);
return 1;
}
HMENU hMenu = LoadMenu(hInstance, MAKEINTRESOURCE(IDR_MENU1)); // 加载菜单资源
HWND hWnd = CreateWindowEx(0, window_class_name, L"简单窗口程序", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 260, 120, NULL, NULL, hInstance, &hMenu); // 可以使用最后一个参数传入数据
if (!hWnd)
{
MessageBox(NULL, L"创建窗口失败!", L"错误", MB_OK);
return 1;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
MSG msg = {0};
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}
命令消息处理
前面只是将菜单绘制到窗口里了,但是点击菜单项并没有任何反应,因此还需要为菜单项点击命令设置行为,即菜单命令的消息处理。
#include <Windows.h>
#include "resource.h" // 菜单资源文件引入
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_COMMAND: // 点击菜单项会收到该消息
{
switch (LOWORD(wParam)) // 匹配对应的菜单项 ID,设计菜单界面的时候在菜单项右键属性也可以修改 ID 值,这里是默认的
{
case ID_40001:
{
MessageBox(NULL, L"点击新建", L"消息", MB_OK);
break;
}
case ID_40002:
{
MessageBox(NULL, L"点击退出", L"消息", MB_OK);
break;
}
case ID_40003:
{
MessageBox(NULL, L"点击关于", L"消息", MB_OK);
break;
}
default:
{
break;
}
}
break;
}
case WM_PAINT:
{
PAINTSTRUCT ps = {0};
HDC hdc = BeginPaint(hWnd, &ps);
TextOut(hdc, 10, 10, L"你好,世界!", wcslen(L"你好,世界!"));
EndPaint(hWnd, &ps);
break;
}
case WM_CLOSE:
{
DestroyWindow(hWnd);
break;
}
case WM_DESTROY:
{
PostQuitMessage(0);
break;
}
default:
{
return DefWindowProc(hWnd, msg, wParam, lParam);
}
}
return 0;
}
int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPreHinstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow)
{
PCWSTR window_class_name = L"Desktop";
WNDCLASSEX wc = {0};
wc.cbSize = sizeof(WNDCLASSEX);
wc.hInstance = hInstance;
wc.lpfnWndProc = WndProc;
wc.lpszClassName = window_class_name;
wc.lpszMenuName = MAKEINTRESOURCE(IDR_MENU1);
if (!RegisterClassEx(&wc))
{
MessageBox(NULL, L"注册窗口类失败!", L"错误", MB_OK);
return 1;
}
HWND hWnd = CreateWindowEx(0, window_class_name, L"简单窗口程序", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 260, 120, NULL, NULL, hInstance, NULL);
if (!hWnd)
{
MessageBox(NULL, L"创建窗口失败!", L"错误", MB_OK);
return 1;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
MSG msg = {0};
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}
上下文菜单
这个菜单就是一般说的右键菜单,右键菜单使用的菜单项内容还是前面绘制的菜单资源。
此处在前面的基础上再添加一个顶层菜单项,专用于上下文菜单使用。
#include <Windows.h>
#include "resource.h" // 菜单资源文件引入
HMENU g_popup = {0}; // 用于存储上下文菜单
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_CONTEXTMENU: // 专用于右键菜单的,在右键放开消息后产生
{
// 添加上下文菜单
// 要添加的菜单的句柄
// 菜单弹出位置,下面的是设置的弹出菜单的左上方贴靠指定坐标
// 接着两个参数分别传横纵坐标,这里消息附带的是当前鼠标位置的屏幕坐标
// 保留参数,写 0
// 窗口句柄
// 不常用的参数,传 NULL
TrackPopupMenu(g_popup, TPM_LEFTALIGN | TPM_TOPALIGN, LOWORD(lParam), HIWORD(lParam), 0, hWnd, NULL);
break;
}
case WM_COMMAND: // 点击菜单项会收到该消息
{
switch (LOWORD(wParam)) // 匹配对应的菜单项 ID,设计菜单界面的时候在菜单项右键属性也可以修改 ID 值,这里是默认的
{
case ID_40001:
{
MessageBox(NULL, L"点击新建", L"消息", MB_OK);
break;
}
case ID_40002:
{
MessageBox(NULL, L"点击退出", L"消息", MB_OK);
break;
}
case ID_40003:
{
MessageBox(NULL, L"点击关于", L"消息", MB_OK);
break;
}
case ID_40004:
{
MessageBox(NULL, L"点击菜单项1", L"消息", MB_OK);
break;
}
case ID_40005:
{
MessageBox(NULL, L"点击菜单项2", L"消息", MB_OK);
break;
}
case ID_40006:
{
MessageBox(NULL, L"点击菜单项3", L"消息", MB_OK);
break;
}
default:
{
break;
}
}
break;
}
case WM_PAINT:
{
PAINTSTRUCT ps = {0};
HDC hdc = BeginPaint(hWnd, &ps);
TextOut(hdc, 10, 10, L"你好,世界!", wcslen(L"你好,世界!"));
EndPaint(hWnd, &ps);
break;
}
case WM_CLOSE:
{
DestroyWindow(hWnd);
break;
}
case WM_DESTROY:
{
PostQuitMessage(0);
break;
}
default:
{
return DefWindowProc(hWnd, msg, wParam, lParam);
}
}
return 0;
}
int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPreHinstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow)
{
PCWSTR window_class_name = L"Desktop";
WNDCLASSEX wc = {0};
wc.cbSize = sizeof(WNDCLASSEX);
wc.hInstance = hInstance;
wc.lpfnWndProc = WndProc;
wc.lpszClassName = window_class_name;
if (!RegisterClassEx(&wc))
{
MessageBox(NULL, L"注册窗口类失败!", L"错误", MB_OK);
return 1;
}
HMENU hMenu = LoadMenu(hInstance, MAKEINTRESOURCE(IDR_MENU1)); // 加载菜单资源
g_popup = GetSubMenu(hMenu, 2); // 获取子菜单,设计的菜单在第三个,从 0 开始索引为 2
HWND hWnd = CreateWindowEx(0, window_class_name, L"简单窗口程序", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 300, 200, NULL, hMenu, hInstance, NULL);
if (!hWnd)
{
MessageBox(NULL, L"创建窗口失败!", L"错误", MB_OK);
return 1;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
MSG msg = {0};
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}
其它资源
创建其它资源文件操作和上面类似,创建的时候选好类型或者添加指定类型的资源
图标资源
注册窗口类的时候,WNDCLASSEX 有一个成员 hIcon 就是设置图标资源句柄的。
需要使用 LoadIcon 加载图标资源,第一个参数就是当前实例句柄,第二个参数传图标资源的名字,和上面的菜单资源加载方法类似。
软件标题框的图标这些都是使用的图标资源。
光标资源
有两种方法使用,一种也是在注册窗口的时候设置,将光标资源句柄设给 hCursor。
加载光标资源使用 LoadCursor,参数也类似上面。
光标资源就是光标的图形,一般是显示的箭头,可以自己画成其它形状的,比如忙碌时鼠标显示圈,有些情况显示十字等等。
上面的设置方法只能在注册窗口的时候使用,但是就无法在程序运行中途进行变化,所以还有一种方法,使用 SetCursor,参数为当前正在使用的光标的句柄(后面的消息会附带传入这个参数)。不过注意这个函数要放到 WM_SETCUSOR 消息的处理中(光标移动就会产生这个消息)。
字符串资源
字符串资源文件可以用于专门存储程序中使用到的字符串,每个字符串有专门的索引 ID,程序中通过 LoadString 去取指定 ID 对应的字符串。
它的意义的话,我想到的是对于多语言的支持。字符串资源文件分别制作不同语言的版本,要用哪种语言就将对应的字符串资源文件放进去,在不修改程序的情况下可以做到程序内语言的修改。
加速键资源
这个加速键就是一般说的快捷键,加速键要和具体的操作绑定起来,所以这里沿用前面“上下文菜单”的源码和菜单资源文件,在上面为部分菜单项添加加速键。
这里要用到加速键资源,就在 menu.rc 里继续添加
我给上下文菜单的那三个菜单项添加加速键,注意他们各自的 ID
设置加速键资源,将 ID 对应要设置加速键的菜单项,后面设置按键。设置完成后记得按下 Ctrl+S 保存。
具体实现看下面代码
#include <Windows.h>
#include "resource.h" // 资源文件引入
HMENU g_popup = {0};
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_CONTEXTMENU:
{
TrackPopupMenu(g_popup, TPM_LEFTALIGN | TPM_TOPALIGN, LOWORD(lParam), HIWORD(lParam), 0, hWnd, NULL);
break;
}
case WM_COMMAND: // 点击菜单项会收到该消息 // 加速键按下也是这个消息代码
{
if (HIWORD(wParam)) // 加速键消息 - 这一步其实不是很有必要,现实使用中一般也不会去区分是直接点击的菜单还是按的加速键,这里只是演示这个功能
{
switch (LOWORD(wParam))
{
case ID_40004:
{
MessageBox(NULL, L"加速键触发菜单项1", L"消息", MB_OK);
break;
}
case ID_40005:
{
MessageBox(NULL, L"加速键触发菜单项2", L"消息", MB_OK);
break;
}
case ID_40006:
{
MessageBox(NULL, L"加速键触发菜单项3", L"消息", MB_OK);
break;
}
default:
{
break;
}
}
break;
}
switch (LOWORD(wParam))
{
case ID_40001:
{
MessageBox(NULL, L"点击新建", L"消息", MB_OK);
break;
}
case ID_40002:
{
MessageBox(NULL, L"点击退出", L"消息", MB_OK);
break;
}
case ID_40003:
{
MessageBox(NULL, L"点击关于", L"消息", MB_OK);
break;
}
case ID_40004:
{
MessageBox(NULL, L"点击菜单项1", L"消息", MB_OK);
break;
}
case ID_40005:
{
MessageBox(NULL, L"点击菜单项2", L"消息", MB_OK);
break;
}
case ID_40006:
{
MessageBox(NULL, L"点击菜单项3", L"消息", MB_OK);
break;
}
default:
{
break;
}
}
break;
}
case WM_PAINT:
{
PAINTSTRUCT ps = {0};
HDC hdc = BeginPaint(hWnd, &ps);
TextOut(hdc, 10, 10, L"你好,世界!", wcslen(L"你好,世界!"));
EndPaint(hWnd, &ps);
break;
}
case WM_CLOSE:
{
DestroyWindow(hWnd);
break;
}
case WM_DESTROY:
{
PostQuitMessage(0);
break;
}
default:
{
return DefWindowProc(hWnd, msg, wParam, lParam);
}
}
return 0;
}
int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPreHinstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow)
{
PCWSTR window_class_name = L"Desktop";
WNDCLASSEX wc = {0};
wc.cbSize = sizeof(WNDCLASSEX);
wc.hInstance = hInstance;
wc.lpfnWndProc = WndProc;
wc.lpszClassName = window_class_name;
if (!RegisterClassEx(&wc))
{
MessageBox(NULL, L"注册窗口类失败!", L"错误", MB_OK);
return 1;
}
HMENU hMenu = LoadMenu(hInstance, MAKEINTRESOURCE(IDR_MENU1));
g_popup = GetSubMenu(hMenu, 2);
HWND hWnd = CreateWindowEx(0, window_class_name, L"简单窗口程序", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 300, 200, NULL, hMenu, hInstance, NULL);
if (!hWnd)
{
MessageBox(NULL, L"创建窗口失败!", L"错误", MB_OK);
return 1;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
HACCEL hAccel = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_ACCELERATOR1)); // 加载加速键资源文件
MSG msg = {0};
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateAccelerator(hWnd, hAccel, &msg); // 翻译加速键
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}