Dear ImGui 开源即时模式 GUI 库深度解析

1. 什么是 Dear ImGui

Dear ImGui 是一个 C++ 即时模式(Immediate Mode)GUI 库 ,由 Omar Cornut 于 2014 年创建,目前托管在 GitHub,累计 64k+ Stars。它不依赖任何特定图形后端,通过 Backend + Renderer 的抽象层接入 OpenGL、DirectX、Vulkan、Metal 等主流图形 API。

核心定位:面向工具与调试的轻量级 GUI 框架,而非面向最终用户的精美 UI 框架。

与其他 GUI 库的本质区别

维度 Dear ImGui Qt / WPF / wxWidgets
模式 即时模式(Immediate Mode) 保留模式(Retained Mode)
状态管理 每一帧重新生成 UI 代码 状态持久化在控件树中
布局 声明式布局,自动换行/对齐 布局管理器 + 信号/槽
控件句柄 无,代码即声明 每个控件有对象句柄
学习曲线 低,100 行可出完整界面 高,需学习信号槽/布局/继承体系
二进制体积 ~500KB(静态库) 数十 MB
启动时间 毫秒级 秒级

2. 核心设计哲学:即时模式

2.1 什么是即时模式

传统 GUI 框架(Qt、Windows Forms)采用保留模式:创建一个按钮后,按钮对象在内存中持久存在,直到显式销毁。框架维护一个完整的控件树,事件由框架分发给控件。

ImGui 采用即时模式每一帧从头到尾重新声明 UI。没有持久化的控件对象,没有事件循环分发。代码像这样:

cpp 复制代码
// 每一帧都执行
if (ImGui::Button("Click Me")) {
    // 只有这一帧按钮被点击时才进入
    printf("Button clicked!\n");
}

按钮在内存中不存在------它只是由 ImGui::Button 函数在当前帧渲染出的一个矩形区域。当这一帧结束时,这个"按钮"消失。下一帧再次调用 ImGui::Button 时,ImGui 通过内部状态(鼠标位置、上次点击状态等)判断是否被点击。

2.2 内部状态流

复制代码

2.3 底层工作原理

ImGui 的核心数据结构是 ID StackDrawList

  • ID Stack:每个控件有一个唯一 ID(由文本/地址/隐式计数器合成),用于关联帧间状态(如输入框焦点、折叠状态)。
  • DrawList:ImGui::Render() 将整帧的 UI 声明转换为一个 ImDrawData 结构体,包含顶点缓冲(ImDrawVert)和索引缓冲,提交给图形 API 渲染。ImGui 不负责渲染,只负责生成三角形网格。

抗锯齿原理:ImGui 的圆角矩形、圆形等使用 GPU 渲染,通过顶点着色器实现抗锯齿,所有控件最终都是三角形网格。


3. 使用优点

3.1 开发效率极高

  • 代码即 UI:不需要拖拽控件、不需要 XML/QML 布局文件,所有 UI 在 C++ 代码中一行行声明。
  • 零样板代码:不需要创建窗口类、注册消息回调、维护析构顺序。
  • 热重载友好:修改代码后重新编译,UI 结构自动跟随变化,无需手动同步状态文件。

3.2 跨平台与后端无关

  • 官方提供 DX9/10/11/12、OpenGL 2/3、Vulkan、Metal 的 Renderer 实现。
  • 官方提供 GLFW、SDL2、Win32、GLUT、Allegro 5 的 Platform 后端。
  • 单个 .h + .cpp 即可嵌入现有项目,零外部依赖。

3.3 可嵌入任何现有渲染管线

cpp 复制代码
// 在你的渲染循环中嵌入 ImGui
while (running) {
    glfwPollEvents();
    ImGui_ImplOpenGL3_NewFrame();
    ImGui_ImplGlfw_NewFrame();
    ImGui::NewFrame();

    // 你的 UI 声明
    my_app_gui();

    ImGui::Render();
    ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
    // 你的渲染
    glfwSwapBuffers(window);
}

3.4 调试工具首选

  • 嵌入到游戏引擎、3D 建模工具、性能分析器中,几行代码即可弹出调试面板。
  • 游戏内控制台、实体属性编辑器、材质预览面板等场景,ImGui 是事实标准。

