Dear ImGui五分钟集成指南:C++轻量级即时模式GUI开发实践
2026/7/24 5:34:35 网站建设 项目流程

1. 项目概述:为什么选择Dear ImGui?

如果你是一个C++开发者,正在为你的命令行工具、游戏引擎、仿真程序或者任何需要用户交互的后台服务,寻找一个快速、轻量且不折腾的图形用户界面(GUI)方案,那么Dear ImGui几乎就是为你量身定做的。我第一次接触它,是在为一个实时数据处理的后台服务添加一个简单的监控面板时。当时的需求很明确:需要一个能实时显示数据曲线、几个按钮控制启停、几个滑块调整参数的界面,并且这个界面必须能无缝嵌入到现有的C++项目中,不能引入复杂的依赖,更不能影响主循环的性能。

传统的GUI框架,比如Qt、wxWidgets,功能强大但“太重”了。它们有自己的事件循环、资源管理体系和庞大的运行时库,集成过程往往意味着项目结构的重构和漫长的编译时间。而Dear ImGui,中文常被亲切地称为“亲爱的ImGui”或直接叫ImGui,它走的是另一条路:即时模式(Immediate Mode)GUI。

这听起来有点抽象,我用一个简单的类比来解释。传统GUI(保留模式)就像是你请了一个管家(GUI框架),你告诉他:“这里放个按钮,那里放个文本框。” 然后管家会记住这些控件的状态、位置、样式,并负责处理所有交互。你需要通过回调函数或信号槽机制来响应事件。而即时模式GUI,更像是你自己在每一帧都亲手重新绘制整个界面。你直接调用代码:“在这里画一个按钮”,如果按钮被点击了,函数会立刻返回true,你紧接着处理点击逻辑。画完这一帧,所有控件的状态就“忘记”了,下一帧再重新画。

这种模式带来了几个颠覆性的优势:

  1. 极简集成:核心库只有几个头文件,没有复杂的构建系统。你只需要提供图形API(如OpenGL, DirectX, Vulkan)的绑定和一个窗口创建库(如GLFW, SDL),就能跑起来。
  2. 与程序逻辑深度耦合:你的界面状态(比如滑块的值、复选框的勾选状态)可以直接用你的程序变量来表示,无需额外的数据绑定层。代码写起来直观得像在写控制台打印语句。
  3. 高性能:由于每帧重绘,它天然适合需要高频更新的实时应用,如游戏调试工具、音视频编辑软件、科学可视化。
  4. 原型速度极快:添加一个控件、调整布局几乎就是一两行代码的事,迭代速度远超传统框架。

所以,当你的C++项目需要一个轻量、可定制、用于调试、配置或简单交互的“专业级”界面,而不是一个功能齐全的桌面应用时,Dear ImGui就是那把“瑞士军刀”。接下来,我会带你从零开始,在5分钟的核心流程内,将它集成到你的项目中,并深入拆解如何打造一个真正专业、实用的界面。

2. 核心思路与五分钟快速集成

“5分钟”不是一个夸张的营销术语,而是基于ImGui极简哲学的真实写照。这五分钟的目标是:在你的原生C++项目中,打开一个窗口,并显示一个可交互的ImGui界面。我们以最通用的跨平台方案为例:使用GLFW处理窗口和输入,使用OpenGL 3作为图形后端。

2.1 环境准备与依赖获取

