【C++】ImGui:VSCode下的无依赖轻量GUI开发

本教程将手把手带您用纯原生方式构建ImGui应用,无需CMake/第三方库。您将全程明了自己每个操作的意义,特别适合首次接触GUI开发的新手。

环境配置

安装VSCode

  • 作用:轻量级代码编辑器,提供智能提示
  • 操作
    1. 官网下载安装包 → 默认选项安装
    2. 安装后打开,按下Ctrl+Shift+X安装"C/C++ Extension Pack"

配置MinGW

  • 作用:提供Windows下的GCC编译环境
  • 操作
    1. 访问WinLibs下载MinGW-W64 GCC 14.2.0 + LLVM/Clang/LLD/LLDB 19.1.7的Release包
    2. 解压到C:\mingw64(路径不可含中文)
    3. 右键开始菜单 → 系统 → 高级系统设置 → 环境变量 → 在Path中添加C:\mingw64\bin

验证成功
打开CMD输入g++ -v应显示类似gcc version 14.2.0的版本信息

项目结构解析

创建标准目录

MyProject/
├─.vscode/       # VSCode专属配置
│   ├─tasks.json # 编译指令配置
│   └─settings.json # 头文件路径配置
├─libs/
│   └─imgui/     # ImGui核心库文件
└─src/
    ├─main.cpp    # 窗口初始化代码(无需修改)
    └─Application.hpp # 你的UI主战场

关键说明

  • .vscode:存放编辑器配置,避免每次手动设置
  • libs/imgui:集中管理第三方库,便于后续升级

获取ImGui源码

精准获取必要文件

  1. 访问Imgui的GitHub仓库
  2. 下载ZIP包后解压,按以下清单复制文件:
    • 核心组件(来自根目录):
      • imgui.cpp, imgui.h, imgui_*.cpp/h
      • imstb_*.h系列文件
    • Windows后端(来自backends/):
      • imgui_impl_win32.h/cpp
      • imgui_impl_dx11.h/cpp

为何需要后端文件
ImGui本身是平台无关的GUI库,需要特定平台的实现才能显示窗口。这里选择DirectX11作为渲染后端。

配置文件详解

tasks.json(编译指令)

{
    
      
    "version": "2.0.0",  // JSON 配置文件的版本号  
    "tasks": [            // 定义一个任务列表  
        {
    
      
            "label": "Build main.exe",  // 任务名称,方便识别  
            "type": "shell",            // 任务类型,表示这是一个 shell 命令  
            "command": "g++",           // 使用的编译器命令,这里是 g++  
            "args": [                   // 传递给编译器的参数列表  
                "-I",                  // 指定头文件搜索路径  
                "${workspaceFolder}/libs/imgui",  // imgui 库的头文件路径  
                "-I",                  // 再次指定头文件搜索路径  
                "${workspaceFolder}/include",  // 项目自己的头文件路径  
                "src/main.cpp",        // 要编译的主源文件  
                "${workspaceFolder}/libs/imgui/*.cpp", // imgui 库的所有实现文件  
                "-o",                  // 用于指定输出文件  
                "${workspaceFolder}/${workspaceFolderBasename}.exe", // 输出的可执行文件路径  
                "-static",             // 静态链接,用于不依赖 DLL  
                "-L/mingw64/lib",      // 指定库文件搜索路径  
                "-ld3d11",             // 链接 Direct3D 11 库  
                "-ldxgi",              // 链接 DXGI 库  
                "-luser32",            // 链接 Windows 用户界面库  
                "-lgdi32",             // 链接 GDI 图形设备接口库  
                "-lole32",             // 链接 OLE 库  
                "-ldwmapi",            // 链接 DWM API 库  
                "-ld3dcompiler",        // 链接 D3D 编译器库  
                "'-Wl,-subsystem,windows'" // 容器选项:指定生成 Windows 应用程序  
            ],  
            "group": {
    
                     // 任务分组信息  
                "kind": "build",       // 组的类型为构建  
                "isDefault": true      // 设定为默认构建任务  
            }  
        }  
    ]  
}  

settings.json(智能提示配置)