3.5 极致轻量

  • 编译后静态库约 500KB。
  • 无运行时依赖,无动态链接。
  • 启动时间 < 10ms。
  • 内存占用:~1MB 基线 + 按 UI 复杂度线性增长。

4. 使用场景

4.1 游戏引擎内嵌工具(最核心场景)

Unreal Engine、Unity、Godot 等引擎的编辑器大量使用 ImGui 或其变体:

  • 场景大纲视图(Entity List)
  • Inspector 属性编辑器
  • 材质/贴图预览面板
  • 性能 HUD(FPS、Draw Call 统计)
  • 地形编辑器工具面板

4.2 性能分析器与 Profiler

  • 实时显示 CPU/GPU 帧时间曲线(ImGui::PlotLines / PlotHistogram)
  • 内存分配热点图
  • 网络包流量监控
  • 自定义性能计数器仪表盘

4.3 3D 建模与 DCC 工具

  • Blender 插件界面
  • 节点编辑器(ImGui 提供 Node Editor 示例代码)
  • 曲线编辑器(Bezier 手柄交互)
  • 自定义导入导出面板

4.4 嵌入式系统调试

  • 通过串口帧缓冲,在嵌入式设备屏幕上渲染 ImGui 调试面板
  • 配合 SDL 或 Framebuffer 后端,在 Linux 嵌入式设备上运行

4.5 原型验证与内部工具

  • 快速搭建配置编辑器
  • 数据集标注工具
  • 日志浏览器
  • A/B 测试结果可视化面板

不适合的场景

  • 面向最终用户的精美 UI(如办公软件、电商界面)
  • 需要复杂动画与过渡效果的应用
  • 对无障碍/国际化/HiDPI 有严格要求的场景
  • 移动端原生 App(虽可运行,但触摸交互不如原生流畅)

5. 快速上手:从零搭建 ImGui 应用

5.1 集成方式

方式一:官方 Bundle(推荐)

bash 复制代码
git clone https://github.com/ocornut/imgui.git --branch v1.91.6
cd imgui
# 复制 backend 文件
# 复制到你的项目,加入 cmake 或直接编译

方式二:CMake FetchContent

复制代码
FetchContent_Declare(
    imgui
    GIT_REPOSITORY https://github.com/ocornut/imgui.git
    GIT_TAG v1.91.6
)
FetchContent_MakeAvailable(imgui)
target_link_libraries(myapp PRIVATE imgui)

5.2 完整示例(GLFW + OpenGL3)

cpp 复制代码
#include "imgui.h"
#include "imgui_impl_glfw.h"
#include "imgui_impl_opengl3.h"
#include <GLFW/glfw3.h>

int main() {
    // 1. 初始化 GLFW
    if (!glfwInit()) return -1;
    GLFWwindow* window = glfwCreateWindow(1280, 720, "Dear ImGui Demo", nullptr, nullptr);
    glfwMakeContextCurrent(window);
    glfwSwapInterval(1); // 启用垂直同步

    // 2. 初始化 ImGui
    IMGUI_CHECKVERSION();
    ImGui::CreateContext();
    ImGuiIO& io = ImGui::GetIO();
    io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // 键盘导航
    io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;     // 启用停靠

    // 3. 设置 Platform + Renderer 后端
    ImGui_ImplGlfw_InitForOpenGL(window, true);
    ImGui_ImplOpenGL3_Init("#version 130");

    // 4. 主循环
    while (!glfwWindowShouldClose(window)) {
        glfwPollEvents();

        ImGui_ImplOpenGL3_NewFrame();
        ImGui_ImplGlfw_NewFrame();
        ImGui::NewFrame();

        // -- 你的 UI 代码 --
        ImGui::ShowDemoWindow(); // 官方 Demo 窗口

        // 自定义窗口
        ImGui::Begin("Hello, ImGui!");
        ImGui::Text("This is a text label.");
        ImGui::Button("Click Me");
        static float f = 0.0f;
        ImGui::SliderFloat("Float", &f, 0.0f, 1.0f);
        ImGui::End();

        // -- 渲染 --
        ImGui::Render();
        int display_w, display_h;
        glfwGetFramebufferSize(window, &display_w, &display_h);
        glViewport(0, 0, display_w, display_h);
        glClearColor(0.45f, 0.55f, 0.60f, 1.00f);
        glClear(GL_COLOR_BUFFER_BIT);
        ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());

        glfwSwapBuffers(window);
    }

    // 5. 清理
    ImGui_ImplOpenGL3_Shutdown();
    ImGui_ImplGlfw_Shutdown();
    ImGui::DestroyContext();
    glfwDestroyWindow(window);
    glfwTerminate();
    return 0;
}