首先,你需要准备两样东西:

  1. Dear ImGui 本体:直接从GitHub仓库(https://github.com/ocornut/imgui)下载最新版本。你只需要imconfig.h,imgui.h,imgui.cpp,imgui_draw.cpp,imgui_tables.cpp,imgui_widgets.cpp这几个核心文件,以及backends目录下的后端文件。
  2. GLFW库:用于创建窗口和处理键盘鼠标输入。可以从其官网(https://www.glfw.org/)下载预编译库或源码。

一个典型的项目目录结构会是这样:

你的项目/ ├── CMakeLists.txt ├── src/ │ ├── main.cpp │ └── ... ├── libs/ │ ├── imgui/ │ │ ├── imgui.h, .cpp 等核心文件 │ │ └── backends/ │ │ ├── imgui_impl_glfw.h, .cpp │ │ └── imgui_impl_opengl3.h, .cpp │ └── glfw/ (或通过包管理器安装)

如果你使用CMake,集成变得非常简单。在你的CMakeLists.txt中,关键步骤是添加ImGui源码并链接:

# 假设imgui库放在libs/imgui目录下 add_subdirectory(libs/imgui) # 或者直接添加源文件 file(GLOB IMGUI_SOURCES "libs/imgui/*.cpp" "libs/imgui/backends/imgui_impl_glfw.cpp" "libs/imgui/backends/imgui_impl_opengl3.cpp" ) add_executable(YourProject src/main.cpp ${IMGUI_SOURCES}) # 包含头文件目录 target_include_directories(YourProject PRIVATE libs/imgui libs/imgui/backends) # 链接库 target_link_libraries(YourProject PRIVATE imgui glfw opengl32)

注意:ImGui的后端绑定文件(如imgui_impl_glfw.cpp)必须和你的图形API及窗口库匹配。如果你用SDL2和DirectX11,就需要对应的imgui_impl_sdl2.cppimgui_impl_dx11.cpp。选错后端会导致编译错误或运行时崩溃。

2.2 五分钟集成代码解析

下面这段代码浓缩了集成的所有核心步骤。请将其放入你的main.cpp,并确保包含路径和链接库正确。

#include <stdio.h> #include "imgui.h" #include "imgui_impl_glfw.h" #include "imgui_impl_opengl3.h" #include <GLFW/glfw3.h> // 确保在OpenGL头文件之前包含GLFW int main() { // 1. 初始化GLFW窗口 if (!glfwInit()) return -1; const char* glsl_version = "#version 130"; // 根据你的OpenGL上下文版本调整 GLFWwindow* window = glfwCreateWindow(1280, 720, "我的ImGui应用", NULL, NULL); if (!window) { glfwTerminate(); return -1; } glfwMakeContextCurrent(window); glfwSwapInterval(1); // 开启垂直同步 // 2. 初始化Dear ImGui上下文 IMGUI_CHECKVERSION(); ImGui::CreateContext(); ImGuiIO& io = ImGui::GetIO(); (void)io; io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // 启用键盘控制 // 设置样式(可选,但能让界面立刻专业起来) ImGui::StyleColorsDark(); // 经典深色主题 // 3. 初始化ImGui的后端(GLFW + OpenGL3) ImGui_ImplGlfw_InitForOpenGL(window, true); ImGui_ImplOpenGL3_Init(glsl_version); // 程序主循环 while (!glfwWindowShouldClose(window)) { // 处理系统事件(输入等) glfwPollEvents(); // 开始新一帧的ImGui绘制 ImGui_ImplOpenGL3_NewFrame(); ImGui_ImplGlfw_NewFrame(); ImGui::NewFrame(); // 4. 在这里构建你的GUI界面(核心!) { // 创建一个名为“控制面板”的窗口 ImGui::Begin("控制面板"); // 静态文本 ImGui::Text("你好,世界!"); // 一个按钮 if (ImGui::Button("点击我")) { // 按钮被点击后的逻辑 printf("按钮被点击了!\n"); } // 一个滑块,直接绑定到一个浮点变量 static float volume = 0.5f; ImGui::SliderFloat("音量", &volume, 0.0f, 1.0f); // 结束这个窗口 ImGui::End(); } // 5. 渲染 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); // 设置清屏颜色(ImGui经典背景色) glClear(GL_COLOR_BUFFER_BIT); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); // 实际绘制 glfwSwapBuffers(window); // 交换前后缓冲区 } // 6. 清理资源 ImGui_ImplOpenGL3_Shutdown(); ImGui_ImplGlfw_Shutdown(); ImGui::DestroyContext(); glfwDestroyWindow(window); glfwTerminate(); return 0; }

编译并运行这段代码,你应该能看到一个带有深色主题的窗口,里面有一个文本标签、一个按钮和一个滑块。拖动滑块,volume变量的值会实时改变;点击按钮,控制台会输出信息。至此,核心集成在5分钟内完成。

实操心得:第一次集成时,最常见的错误是glsl_version字符串与你的OpenGL上下文不匹配。如果你使用的是较新的OpenGL核心模式(比如通过GLAD或GLEW加载的4.6),这里可能需要#version 460。如果运行时界面一片空白或闪烁,首先检查这个版本字符串,其次检查glViewportglClear调用是否正确执行。

3. 从“能用”到“专业”:界面设计与控件深度应用

一个能弹出的窗口只是开始。一个“专业”的GUI界面,意味着清晰的布局、直观的交互、高效的数据展示和舒适的视觉体验。ImGui提供了丰富的控件和灵活的布局系统来实现这些。

3.1 布局管理:窗口、分组与排版

ImGui没有复杂的绝对坐标布局管理器,它使用一种基于“游标”的相对布局模式。控件会按照你调用的顺序依次排列。但通过一些简单的函数,你可以实现复杂的布局。

窗口(Window):所有控件的容器。ImGui::Begin()ImGui::End()创建一个窗口。你可以控制其大小、位置、是否可拖动、是否有滚动条等。

// 创建一个固定大小、不可调整、带菜单栏的窗口 ImGui::SetNextWindowSize(ImVec2(400, 300), ImGuiCond_FirstUseEver); if (ImGui::Begin("配置窗口", nullptr, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_MenuBar)) { // ... 控件放在这里 } ImGui::End();

子窗口与子区域(Child):用于在窗口内创建独立的滚动或裁剪区域,非常适合设置面板或列表。

ImGui::Begin("主窗口"); if (ImGui::BeginChild("左侧面板", ImVec2(200, 0), true)) { // true表示带边框 ImGui::Text("这里是导航栏"); ImGui::Button("项目A"); ImGui::Button("项目B"); } ImGui::EndChild(); ImGui::SameLine(); // 让下一个控件在同一行开始 if (ImGui::BeginChild("右侧内容区")) { ImGui::Text("这里是详细内容"); } ImGui::EndChild(); ImGui::End();

分组(Group)ImGui::BeginGroup()ImGui::EndGroup()可以将一系列控件打包成一个逻辑组,方便整体管理间距和对齐。排版助手ImGui::SameLine()让下一个控件紧跟上一个;ImGui::Spacing(),ImGui::Separator()用于添加间距和分隔线;ImGui::Indent()/Unindent()用于缩进。

3.2 核心控件详解与数据绑定

ImGui的控件函数通常直接与你程序中的变量绑定,这是即时模式最强大的特性之一。

输入类控件

  • ImGui::InputText(“标签”, char_buffer, buffer_size):文本输入。注意:要确保缓冲区足够大,并考虑使用ImGuiInputTextFlags_CallbackResize标志或ImGui::InputTextWithHint等更安全的变体。
  • ImGui::InputInt,ImGui::InputFloat:数字输入。可以设置步长和格式。
  • ImGui::SliderInt,ImGui::SliderFloat:滑块。绑定变量,直观调整数值范围。
  • ImGui::DragInt,ImGui::DragFloat:拖拽控件。比滑块更紧凑,适合快速调整。
  • ImGui::ColorEdit3,ImGui::ColorPicker3:颜色选择。

交互类控件

  • ImGui::Button:按钮。返回bool表示是否被点击。可以设置大小ImVec2
  • ImGui::Checkbox:复选框。绑定bool变量。
  • ImGui::RadioButton:单选按钮。需要手动管理选中状态。
  • ImGui::Combo/ImGui::ListBox:下拉列表和列表框。需要传递一个字符串数组和当前选中索引。

显示类控件

  • ImGui::Text/ImGui::TextColored/ImGui::TextWrapped:文本显示。
  • ImGui::BulletText:带圆点的文本。
  • ImGui::ProgressBar:进度条。
  • ImGui::PlotLines/ImGui::PlotHistogram这是ImGui的杀手锏之一,用于实时绘制数据曲线,对于监控、调试数据流无比方便。
// 实时绘制正弦波示例 static std::vector<float> values; static float t = 0.0f; t += 0.01f; values.push_back(sin(t)); if (values.size() > 100) values.erase(values.begin()); ImGui::PlotLines("实时波形", values.data(), values.size(), 0, nullptr, -1.0f, 1.0f, ImVec2(0, 80.0f));

3.3 样式定制:打造独特视觉风格

默认的深色或浅色主题已经很不错,但专业的产品往往需要品牌化的UI。ImGui的样式系统非常灵活。

全局样式(ImGuiStyle):通过ImGui::GetStyle()获取一个ImGuiStyle对象的引用,你可以修改几乎所有视觉属性:颜色、间距、圆角、边框大小等。

ImGuiStyle& style = ImGui::GetStyle(); style.WindowRounding = 5.0f; // 窗口圆角 style.FrameRounding = 3.0f; // 按钮等控件圆角 style.Colors[ImGuiCol_Button] = ImVec4(0.26f, 0.59f, 0.98f, 0.40f); // 按钮颜色 style.Colors[ImGuiCol_ButtonHovered] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); style.Colors[ImGuiCol_ButtonActive] = ImVec4(0.06f, 0.53f, 0.98f, 1.00f);

字体:ImGui支持加载TTF/OTF字体,这是提升界面质感的关键一步。

ImGuiIO& io = ImGui::GetIO(); io.Fonts->AddFontFromFileTTF("fonts/Roboto-Medium.ttf", 16.0f); // 加载字体 // 在加载字体后、第一次渲染前,需要重新上传纹理 ImGui_ImplOpenGL3_CreateFontsTexture();

注意事项:修改样式和字体通常在初始化阶段完成。动态修改样式是允许的,但可能会引起性能开销。对于颜色主题,网上有很多现成的配色方案(如“Material Design”、“Corporate Grey”),可以直接复制Colors数组的值,快速切换主题。

4. 高级特性与性能优化实战

当你的界面变得复杂,包含大量控件或需要渲染复杂图形时,就需要关注性能和高级功能了。

4.1 窗口停靠与多视口

这是ImGui迈向“专业IDE级”界面的重要功能。

  • 停靠(Docking):允许窗口像Visual Studio或Blender那样相互停靠、标签化、分割。启用它需要在初始化时设置标志:io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;。之后,你可以在主窗口上调用ImGui::DockSpace()来创建一个停靠空间,其他带有ImGuiWindowFlags_ChildWindow标志的窗口就可以在其中停靠了。
  • 多视口(Multi-Viewport):允许ImGui窗口脱离主窗口,成为原生系统窗口。启用标志:io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable;。启用后,拖拽窗口标签栏可以将其拖出成为独立窗口。注意:这需要后端支持(如GLFW+OpenGL后端需要额外处理平台窗口的创建和渲染)。

4.2 列表与表格的优化渲染

当需要显示成百上千行数据时,直接使用循环创建ImGui::Text会导致严重的性能问题,因为每一行都是一个独立的控件,即使不可见也会被处理。

使用ImGuiListClipper:这是一个虚拟滚动的神器。它自动计算哪些行是可见的,只渲染这些行。

ImGui::Begin("大数据列表"); ImGuiListClipper clipper; clipper.Begin(10000); // 我们有一万行数据 while (clipper.Step()) { for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) { ImGui::Text("行 %d: 一些数据", i); } } ImGui::End();

使用ImGui::BeginTable/ImGui::EndTable:对于表格数据,这是比手动排版SameLine更强大、性能更好的选择。它支持排序、冻结行列、上下文菜单等。

if (ImGui::BeginTable("数据表", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { // 表头 ImGui::TableSetupColumn("姓名"); ImGui::TableSetupColumn("年龄"); ImGui::TableSetupColumn("部门"); ImGui::TableHeadersRow(); // 数据行 for (int row = 0; row < 50; row++) { ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); ImGui::Text("张三 %d", row); ImGui::TableSetColumnIndex(1); ImGui::Text("%d", 20 + row); ImGui::TableSetColumnIndex(2); ImGui::Text("技术部"); } ImGui::EndTable(); }

4.3 自定义绘制与集成渲染

ImGui不仅可以绘制UI,你还可以在ImGui窗口内绘制你自己的几何图形、纹理甚至3D场景。

使用ImDrawListAPI:每个窗口都有一个ImDrawList(绘制命令列表)。你可以直接向其中添加原始的2D绘制命令,如线段、矩形、圆、多边形、文字和纹理四边形。

ImGui::Begin("自定义绘制窗口"); ImDrawList* draw_list = ImGui::GetWindowDrawList(); ImVec2 p = ImGui::GetCursorScreenPos(); // 获取窗口左上角的屏幕坐标 draw_list->AddCircleFilled(ImVec2(p.x + 50, p.y + 50), 30.0f, IM_COL32(255, 0, 0, 255), 20); // 画一个红色实心圆 draw_list->AddRect(ImVec2(p.x + 10, p.y + 10), ImVec2(p.x + 90, p.y + 90), IM_COL32(0, 255, 0, 255)); // 画一个绿色矩形 ImGui::End();

渲染纹理:如果你有一个OpenGL纹理ID,可以将其转换为ImGui的ImTextureID进行显示。

// 假设你有一个OpenGL纹理,其ID存储在 `my_texture_id` (GLuint) 中 ImGui::Begin("纹理查看"); ImGui::Image((void*)(intptr_t)my_texture_id, ImVec2(my_texture_width, my_texture_height)); ImGui::End();

性能优化心得

  1. 避免每帧创建/销毁字符串ImGui::Text(“FPS: %d”, fps)会临时构造字符串。对于频繁更新的文本,考虑使用静态缓冲区或ImGui::TextUnformatted
  2. 慎用ImGui::GetIO().Framerate:这个调用本身有一定开销,在发布版本中可以考虑自己计算帧率。
  3. 利用ImGui::BeginChild的裁剪:将复杂但不常变化的部分放在Child窗口里,可以利用裁剪优化。
  4. 状态去重:ImGui内部会做一定优化,但如果你自己管理着大量状态(如展开/折叠的树节点),确保只在状态改变时更新相关UI部分。

5. 工程化实践:架构、调试与发布

将ImGui用于实际项目,而不仅仅是Demo,需要考虑代码组织、状态管理和发布部署。

5.1 界面逻辑与业务逻辑分离

虽然ImGui鼓励将UI状态和程序状态绑定,但为了代码清晰,建议进行一定分离。一个常见的模式是:为每个主要的UI窗口或面板创建一个类或命名空间

// UIPanel_SystemMonitor.h class UIPanel_SystemMonitor { public: void Render(); // 负责渲染该面板的所有ImGui控件 void UpdateData(double delta_time); // 从业务逻辑更新数据 private: bool m_show_cpu_graph = true; float m_cpu_usage = 0.0f; std::vector<float> m_fps_history; // ... 其他UI状态 }; // main.cpp 中 UIPanel_SystemMonitor sys_monitor_panel; UIPanel_Configuration config_panel; while (!glfwWindowShouldClose(window)) { // ... 帧开始 sys_monitor_panel.UpdateData(delta_time); ImGui::NewFrame(); sys_monitor_panel.Render(); config_panel.Render(); // ... 帧结束 }

这样,你的main.cpp保持简洁,每个UI模块的代码和内聚状态被封装在一起,易于维护和测试。

5.2 状态持久化(保存/加载布局)

用户调整了窗口位置、折叠了某些面板,下次启动时希望能恢复。ImGui提供了ImGui::SaveIniSettingsToMemory()ImGui::LoadIniSettingsFromMemory()函数,可以将所有窗口位置、大小、折叠状态等保存到一个字符串中。你可以将这个字符串轻松地保存到磁盘文件或注册表中。

// 程序退出时保存 ImGui::SaveIniSettingsToDisk("imgui.ini"); // 最简单,直接存文件 // 或者获取内存数据自己处理 size_t ini_size = 0; const char* ini_data = ImGui::SaveIniSettingsToMemory(&ini_size); // 将 ini_data 保存到你的配置系统... // 程序启动时加载 ImGui::LoadIniSettingsFromDisk("imgui.ini"); // 或者从内存加载 // ImGui::LoadIniSettingsFromMemory(ini_data_loaded_from_file, ini_size);

5.3 调试与问题排查

ImGui自带一个强大的Metrics/Debugger窗口Style Editor窗口。在初始化后,你可以在代码中通过一个复选框来开关它们。

static bool show_debug_window = false; if (ImGui::BeginMainMenuBar()) { if (ImGui::BeginMenu("调试")) { ImGui::MenuItem("显示ImGui调试窗口", NULL, &show_debug_window); ImGui::EndMenu(); } ImGui::EndMainMenuBar(); } if (show_debug_window) { ImGui::ShowMetricsWindow(&show_debug_window); // ImGui::ShowStyleEditor(); // 也可以打开样式编辑器 }

Metrics窗口会显示绘制调用次数、顶点数、窗口列表、输入状态等极其详细的信息,是性能分析和UI问题排查的终极工具。

5.4 发布构建与依赖管理

对于发布版本,你希望获得最小的二进制体积和最佳性能。

  • 编译优化:确保在Release模式下编译,并开启链接时优化(LTO)。
  • 单头文件模式:ImGui官方提供了一个single header版本(imconfig.h,imgui.h,imgui.cpp合并为一个imgui_single_file.h),虽然不推荐日常开发使用,但对于简化发布包的依赖有一定好处。
  • 静态链接:将ImGui和其后端实现静态链接到你的最终可执行文件中,避免携带额外的DLL。
  • 字体处理:如果你加载了自定义字体,发布时需要将字体文件(.ttf)一同打包,或者考虑将字体数据编译进资源中。可以使用ImGui::GetIO().Fonts->AddFontFromMemoryTTF()从内存加载字体数据。

6. 常见问题与避坑指南

在实际使用中,你肯定会遇到一些“坑”。这里记录了一些最常见的问题和解决方案。

问题1:界面闪烁或部分不显示。

  • 原因A:图形API上下文问题。确保ImGui的渲染调用(ImGui_ImplOpenGL3_RenderDrawData)发生在正确的OpenGL/DirectX上下文中,并且在每帧只调用一次。
  • 原因B:清屏颜色与ImGui背景色冲突。确保在ImGui渲染之前清屏。
  • 原因C:视口(Viewport)设置不正确。窗口大小改变后,必须调用glViewport更新渲染尺寸。使用glfwGetFramebufferSize而不是glfwGetWindowSize来获取正确的像素尺寸。

问题2:输入(鼠标、键盘)无响应。

  • 排查:首先检查是否在后端初始化时正确传递了install_callbacks参数(ImGui_ImplGlfw_InitForOpenGL(window, true)中的true)。如果设置为false,你需要手动将GLFW的回调函数转发给ImGui。
  • 检查:确认你的主循环中正确调用了ImGui_ImplGlfw_NewFrame()

问题3:中文或其他非ASCII字符显示为乱码。

  • 解决方案:加载支持中文的字体文件(如思源黑体、文泉驿等),并在加载时指定中文字符范围。
ImGuiIO& io = ImGui::GetIO(); ImFontConfig config; config.OversampleH = 2; // 水平过采样,改善小字号显示 config.OversampleV = 1; // 垂直过采样 io.Fonts->AddFontFromFileTTF("fonts/simhei.ttf", 18.0f, &config, io.Fonts->GetGlyphRangesChineseFull());

问题4:在复杂的多线程环境中使用ImGui。

  • 核心原则ImGui不是线程安全的。所有ImGui的API调用必须发生在同一个线程,通常是主线程。你可以从其他线程准备数据,但必须在主线程的渲染循环中调用ImGui::NewFrame()ImGui::Render()之间的代码来提交UI。使用线程安全的队列或标志来传递数据。

问题5:自定义控件或复杂交互。

  • 方法:ImGui的设计使得创建自定义控件相对容易。你可以组合现有控件,或者直接使用ImDrawListAPI从头绘制。研究imgui_widgets.cpp源码中标准控件的实现(如ButtonEx函数)是最好的学习方式。对于复杂的交互(如拖拽排序列表),社区有很多扩展库(如imgui_tables已内置排序,ImGuiWidgets等第三方库提供了更多高级控件)。

最后,一个我个人非常受用的技巧:充分利用ImGui的“Demo窗口”。在代码中调用ImGui::ShowDemoWindow(),它会展示几乎所有控件、布局和特性的用法示例,并且是交互式的。这不仅是学习工具,在开发过程中也是一个随时可查的“说明书”,能极大地提升开发效率。当你不知道某个效果如何实现时,先看看Demo窗口的代码,往往能找到答案。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询