一、资源的添加

右键"资源文件"->添加->资源:

此时自动创建了resource.h头文件和资源脚本(.rc),同时自动在resource.h头文件中进行了宏定义:

在Visual Studio中添加资源
-
方法1:通过解决方案资源管理器
-
右键点击项目 → 添加 → 资源
-
选择资源类型(Icon、Bitmap、Dialog等)
-
编辑资源并自动生成.rc文件
-
-
方法2:手动添加
-
将.rc文件添加到项目
-
将resource.h添加到项目
-
MAKEINTRESOURCE宏
cpp
// 作用:将整数ID转换为资源标识符字符串
// 资源ID <= 65535 时使用
#define MAKEINTRESOURCE(i) ((LPWSTR)((ULONG_PTR)((WORD)(i))))
// 使用示例
LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON1));
// 注意:资源ID > 65535 时使用字符串名称
LoadIcon(hInstance, L"MyLargeIcon");
二、各阶段的资源载入时机
1. 窗口类注册时(WinMain中)
cpp
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
// 时机1:注册窗口类时
WNDCLASSEX wc = {0};
wc.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); // 主图标
wc.hIconSm = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_APP_SMALL)); // 小图标
wc.hCursor = LoadCursor(NULL, IDC_ARROW); // 光标
// 时机2:检查DPI缩放,可能需要不同尺寸的图标
if (GetDpiForSystem() > 96) // 高DPI
{
wc.hIcon = (HICON)LoadImage(hInstance,
MAKEINTRESOURCE(IDI_APP_ICON_LARGE),
IMAGE_ICON, 48, 48, 0);
}
RegisterClassEx(&wc);
// 时机3:创建主窗口前预载入常用资源
PreloadFrequentlyUsedResources(hInstance);
// 主消息循环...
}
2. 窗口创建时(WM_CREATE消息)
cpp
case WM_CREATE:
{
// 时机:窗口刚创建,适合初始化窗口特定资源
// 1. 载入并设置菜单
HMENU hMenu = LoadMenu(hInstance, MAKEINTRESOURCE(IDR_MAIN_MENU));
SetMenu(hWnd, hMenu);
// 2. 载入加速键表
g_hAccel = LoadAccelerators(hInstance,
MAKEINTRESOURCE(IDR_ACCELERATOR));
// 3. 创建工具栏并载入位图
CreateToolbar(hWnd, hInstance);
// 4. 载入字符串资源用于设置窗口标题
TCHAR szTitle[256];
LoadString(hInstance, IDS_APP_TITLE, szTitle, 256);
SetWindowText(hWnd, szTitle);
// 5. 创建状态栏
CreateStatusBar(hWnd);
// 6. 初始化图像列表(用于列表视图、树视图)
g_hImageList = ImageList_Create(16, 16, ILC_COLOR32 | ILC_MASK, 5, 5);
HICON hFolderIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_FOLDER));
ImageList_AddIcon(g_hImageList, hFolderIcon);
return 0;
}
3. 窗口显示时(WM_SHOWWINDOW消息)
cpp
case WM_SHOWWINDOW:
if (wParam) // 窗口正在显示
{
// 时机:窗口首次显示或重新显示
if (!g_bResourcesLoaded)
{
// 延迟载入大资源(如图片、音频)
LoadBackgroundImage(hWnd);
LoadAudioResources();
g_bResourcesLoaded = TRUE;
}
}
break;
4. 绘制阶段(WM_PAINT消息)
cpp
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
// 时机:绘制时按需载入资源
if (!g_hBackgroundBitmap) // 懒加载
{
g_hBackgroundBitmap = LoadBitmap(hInstance,
MAKEINTRESOURCE(IDB_BACKGROUND));
}
// 绘制位图
HDC hdcMem = CreateCompatibleDC(hdc);
HBITMAP hOldBitmap = (HBITMAP)SelectObject(hdcMem, g_hBackgroundBitmap);
BitBlt(hdc, 0, 0, 800, 600, hdcMem, 0, 0, SRCCOPY);
SelectObject(hdcMem, hOldBitmap);
DeleteDC(hdcMem);
EndPaint(hWnd, &ps);
return 0;
}
5. 窗口大小改变时(WM_SIZE消息)
cpp
case WM_SIZE:
{
int width = LOWORD(lParam);
int height = HIWORD(lParam);
// 时机:窗口大小改变,可能需要不同尺寸的资源
if (width > 1024 && !g_hLargeBackground) // 大窗口
{
// 载入高清背景
g_hLargeBackground = (HBITMAP)LoadImage(
hInstance,
MAKEINTRESOURCE(IDB_BACKGROUND_LARGE),
IMAGE_BITMAP,
width, height,
LR_DEFAULTCOLOR
);
}
// 调整控件位置
MoveWindow(g_hStatusBar, 0, height - 20, width, 20, TRUE);
// 强制重绘
InvalidateRect(hWnd, NULL, TRUE);
return 0;
}
6. 主题/外观改变时(WM_THEMECHANGED消息)
cpp
case WM_THEMECHANGED:
// 时机:系统主题改变,可能需要更换资源
if (IsThemeActive())
{
// 使用主题化资源
LoadThemeAwareResources();
}
else
{
// 使用经典资源
LoadClassicResources();
}
// 重绘窗口
InvalidateRect(hWnd, NULL, TRUE);
return 0;
7. 用户交互时(WM_COMMAND等)
cpp
case WM_COMMAND:
switch (LOWORD(wParam))
{
case ID_FILE_OPEN:
// 时机:用户打开文件时
LoadDocumentResources();
break;
case ID_VIEW_LARGE_ICONS:
// 时机:切换视图模式
ImageList_Destroy(g_hImageList);
g_hImageList = ImageList_Create(32, 32, ILC_COLOR32, 0, 10);
// 载入大图标
LoadLargeIcons();
break;
case ID_HELP_ABOUT:
// 时机:显示关于对话框
DialogBox(hInstance, MAKEINTRESOURCE(IDD_ABOUT),
hWnd, AboutDialogProc);
break;
}
break;
8. 鼠标/键盘交互时
cpp
case WM_MOUSEMOVE:
// 时机:鼠标移动时动态改变光标
POINT pt = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
RECT rcButton;
GetClientRect(g_hButton, &rcButton);
if (PtInRect(&rcButton, pt))
{
// 鼠标在按钮上,显示手型光标
SetCursor(LoadCursor(NULL, IDC_HAND));
}
else
{
// 恢复默认光标
SetCursor(LoadCursor(NULL, IDC_ARROW));
}
break;
case WM_SETCURSOR:
// 时机:系统设置光标前
if (LOWORD(lParam) == HTCLIENT)
{
// 自定义客户端区域光标
SetCursor(g_hCustomCursor);
return TRUE;
}
break;
三、资源载入函数详解
1. Load系列函数(同步载入)
| 函数 | 用途 | 何时使用 | 内存管理 |
|---|---|---|---|
LoadIcon |
载入图标 | 窗口创建、设置图标时 | 系统管理,无需销毁 |
LoadCursor |
载入光标 | 鼠标消息、设置光标时 | 系统管理,无需销毁 |
LoadBitmap |
载入位图 | 绘制前、控件图像 | 必须 用DeleteObject销毁 |
LoadMenu |
载入菜单 | 窗口创建、改变菜单时 | 附加到窗口则自动销毁,否则需DestroyMenu |
LoadString |
载入字符串 | 显示文本前 | 无需销毁 |
LoadAccelerators |
载入加速键表 | 消息循环前 | 必须 用DestroyAcceleratorTable销毁 |
cpp
// ANSI版本
int LoadStringA(
HINSTANCE hInstance, // 包含字符串资源的模块句柄
UINT uID, // 字符串资源ID
LPSTR lpBuffer, // 接收字符串的缓冲区
int cchBufferMax // 缓冲区大小(字符数)
);
// Unicode版本(推荐)
int LoadStringW(
HINSTANCE hInstance,
UINT uID,
LPWSTR lpBuffer,
int cchBufferMax
);
// 通用宏定义
int LoadString(
HINSTANCE hInstance,
UINT uID,
LPTSTR lpBuffer,
int cchBufferMax
);
2. LoadImage函数(通用载入)
cpp
// 可载入多种类型资源,更灵活
HANDLE LoadImage(
HINSTANCE hInst, // 实例句柄
LPCSTR name, // 资源名/ID
UINT type, // 类型:IMAGE_BITMAP/ICON/CURSOR
int cx, int cy, // 期望尺寸(0=原始)
UINT load // 载入标志
);
// 常用标志:
// LR_DEFAULTCOLOR - 默认颜色(不转换)
// LR_LOADFROMFILE - 从文件载入
// LR_CREATEDIBSECTION- 创建DIB段(位图)
// LR_SHARED - 共享资源(多线程)
// LR_LOADTRANSPARENT - 透明色
3. FindResource/LoadResource(原始资源访问)
cpp
// 用于访问自定义二进制资源
HRSRC FindResource(HINSTANCE hModule, LPCSTR name, LPCSTR type);
DWORD SizeofResource(HINSTANCE hModule, HRSRC hResInfo);
HGLOBAL LoadResource(HINSTANCE hModule, HRSRC hResInfo);
LPVOID LockResource(HGLOBAL hResData);
// 使用模式:
HRSRC hRes = FindResource(hInst, MAKEINTRESOURCE(IDR_DATA), "BINARY");
if (hRes)
{
DWORD dwSize = SizeofResource(hInst, hRes);
HGLOBAL hGlobal = LoadResource(hInst, hRes);
BYTE* pData = (BYTE*)LockResource(hGlobal);
// 使用数据...
// 注意:不需要调用UnlockResource或FreeResource
}
示例:
cpp
#include <windows.h>
#include "resource.h" // 包含IDR_MAIN_MENU定义
HINSTANCE g_hInstance;
HWND g_hWndMain;
// 菜单命令处理函数
void HandleMenuCommand(WPARAM wParam)
{
switch (LOWORD(wParam))
{
case ID_FILE_NEW:
MessageBox(g_hWndMain, "新建文件", "菜单", MB_OK);
break;
case ID_FILE_OPEN:
MessageBox(g_hWndMain, "打开文件", "菜单", MB_OK);
break;
case ID_FILE_EXIT:
PostQuitMessage(0);
break;
}
}
// 窗口过程
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_CREATE:
{
// 载入并设置菜单
HMENU hMenu = LoadMenu(g_hInstance, MAKEINTRESOURCE(IDR_MAIN_MENU));
if (hMenu)
{
SetMenu(hWnd, hMenu);
// 可选:启用/禁用特定菜单项
EnableMenuItem(hMenu, ID_FILE_SAVE, MF_GRAYED);
// 可选:检查菜单项
CheckMenuItem(hMenu, ID_VIEW_TOOLBAR, MF_CHECKED);
}
}
break;
case WM_COMMAND:
HandleMenuCommand(wParam);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
}
return DefWindowProc(hWnd, msg, wParam, lParam);
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
g_hInstance = hInstance;
// 注册窗口类
WNDCLASSEX wc = {0};
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = WndProc;
wc.hInstance = hInstance;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszClassName = TEXT("MyWindowClass");
if (!RegisterClassEx(&wc))
return 0;
// 创建窗口
g_hWndMain = CreateWindow(
TEXT("MyWindowClass"), TEXT("菜单示例"),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 800, 600,
NULL, NULL, hInstance, NULL
);
if (!g_hWndMain)
return 0;
ShowWindow(g_hWndMain, nCmdShow);
UpdateWindow(g_hWndMain);
// 消息循环
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}
四、资源设置/改变函数
1. 图标相关
cpp
// 设置窗口图标(大/小)
SendMessage(hWnd, WM_SETICON, ICON_BIG, (LPARAM)hIcon);
SendMessage(hWnd, WM_SETICON, ICON_SMALL, (LPARAM)hIconSmall);
// 直接设置窗口类图标(影响后续窗口)
SetClassLongPtr(hWnd, GCLP_HICON, (LONG_PTR)hIcon);
SetClassLongPtr(hWnd, GCLP_HICONSM, (LONG_PTR)hIconSmall);
// 获取当前图标
HICON hCurrentIcon = (HICON)GetClassLongPtr(hWnd, GCLP_HICON);
2. 光标相关
cpp
// 立即设置光标(仅当前线程)
SetCursor(hCursor);
// 设置窗口类光标(影响后续光标)
SetClassLongPtr(hWnd, GCLP_HCURSOR, (LONG_PTR)hCursor);
// 在WM_SETCURSOR中处理
case WM_SETCURSOR:
if (LOWORD(lParam) == HTCLIENT) // 在客户端区域
{
SetCursor(hCustomCursor);
return TRUE; // 阻止默认处理
}
break;
3. 位图相关
cpp
// 设置静态控件位图
SendMessage(hStatic, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)hBitmap);
// 设置按钮位图
SendMessage(hButton, BM_SETIMAGE, IMAGE_BITMAP, (LPARAM)hBitmap);
// 设置列表框项目图像
SendMessage(hList, LB_SETITEMDATA, index, (LPARAM)hBitmap);
// 更换图像列表中的位图
ImageList_Replace(g_hImageList, index, hNewBitmap, NULL);
4. 菜单相关
cpp
// 设置窗口菜单
SetMenu(hWnd, hNewMenu);
// 修改菜单项
ModifyMenu(hMenu, ID_ITEM, MF_BYCOMMAND | MF_STRING,
ID_ITEM, "新文本");
// 启用/禁用菜单项
EnableMenuItem(hMenu, ID_ITEM, MF_GRAYED | MF_DISABLED);
EnableMenuItem(hMenu, ID_ITEM, MF_ENABLED);
// 标记菜单项
CheckMenuItem(hMenu, ID_ITEM, MF_CHECKED);
CheckMenuItem(hMenu, ID_ITEM, MF_UNCHECKED);
5. 字符串相关
cpp
// 设置窗口标题
SetWindowText(hWnd, "新标题");
// 设置控件文本
SetDlgItemText(hDlg, IDC_EDIT, "新文本");
// 设置状态栏文本
SendMessage(hStatusBar, SB_SETTEXT, part, (LPARAM)"状态");
// 动态创建字符串资源
TCHAR szBuffer[256];
wsprintf(szBuffer, "格式化的: %s %d", szName, nValue);
SetWindowText(hWnd, szBuffer);
五、常见问题
1. LoadBitmap载入的位图需要销毁吗?为什么?
- 必须用DeleteObject销毁
- 原因:GDI对象由应用程序管理,系统不会自动释放
- 不销毁会导致资源泄漏(GDI泄漏)
2. LoadMenu和LoadString的内存管理有什么区别?
- LoadMenu:
- 附加到窗口时:窗口销毁自动销毁
- 不附加时:必须用DestroyMenu手动销毁
- 动态创建的菜单:必须用DestroyMenu销毁
- LoadString:
- 无需特殊销毁
- 字符串缓冲由调用者管理
- 资源段中的字符串只读,系统管理