5.3 CMakeLists.txt 完整配置

复制代码
cmake_minimum_required(VERSION 3.16)
project(ImGuiDemo)

set(CMAKE_CXX_STANDARD 17)

# 源码直接编译
add_executable(imgui_demo
    main.cpp
    imgui/imgui.cpp
    imgui/imgui_draw.cpp
    imgui/imgui_tables.cpp
    imgui/imgui_widgets.cpp
    imgui/backends/imgui_impl_glfw.cpp
    imgui/backends/imgui_impl_opengl3.cpp
)

target_include_directories(imgui_demo PRIVATE imgui imgui/backends)
target_link_libraries(imgui_demo PRIVATE glfw3 opengl32)

6. 核心 API 详解

6.1 窗口系统

cpp 复制代码
// 基本窗口
ImGui::Begin("窗口标题", &open, flags);
ImGui::Text("内容");
ImGui::End();

// flags 常用组合
ImGuiWindowFlags_NoMove          // 不可移动
ImGuiWindowFlags_NoResize        // 不可调整大小
ImGuiWindowFlags_NoCollapse      // 无折叠按钮
ImGuiWindowFlags_NoTitleBar      // 无标题栏
ImGuiWindowFlags_AlwaysAutoResize // 自动适配内容
ImGuiWindowFlags_NoScrollbar     // 无滚动条

6.2 常用控件

cpp 复制代码
// 文本
ImGui::Text("普通文本");
ImGui::TextColored(ImVec4(1,0,0,1), "红色文本");
ImGui::BulletText("项目符号文本");

// 按钮
ImGui::Button("普通按钮");
ImGui::SmallButton("小按钮");
ImGui::ArrowButton("arrow", ImGuiDir_Right);
bool clicked = ImGui::Button("带返回值按钮");
if (clicked) { /* 处理点击 */ }

// 输入
static char input[128] = "";
ImGui::InputText("标签", input, sizeof(input));
ImGui::InputInt("整数", &i);
ImGui::InputFloat("浮点数", &f, 0.1f, 1.0f, "%.3f");

// 滑块
static float v = 0.5f;
ImGui::SliderFloat("滑块", &v, 0.0f, 1.0f);
ImGui::SliderInt("整数滑块", &iv, 0, 100);
ImGui::SliderAngle("角度滑块", &angle);
ImGui::VSliderFloat("垂直滑块", ImVec2(18, 160), &vv, 0, 1);

// 选择框
static bool checked = false;
ImGui::Checkbox("复选框", &checked);
ImGui::RadioButton("单选A", &selected, 0);
ImGui::RadioButton("单选B", &selected, 1);

// 组合框
static int item = 0;
const char* items[] = {"选项A", "选项B", "选项C"};
ImGui::Combo("下拉框", &item, items, IM_ARRAYSIZE(items));

// 颜色
static ImVec4 color = ImVec4(1,1,1,1);
ImGui::ColorEdit3("RGB颜色", (float*)&color);
ImGui::ColorPicker4("拾色器", (float*)&color);

// 进度条
static float progress = 0.0f;
ImGui::ProgressBar(progress, ImVec2(-1, 0), "进度");

6.3 布局控制

cpp 复制代码
// 分组
ImGui::BeginGroup();
ImGui::Button("A");
ImGui::Button("B");
ImGui::EndGroup();

// 间距
ImGui::Spacing();
ImGui::Separator();          // 横线分隔
ImGui::SameLine();           // 放在同一行
ImGui::Indent(20.0f);        // 缩进
ImGui::Unindent(20.0f);

// 可折叠区域
if (ImGui::CollapsingHeader("高级设置")) {
    ImGui::Text("折叠内容");
}

