Windows学习笔记-07(资源的添加、载入和使用)

一、资源的添加

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

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

在Visual Studio中添加资源

  • 方法1:通过解决方案资源管理器

    1. 右键点击项目 → 添加 → 资源

    2. 选择资源类型(Icon、Bitmap、Dialog等)

    3. 编辑资源并自动生成.rc文件

  • 方法2:手动添加

    1. 将.rc文件添加到项目

    2. 将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:
  - 无需特殊销毁
  - 字符串缓冲由调用者管理
  - 资源段中的字符串只读,系统管理
相关推荐
齐生116 小时前
iOS 知识点 - IAP 是怎样的?
笔记
tingshuo29171 天前
D006 【模板】并查集
笔记
tingshuo29172 天前
S001 【模板】从前缀函数到KMP应用 字符串匹配 字符串周期
笔记
阿白的白日梦3 天前
winget基础管理---更新/修改源为国内源
windows
埃博拉酱7 天前
VS Code Remote SSH 连接 Windows 服务器卡在"下载 VS Code 服务器":prcdn DNS 解析失败的诊断与 BITS 断点续传
windows·ssh·visual studio code
唐宋元明清21887 天前
.NET 本地Db数据库-技术方案选型
windows·c#
西岸行者7 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
加号37 天前
windows系统下mysql多源数据库同步部署
数据库·windows·mysql
tryCbest7 天前
Windows环境下配置pip镜像源
windows·pip
呉師傅7 天前
火狐浏览器报错配置文件缺失如何解决#操作技巧#
运维·网络·windows·电脑