{
    
      
    "C_Cpp.default.includePath": [  // C/C++ 扩展的默认头文件搜索路径  
        "${workspaceFolder}/include", // 项目中包含头文件的路径  
        "${workspaceFolder}/libs/imgui" // ImGui 库的头文件路径  
    ],  
    "C_Cpp.default.Definition": [   // C/C++ 扩展的默认宏定义路径  
        "${workspaceFolder}/include" // 需要使用宏定义的头文件路径  
    ],  
    "C_Cpp.errorSquiggles": "disabled", // 禁用错误波浪线提示  
    "C_Cpp.intelliSenseEngine": "default", // 使用默认的智能感知引擎  
    "C_Cpp.default.compileCommands": "${workspaceFolder}/build/compile_commands.json", // 指定编译命令 JSON 文件的路径(用于基于 CMake 或其他构建系统时提供编译信息)  
    "files.associations": {
    
                // 文件类型关联设置  
        "*.cpp": "cpp",                // 将所有 .cpp 文件识别为 C++ 文件  
        "*.h": "cpp",                  // 将所有 .h 文件识别为 C++ 文件(兼容 C++ 头文件)  
        "vector": "cpp"                // 将名为 vector 的文件/模块识别为 C++ 类型(通常是 STL 的一部分)  
    }  
}  

编写代码

main.cpp(固定结构,无需修改)

#include "imgui.h"
#include "imgui_impl_win32.h"
#include "imgui_impl_dx11.h"
#include <d3d11.h>
#include <tchar.h>
#include "Application.hpp"

// 数据
static ID3D11Device *g_pd3dDevice = nullptr;
static ID3D11DeviceContext *g_pd3dDeviceContext = nullptr;
static IDXGISwapChain *g_pSwapChain = nullptr;
static bool g_SwapChainOccluded = false;
static UINT g_ResizeWidth = 0, g_ResizeHeight = 0;
static ID3D11RenderTargetView *g_mainRenderTargetView = nullptr;

// 辅助函数的前向声明
bool CreateDeviceD3D(HWND hWnd);
void CleanupDeviceD3D();
void CreateRenderTarget();
void CleanupRenderTarget();
LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