// 树节点
if (ImGui::TreeNode("节点1")) {
    ImGui::Text("子内容");
    ImGui::TreePop();
}

6.4 表格

cpp 复制代码
if (ImGui::BeginTable("table", 3, ImGuiTableFlags_Borders)) {
    ImGui::TableSetupColumn("名称");
    ImGui::TableSetupColumn("类型");
    ImGui::TableSetupColumn("值");
    ImGui::TableHeadersRow();

    for (int row = 0; row < 10; row++) {
        ImGui::TableNextRow();
        ImGui::TableSetColumnIndex(0);
        ImGui::Text("item_%d", row);
        ImGui::TableSetColumnIndex(1);
        ImGui::Text("float");
        ImGui::TableSetColumnIndex(2);
        ImGui::Text("%.2f", row * 0.5f);
    }
    ImGui::EndTable();
}

6.5 绘图 API(ImDrawList)

cpp 复制代码
ImDrawList* draw = ImGui::GetWindowDrawList();
ImVec2 p = ImGui::GetCursorScreenPos();

// 画线
draw->AddLine(p, ImVec2(p.x+100, p.y), IM_COL32(255,0,0,255), 2.0f);

// 画矩形
draw->AddRect(ImVec2(p.x, p.y+20), ImVec2(p.x+100, p.y+60),
              IM_COL32(0,255,0,255), 5.0f);

// 画圆
draw->AddCircle(ImVec2(p.x+50, p.y+100), 30.0f,
                IM_COL32(0,0,255,255), 32, 2.0f);

// 画文本
draw->AddText(ImVec2(p.x, p.y+150), IM_COL32_WHITE, "Hello DrawList!");

// 画图片
draw->AddImage((ImTextureID)texture_id,
               ImVec2(p.x, p.y), ImVec2(p.x+100, p.y+100));

7. Docking + Viewport 多窗口系统

7.1 启用 Docking

cpp 复制代码
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;

启停靠后,窗口可以拖拽到任意位置停靠、合并为 Tab 页、分栏排列。

7.2 Docking 布局初始化

cpp 复制代码
ImGui::DockSpaceOverViewport(ImGui::GetMainViewport());

配合 ImGuiDockNodeFlags_PassthruCentralNode 可创建类似 Unity/Unreal 的编辑器布局。

7.3 Viewport 多窗口

cpp 复制代码
io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable;

启用后,ImGui 窗口可以拖出主窗口成为独立 OS 窗口。需要 Platform 后端支持(GLFW/SDL 原生支持)。每个窗口有自己的 OS 标题栏、任务栏入口。

7.4 持久化布局

cpp 复制代码
// 保存
ImGui::SaveIniSettingsToDisk("layout.ini");

// 加载
ImGui::LoadIniSettingsFromDisk("layout.ini");

布局文件为 INI 格式,包含窗口位置、大小、停靠状态、折叠状态等。


8. 自定义控件与绘图

8.1 自定义控件示例:颜色条

cpp 复制代码
void ColorBar(const char* id, float* values, int count, float min_v, float max_v) {
    ImGui::PushID(id);
    ImDrawList* draw = ImGui::GetWindowDrawList();
    ImVec2 pos = ImGui::GetCursorScreenPos();
    float width = ImGui::GetContentRegionAvail().x;
    float bar_height = 20.0f;

    // 绘制背景
    draw->AddRectFilled(pos, ImVec2(pos.x + width, pos.y + bar_height),
                        IM_COL32(30, 30, 30, 255));

    // 绘制每个色块
    float seg_w = width / count;
    for (int i = 0; i < count; i++) {
        float t = (values[i] - min_v) / (max_v - min_v);
        ImU32 color = ImGui::GetColorU32(ImVec4(t, 0.2f, 1.0f - t, 1.0f));
        draw->AddRectFilled(
            ImVec2(pos.x + i * seg_w, pos.y),
            ImVec2(pos.x + (i + 1) * seg_w, pos.y + bar_height),
            color
        );
    }

    ImGui::Dummy(ImVec2(width, bar_height));
    ImGui::PopID();
}

8.2 自定义控件示例:可拖拽节点

cpp 复制代码
struct DraggableNode {
    ImVec2 pos;
    ImVec2 size;
    const char* label;
};

