资源是 Windows 应用程序图形用户界面(GUI)的重要组成部分,简单来说是每一个应用程序或多或少会用到的几类数据,这些数据在编译后,被包含进 EXE 文件。
包括光标、位图、图标、加速键、菜单、字符串和对话框等。
我们无法直接定位到资源,并使用函数操作他们,和窗口一样,一般情况我们都是得到资源的。
#include <Windows.h>
#include <CommCtrl.h>
#include "resource.h"
// 一旦添加了资源文件,就会自动的生成 resource.h 头文件和 rc文件
// 其中头文件定义了资源的 ID ,rc 文件提供了资源的布局以及和 ID 的
// 对应关系,在 VS 中,无法同时打开这两个文件。不要尝试手动的修改头
// 文件,容易导致资源错乱或程序崩溃。
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
// 定义一个静态局部变量,通过函数 GetModuleHandle 获取实例句柄
static HINSTANCE hInstance = GetModuleHandle(NULL);
switch (uMsg)
{
// 在调用 CreateWindow 时会发送这个消息,通常用于初始化控件
case WM_CREATE:
{
CreateWindow(WC_BUTTON, L"切换菜单", WS_CHILD | WS_VISIBLE,
10, 10, 200, 50, hWnd, (HMENU)0x1001, hInstance, NULL);
CreateWindow(WC_BUTTON, L"修改光标", WS_CHILD | WS_VISIBLE,
10, 70, 200, 50, hWnd, (HMENU)0x1002, hInstance, NULL);
CreateWindow(WC_BUTTON, L"修改图标", WS_CHILD | WS_VISIBLE,
10, 130, 200, 50, hWnd, (HMENU)0x1003, hInstance, NULL);
break;
}
// 响应按钮使用的是 WM_COMMAND 消息
case WM_COMMAND:
{
if (LOWORD(wParam) == 0x1001)
{
static bool b = true;
HMENU hMenu = LoadMenu(hInstance, MAKEINTRESOURCE(IDR_MENU1 + b));
SetMenu(hWnd, hMenu);
b = !b;
}
if (LOWORD(wParam) == 0x1002)
{
// SetClassLong 和 SetWindowLong 区别主要在于,SetClassLong 设置的是窗口类
// 会影响使用当前窗口类的所有对象,SetWindowLong 只会影响到被设置的窗口
SetClassLong(hWnd, GCL_HCURSOR, (LONG)LoadCursor(NULL, IDC_HELP));
}
if (LOWORD(wParam) == 0x1003)
{
SetClassLong(hWnd, GCL_HICON, (LONG)LoadIcon(NULL, IDI_APPLICATION));
}
// 菜单也是在 WM_COMMAND 响应的,LOWROD(wParam)保存的是 ID
if (LOWORD(wParam) == ID_40001)
{
MessageBox(hWnd, L"菜单点击", L"菜单点击", MB_OK);
}
break;
}
// 当右键按下的时候,响应消息,弹出菜单
case WM_RBUTTONDOWN:
{
// WM_RBUTTONDOWN 提供的坐标实际上是一个客户区坐标,但是弹出
// 菜单要求提供的坐标是相对于屏幕上左上角的坐标,所以需要进行转换
POINT pt = { LOWORD(lParam), HIWORD(lParam) };
ClientToScreen(hWnd, &pt);
// 先获取到一个菜单资源,一个菜单资源中可能有多个顶级菜单,而弹出
// 的菜单只能是其中的一个,所以需要再次获取子菜单项
HMENU hParentMenu = LoadMenu(hInstance, MAKEINTRESOURCE(IDR_MENU1));
HMENU hChildMenu = GetSubMenu(hParentMenu, 0);
// 必须提供窗口句柄,表示菜单产生的消息由哪一个窗口进行响应
TrackPopupMenu(hChildMenu, TPM_LEFTALIGN, pt.x, pt.y, 0, hWnd, NULL);
break;
}
// 当用户点击关闭按钮时会产生 WM_CLOSE 消息
case WM_CLOSE:
{
DestroyWindow(hWnd);
break;
}
// 当调用 DestroyWindow 函数的时候会产生下面的消息
case WM_DESTROY:
{
PostQuitMessage(0);
break;
}
}
// 对于任何不关系的消息,我们应该使用操作系统的默认行为做出相应
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
// 1. 注册窗口类,必须需要提供的成员有回调函数和窗口类名
WNDCLASS WndClass{};
WndClass.lpfnWndProc = WndProc;
WndClass.lpszClassName = L"myclass";
// 可以直接在窗口类中指定使用的光标和图标资源
WndClass.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON1));
WndClass.hCursor = LoadCursor(hInstance, MAKEINTRESOURCE(IDC_CURSOR1));
// 可以在窗口类中添加菜单,优先级是最低的
WndClass.lpszMenuName = MAKEINTRESOURCE(IDR_MENU1);
if (!RegisterClass(&WndClass))
{
MessageBoxA(NULL, "窗口类注册失败", "错误消息", MB_OK | MB_ICONERROR);
return -1;
}
// 2. 使用注册好的窗口类创建窗口并显示更新窗口,也可以在创建窗口时指定菜单
HMENU hMenu = LoadMenu(hInstance, MAKEINTRESOURCE(IDR_MENU2));
HWND hWnd = CreateWindow(L"myclass", L"window", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, hMenu, hInstance, NULL);
ShowWindow(hWnd, SW_SHOWNORMAL);
UpdateWindow(hWnd);
// 3. 编写消息泵接收并分发消息,【消息本质上是一个结构体】
MSG msg{};
while (GetMessage(&msg, NULL, 0, 0))
{
DispatchMessage(&msg);
}
return 0;
}