// 主代码
int main(int, char **)
{
    
    
    // 创建应用程序窗口
    // ImGui_ImplWin32_EnableDpiAwareness();
    WNDCLASSEXW wc = {
    
    sizeof(wc), CS_CLASSDC, WndProc, 0L, 0L, GetModuleHandle(nullptr), nullptr, nullptr, nullptr, nullptr, L"ImGui Example", nullptr};
    ::RegisterClassExW(&wc);
    HWND hwnd = ::CreateWindowW(wc.lpszClassName, L"Dear ImGui DirectX11 示例", WS_OVERLAPPEDWINDOW, 100, 100, 1280, 800, nullptr, nullptr, wc.hInstance, nullptr);

    // 初始化 Direct3D
    if (!CreateDeviceD3D(hwnd))
    {
    
    
        CleanupDeviceD3D();
        ::UnregisterClassW(wc.lpszClassName, wc.hInstance);
        return 1;
    }

    // 显示窗口
    ::ShowWindow(hwnd, SW_SHOWDEFAULT);
    ::UpdateWindow(hwnd);

    // 设置 Dear ImGui 上下文
    IMGUI_CHECKVERSION();
    ImGui::CreateContext();
    ImGuiIO &io = ImGui::GetIO();
    (void)io;
    io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // 启用键盘控制
    io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;  // 启用游戏手柄控制

    // 设置 Dear ImGui 样式
    ImGui::StyleColorsDark();
    // ImGui::StyleColorsLight();

    // 设置平台/渲染器后端
    ImGui_ImplWin32_Init(hwnd);
    ImGui_ImplDX11_Init(g_pd3dDevice, g_pd3dDeviceContext);

    // 加载字体
    // - 如果没有加载字体,Dear ImGui 将使用默认字体。您还可以加载多个字体并使用 ImGui::PushFont()/PopFont() 来选择它们。
    // - AddFontFromFileTTF() 将返回 ImFont*,如果您需要在多个字体中选择,可以存储它。
    // - 如果文件无法加载,该函数将返回 nullptr。请在您的应用程序中处理这些错误(例如使用断言,或显示错误并退出)。
    // - 字体将在给定大小(带过采样)下被栅格化并存储到纹理中,当调用 ImFontAtlas::Build()/GetTexDataAsXXXX() 时,ImGui_ImplXXXX_NewFrame 将在下面调用它。
    // - 在 imconfig 文件中使用 '#define IMGUI_ENABLE_FREETYPE' 以使用 Freetype 进行更高质量的字体渲染。
    // - 阅读 'docs/FONTS.md' 获取更多说明和细节。
    // - 请记住,在 C/C++ 中,如果您想在字符串字面量中包含反斜杠 \,需要写双反斜杠 \\!
    // io.Fonts->AddFontDefault();
    // io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf", 18.0f);
    // io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f);
    // io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f);
    // io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f);
    // ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, nullptr, io.Fonts->GetGlyphRangesJapanese());
    // IM_ASSERT(font != nullptr);

    // 我们的状态
    bool show_demo_window = true;
    bool show_another_window = false;
    ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f);

    // 主循环
    bool done = false;
    while (!done)
    {
    
    
        // 轮询并处理消息(输入、窗口调整大小等)
        // 请参阅下面的 WndProc() 函数,了解我们如何将事件分派到 Win32 后端。
        MSG msg;
        while (::PeekMessage(&msg, nullptr, 0U, 0U, PM_REMOVE))
        {
    
    
            ::TranslateMessage(&msg);
            ::DispatchMessage(&msg);
            if (msg.message == WM_QUIT)
                done = true;
        }
        if (done)
            break;

        // 处理窗口最小化或屏幕锁定的情况
        if (g_SwapChainOccluded && g_pSwapChain->Present(0, DXGI_PRESENT_TEST) == DXGI_STATUS_OCCLUDED)
        {
    
    
            ::Sleep(10);
            continue;
        }
        g_SwapChainOccluded = false;

        // 处理窗口大小调整(我们不在 WM_SIZE 处理程序中直接调整大小)
        if (g_ResizeWidth != 0 && g_ResizeHeight != 0)
        {
    
    
            CleanupRenderTarget();
            g_pSwapChain->ResizeBuffers(0, g_ResizeWidth, g_ResizeHeight, DXGI_FORMAT_UNKNOWN, 0);
            g_ResizeWidth = g_ResizeHeight = 0;
            CreateRenderTarget();
        }

        // 开始 Dear ImGui 帧
        ImGui_ImplDX11_NewFrame();
        ImGui_ImplWin32_NewFrame();
        ImGui::NewFrame();

        app::RenderUI();

        // 渲染
        ImGui::Render();
        const float clear_color_with_alpha[4] = {
    
    clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w};
        g_pd3dDeviceContext->OMSetRenderTargets(1, &g_mainRenderTargetView, nullptr);
        g_pd3dDeviceContext->ClearRenderTargetView(g_mainRenderTargetView, clear_color_with_alpha);
        ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());

        // 呈现
        HRESULT hr = g_pSwapChain->Present(1, 0); // 使用垂直同步呈现
        // HRESULT hr = g_pSwapChain->Present(0, 0); // 不使用垂直同步呈现
        g_SwapChainOccluded = (hr == DXGI_STATUS_OCCLUDED);
    }

    // 清理
    ImGui_ImplDX11_Shutdown();
    ImGui_ImplWin32_Shutdown();
    ImGui::DestroyContext();

    CleanupDeviceD3D();
    ::DestroyWindow(hwnd);
    ::UnregisterClassW(wc.lpszClassName, wc.hInstance);

    return 0;
}

// 辅助函数

bool CreateDeviceD3D(HWND hWnd)
{
    
    
    // 设置交换链
    DXGI_SWAP_CHAIN_DESC sd;
    ZeroMemory(&sd, sizeof(sd));
    sd.BufferCount = 2;
    sd.BufferDesc.Width = 0;
    sd.BufferDesc.Height = 0;
    sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
    sd.BufferDesc.RefreshRate.Numerator = 60;
    sd.BufferDesc.RefreshRate.Denominator = 1;
    sd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
    sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    sd.OutputWindow = hWnd;
    sd.SampleDesc.Count = 1;
    sd.SampleDesc.Quality = 0;
    sd.Windowed = TRUE;
    sd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;

    UINT createDeviceFlags = 0;
    // createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
    D3D_FEATURE_LEVEL featureLevel;
    const D3D_FEATURE_LEVEL featureLevelArray[2] = {
    
    
        D3D_FEATURE_LEVEL_11_0,
        D3D_FEATURE_LEVEL_10_0,
    };
    HRESULT res = D3D11CreateDeviceAndSwapChain(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, createDeviceFlags, featureLevelArray, 2, D3D11_SDK_VERSION, &sd, &g_pSwapChain, &g_pd3dDevice, &featureLevel, &g_pd3dDeviceContext);
    if (res == DXGI_ERROR_UNSUPPORTED) // 如果硬件不可用,尝试高性能 WARP 软件驱动
        res = D3D11CreateDeviceAndSwapChain(nullptr, D3D_DRIVER_TYPE_WARP, nullptr, createDeviceFlags, featureLevelArray, 2, D3D11_SDK_VERSION, &sd, &g_pSwapChain, &g_pd3dDevice, &featureLevel, &g_pd3dDeviceContext);
    if (res != S_OK)
        return false;

    CreateRenderTarget();
    return true;
}