bool DraggableNode(DraggableNode& node) {
    ImDrawList* draw = ImGui::GetWindowDrawList();
    ImVec2 cursor = ImGui::GetCursorScreenPos();

    // 绘制节点背景
    ImRect rect(node.pos, ImVec2(node.pos.x + node.size.x, node.pos.y + node.size.y));
    draw->AddRectFilled(rect.Min, rect.Max, IM_COL32(60, 60, 80, 255), 4.0f);
    draw->AddRect(rect.Min, rect.Max, IM_COL32(100, 100, 200, 255), 4.0f);

    // 绘制文本
    draw->AddText(ImVec2(rect.Min.x + 5, rect.Min.y + 5), IM_COL32_WHITE, node.label);

    // 拖拽交互
    ImGui::SetCursorScreenPos(rect.Min);
    ImGui::InvisibleButton(node.label, node.size);
    if (ImGui::IsItemActive() && ImGui::IsMouseDragging(0)) {
        node.pos.x += ImGui::GetIO().MouseDelta.x;
        node.pos.y += ImGui::GetIO().MouseDelta.y;
        return true;
    }
    return false;
}

9. 性能优化指南

9.1 核心性能指标

ImGui 的帧开销主要来自三个方面:

指标 含义 优化目标
顶点数 每帧生成的三角形顶点 控制 < 500k
Draw Call 数 每帧提交的绘制批次 控制 < 200
控件数 每帧声明的控件数量 控制 < 10k

9.2 优化技巧

1. 使用 ImGuiListClipper 虚拟化列表

cpp 复制代码
// 不要这样
for (int i = 0; i < 100000; i++) {
    ImGui::Text("item %d", i);
}

// 要这样
ImGuiListClipper clipper;
clipper.Begin(100000);
while (clipper.Step()) {
    for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) {
        ImGui::Text("item %d", i);
    }
}

2. 启用 ImDrawList 合并

cpp 复制代码
io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange; // 减少字体纹理切换

3. 避免每帧重新创建大量字符串

cpp 复制代码
// 差
for (int i = 0; i < items.size(); i++) {
    ImGui::Text("Item %d: %s", i, items[i].c_str());
}

// 好:使用 PushID + 预格式化
static char buf[256];
for (int i = 0; i < items.size(); i++) {
    ImGui::PushID(i);
    snprintf(buf, sizeof(buf), "Item %d: %s", i, items[i].c_str());
    ImGui::TextUnformatted(buf);
    ImGui::PopID();
}

4. 使用 Begin 的 open 参数控制窗口创建

cpp 复制代码
static bool show_advanced = false;
if (show_advanced) {
    ImGui::Begin("Advanced", &show_advanced);
    // 大量控件
    ImGui::End();
}

5. 减少纹理切换

将多个小图标打包到一张图集中,使用 AddImage 时指定 UV 坐标。

9.3 性能分析工具

cpp 复制代码
// 内置性能监控
ImGui::ShowMetricsWindow();       // 显示控件/顶点/DrawCall 统计
ImGui::ShowDebugLogWindow();      // 调试日志
ImGui::ShowStyleEditor();         // 样式编辑器
ImGui::ShowAboutWindow();         // 版本信息

10. 与 Qt/WPF/Retained Mode 对比

对比维度 Dear ImGui Qt 6 WPF (.NET)
模式 即时模式 保留模式 保留模式
布局方式 声明式(ImGui::Begin/End) QML / 布局管理器 XAML + 布局面板
事件模型 每帧轮询 信号/槽 + 事件循环 路由事件 + 命令
自定义控件难度 低(直接 DrawList 绘图) 中(QPainter 或子类化) 中(ControlTemplate)
数据绑定 手动同步 QProperty/QML 绑定 INotifyPropertyChanged
多语言支持 无内建,需手动处理 UTF-8 完备(Qt Linguist) 完备(RESX + 本地化)
无障碍支持 完备(QAccessible) 完备(UIAutomation)
动画系统 无(手动帧间 lerp) QPropertyAnimation Storyboard + DoubleAnimation
外部依赖 0(仅后端依赖图形 API) 大量(QtCore/QtGui/QtWidgets) .NET Framework
编译时间 < 1s 数分钟 N/A
二进制体积 ~500KB ~20MB+ 数十 MB
适用场景 工具/调试/游戏内嵌 桌面应用/嵌入式 Windows 企业应用

11. FAQ 速查表

Q: ImGui 能做正式产品 UI 吗?

A : 可以,但不推荐。ImGui 的设计目标就是工具和调试,不是面向最终消费者的精美 UI。缺乏无障碍、国际化、主题切换、复杂动画等成熟框架的标准能力。但许多商业产品(Unity Editor、Unreal Editor、RenderDoc、Blender 插件)确实使用 ImGui 做内部工具面板。

Q: ImGui 支持中文吗?

A: 支持。ImGui 使用 UTF-8 编码,只需确保字体文件包含中文字形。在 ImFontAtlas 中加载中文字体即可:

cpp 复制代码
ImGuiIO& io = ImGui::GetIO();
io.Fonts->AddFontFromFileTTF("C:/Windows/Fonts/msyh.ttc", 16.0f, nullptr,
    io.Fonts->GetGlyphRangesChineseFull());

Q: ImGui 支持触摸交互吗?

A: 有限支持。可通过 io.MouseDown 和 io.MousePos 模拟触摸事件,但无原生多点触控、手势识别。嵌入式场景可通过 ImGuiNavInput_ 映射实现触摸导航。

Q: ImGui 线程安全吗?

A : 。ImGui 的所有 API 必须在主渲染线程中调用。如果需要多线程,只能在一个线程中调用 ImGui,其他线程通过队列传递数据。

Q: ImGui 的字体纹理太大怎么办?

A: 可以使用 ImFontAtlas::AddFontFromFileTTF 的 size_pixels 参数控制字号,或使用 GlyphRanges 限制字符集(如只加载 ASCII 或常用中文字符)。

Q: ImGui 的 Draw Call 太多怎么办?

A: 1) 启用 ImGuiConfigFlags_NoMouseCursorChange 减少纹理切换;2) 使用 ImDrawList::ChannelsSplit 分层合并;3) 减少 Alpha 渐变(透明区域会打断批次合并)。

Q: 如何让 ImGui 窗口透明?

cpp 复制代码
ImGui::SetNextWindowBgAlpha(0.5f);  // 半透明背景
ImGui::Begin("Transparent Window");
// ...
ImGui::End();

Q: ImGui 的字体模糊怎么办?

A: 1) 确保 ImGui::GetIO().FontGlobalScale 匹配 DPI 缩放;2) 使用 ImGui::GetStyle().ScaleAllSizes(scale) 缩放控件尺寸;3) 在 HiDPI 屏幕上,让后端使用高分辨率帧缓冲,ImGui 自动适配。


总结 :Dear ImGui 的核心价值在于极低的集成成本和极高的迭代速度 。它不是 Qt 的替代品,而是在"需要快速出一个可交互的工具面板"这个场景下最趁手的工具。使用 ImGui 的正确姿势是:把它当作可嵌入的调试控制台,而不是桌面应用框架

相关推荐
网安小学生(兼顾数据库版)1 小时前
Mend SCA 实操指南:使用 Mend CLI 进行开源组件安全扫描
安全·开源
ShineWinsu2 小时前
对于TRAE中配置Qt的解析
开发语言·c++·ide·vscode·qt·ai·trae
xy34532 小时前
Axure9.0让条形图实现动态交互(鼠标悬停高亮 + 显示详情卡片)
ui·产品经理·原型·axure9.0
小小龙学IT2 小时前
第一节 QML 背景介绍
c++·qt·js
水饺编程2 小时前
第5章,[Win32 章节] :创建、选择和删除画笔
c语言·c++·windows·visual studio
wind100322 小时前
Outdated Visual C++ Redistributable 过时 VC++ 运行库报错修复
开发语言·c++
小小龙学IT2 小时前
Abseil 开源 C++ 公共库深度解析:从 SwissTable 到 Status 的 Google 工程实践
c++·开源
兔兔兔兔13 小时前
记录C++ 12
开发语言·c++
此陆昭昭3 小时前
qt,使用qml写一个简单的界面
c++·qt