void CleanupDeviceD3D()
{
    
    
    CleanupRenderTarget();
    if (g_pSwapChain)
    {
    
    
        g_pSwapChain->Release();
        g_pSwapChain = nullptr;
    }
    if (g_pd3dDeviceContext)
    {
    
    
        g_pd3dDeviceContext->Release();
        g_pd3dDeviceContext = nullptr;
    }
    if (g_pd3dDevice)
    {
    
    
        g_pd3dDevice->Release();
        g_pd3dDevice = nullptr;
    }
}

void CreateRenderTarget()
{
    
    
    ID3D11Texture2D *pBackBuffer;
    g_pSwapChain->GetBuffer(0, IID_PPV_ARGS(&pBackBuffer));
    g_pd3dDevice->CreateRenderTargetView(pBackBuffer, nullptr, &g_mainRenderTargetView);
    pBackBuffer->Release();
}

void CleanupRenderTarget()
{
    
    
    if (g_mainRenderTargetView)
    {
    
    
        g_mainRenderTargetView->Release();
        g_mainRenderTargetView = nullptr;
    }
}

// 从 imgui_impl_win32.cpp 前向声明消息处理程序
extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

// Win32 消息处理程序
// 您可以读取 io.WantCaptureMouse、io.WantCaptureKeyboard 标志来判断 Dear ImGui 是否想使用您的输入。
// - 当 io.WantCaptureMouse 为 true 时,不要将鼠标输入数据分派到您的主应用程序,或者清除/覆盖您的鼠标数据副本。
// - 当 io.WantCaptureKeyboard 为 true 时,不要将键盘输入数据分派到您的主应用程序,或者清除/覆盖您的键盘数据副本。
// 通常,您可以始终将所有输入传递给 Dear ImGui,并根据这两个标志从您的应用程序中隐藏它们。
LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    
    
    if (ImGui_ImplWin32_WndProcHandler(hWnd, msg, wParam, lParam))
        return true;

    switch (msg)
    {
    
    
    case WM_SIZE:
        if (wParam == SIZE_MINIMIZED)
            return 0;
        g_ResizeWidth = (UINT)LOWORD(lParam); // 队列调整大小
        g_ResizeHeight = (UINT)HIWORD(lParam);
        return 0;
    case WM_SYSCOMMAND:
        if ((wParam & 0xfff0) == SC_KEYMENU) // 禁用 ALT 应用程序菜单
            return 0;
        break;
    case WM_DESTROY:
        ::PostQuitMessage(0);
        return 0;
    }
    return ::DefWindowProcW(hWnd, msg, wParam, lParam);
}
  • 作用:创建窗口、初始化DirectX、处理消息循环
  • 你的关注点:无需理解细节,这是框架的"发动机"

Application.hpp(你的创作空间)

// Application.hpp - 在此自由设计界面
#pragma once
#include <imgui.h>
namespace app
{
    
    
    void RenderUI()
    {
    
    
        ImGui::Begin("My First GUI");

        // 示例:创建一个红色按钮
        ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(1, 0, 0, 1));
        if (ImGui::Button("Click for a Surprise"))
        {
    
    
            // 在此添加点击事件
        }
        ImGui::PopStyleColor();

        ImGui::End();
    }
}

开发技巧

  • 所有UI代码写在RenderUI()函数内
  • 使用ImGui::开头的函数创建控件
  • Ctrl+Space触发代码提示

构建与调试

一键编译

  • 按下Ctrl+Shift+B启动编译(编译生成的二进制文件名为项目文件夹名)
  • 观察底部终端输出:
    • 出现Finished表示成功
    • 错误信息通常指向缺失文件或语法错误

运行程序

  • 方式一:VSCode终端输入./app.exe
  • 方式二:直接双击生成的app.exe

预期效果
显示1280x800窗口,其中包含你设计的UI元素

进阶指引

修改窗口属性

main.cpp中搜索以下常量修改:

// 原值
CreateWindow(..., "标题", WS_OVERLAPPEDWINDOW, 100, 100, 1280, 800, ...)

// 修改示例
CreateWindow(..., "新标题", WS_POPUP, 0, 0, 1920, 1080, ...)

学习资源推荐