早两个月我一直在折腾一个 C++ 桌面端小项目,逻辑层倒还好说,真正烦的是 UI 部分——传统的 Win32 手写消息循环写得人犯困,Qt 又嫌工程太大。后来偶然把 nim_duilib 和 AI 编程工具 TRAE 搭在一起用,突然发现这组合居然意外地顺手:AI 负责把骨架、布局、事件绑定这些模板活儿一口气写完,我只需要盯住设计细节和业务逻辑。这篇就把我这两周的真实操作过程、踩过的坑、以及怎么让 AI“少写废代码”的方法完整记录下来,给也想用 AI 写 C++ 桌面 UI 的朋友做个参考。
这个方案适合谁?适合会用 C++ 但不想在 UI 样板代码上耗时间的人,也适合对 AI 编程有兴趣、想试 TRAE 但不知道该怎么提需求的人。看完你应该能掌握一套从环境搭建、AI 生成代码到排错上线的完整流程。
1. 为什么是 TRAE + nim_duilib:这个组合解决的核心问题
先说结论:TRAE 和 nim_duilib 不是偶然凑在一起,而是刚好卡在“AI 擅长什么”和“C++ UI 需要什么”的交叉点上。
1.1 TRAE 到底能帮 C++ 桌面开发做到什么程度
很多人对 AI 编程工具的认知还停留在“对话框补全代码”,实际用下来完全不是一回事。TRAE 真正有价值的两个点:一是 Agent 模式下它能跨文件改代码,二是它能自己执行终端命令。
什么叫跨文件改代码?你给我提一个“把登录窗口加进去”的需求,它不会只给你一段代码让你自己复制,而是会自己去找 CMakeLists.txt、去看看你的工程结构、创建新的头文件和 cpp、再把入口处改好。这种“项目级”操作,和以前那种隔着聊天窗口给代码片段完全是两种体验。
更关键的是终端操作。C++ 工程的编译、CMake 配置、依赖下载,这些以前都是人工干的活。TRAE Agent 模式被允许执行命令以后,它能自己跑 CMake、自己编译、看到报错还会尝试修复。我在实操中经常干的一件事就是:让 TRAE 编译,然后根据编译日志自己迭代修改。这个闭环一旦跑起来,效率提升非常明显。
但要冷静一点。C++ 这门语言的复杂程度决定了 AI 不可能像写 Python 一样信手拈来。模板、指针、生命周期、虚函数重写、平台 API……这些概念摞在一起,AI 很容易在细节上出错。所以我对 TRAE 的定位是“高配合度的工程助手”,不是“全自动程序员”。
1.2 nim_duilib 的定位:声明式 UI 恰好命中 AI 的强项
再说 nim_duilib。它是经典 duilib 库的一个活跃维护分支,核心特色是 UI 描述走 XML,业务逻辑走 C++,界面和代码分离。
这套设计对 AI 特别友好,原因其实很简单——XML 布局和写 HTML 差不多,是树状的、声明式的、结构化很强的数据。AI 最擅长的恰恰就是这种有规律的标记语言。你让 AI 手写一个 Win32 窗口的 WndProc 消息分发,它容易搞混消息参数;但你让它写一段 XML 布局,把几个按钮和输入框排进去,它的准确率能到 90% 以上。
而 C++ 部分,duilib 的事件回调模型也足够简单直接,不像 MFC 那套消息映射表绕来绕去。窗口类继承WindowImplBase,重写OnClick处理点击,UI 控件名对得上就能响应。对 AI 来说,这种“控件 name 等于事件入口”的模型很好理解,不太容易跑偏。
1.3 我为什么没有选 Qt / Win32 / C# 的组合
很多人会问:既然要写桌面 UI,用 Qt 或者干脆用 C# + WPF 不香吗?
Qt 当然好,但它重。moc预处理、信号槽元对象系统、三角形依赖链头文件,这些对 AI 来说都是额外的“陷阱”。AI 生成的 Qt 代码经常出现信号槽不会触发、Q_OBJECT宏位置不对这类问题,排查起来很费劲。至于 Win32 原生 UI,我前面说了,纯手写样板代码太费人,而且视觉表现力全靠自己画,吃力不讨好。
nim_duilib 的优势在“轻”和“直观”。它不需要复杂预处理器,就是一个 C++ 类继承 + XML 布局文件。整个技术栈的宽度非常适合 AI 发挥,出错概率比 Qt 低一截。这也是我这篇文章愿意把它和环境绑在一起的原因——两者搭配,是用最小的心智负担换最大的生产力。
2. 开工前的环境准备:把一半的报错按死在初始化阶段
环境配置是 C++ 项目里最劝退新人的环节,没有之一。我这次按照下面的流程准备,全程没怎么折腾,把常见问题提前规避掉了。
2.1 工具链与运行库:VS 版本和 VC++ Redistributable
编译器我选的是 Visual Studio 2022 的 C++ 桌面开发组件,装的时候记得把“MSVC v143 生成工具”和“Windows 11 SDK”勾上。如果你机器上已经有 VS 2019 也没问题,duilib 对编译器要求不高,但建议统一用 x64 平台,避免后面混着 x86 和 x64 的库打起来。
这里有个和Visual C++ Redistributable相关的事得单独说。很多 C++ 桌面程序在开发机器上跑得好好的,拷到别的电脑上就报“VCRUNTIME140.dll 找不到”或者“无法定位程序输入点”。这是因为目标机器上没有安装对应的 VC++ 运行库。VS2022 对应的是“Microsoft Visual C++ 2015-2022 Redistributable(x64)”。在项目交付阶段,这个运行库要么打包进安装程序,要么在文档里明确告诉用户先装。TRAE 帮你编译出来的程序不会自动替你做这件事,这个责任还是得背着。
2.2 拉取 nim_duilib 源码并建立工程目录
nim_duilib 的源码直接去 GitHub 拉主分支就行。拉下来以后先别急着撒手跑,花十分钟看一下目录结构,因为后面所有跟 AI 的对话都建立在“你知道和它知道用的是同一个工程结构”这个前提下。
一般你会看到几个核心目录和配套工程示例。比如源码目录、示例工程目录、以及若干用于演示的小项目。我个人的经验是:优先参考其中最简单的那个示例工程,先把“一个窗口 + 一个按钮”跑通,再做复杂的。这样后面无论手写还是让 AI 生成,都有了一个“已验证的金标准”。
然后建自己的工程目录时,建议把 nim_duilib 源码和自己写的 UI 皮肤资源分开管理。比如:
MyDesktopApp/ ├─ 3rdparty/ # 第三方库,放 nim_duilib ├─ src/ # 业务代码 ├─ skin/ # XML 布局、图片、字体 └─ CMakeLists.txt这种“库 / 代码 / 皮肤”三段式结构非常管用。皮肤资源集中在skin目录里,改界面不用动代码;库放在3rdparty固定不动,AI 不会在改业务代码时误伤库文件。
2.3 让 TRAE 认识你的工程:索引与 Agent 权限
TRAE 打开一个工程目录后,会默认做索引。但它对大型第三方源码库的索引有时不完整,尤其是 nim_duilib 这种模板较多的 C++ 库。实操里我会在 TRAE 的索引设置里手动把3rdparty/nim_duilib和src添加为高优先级目录,确保 AI 在分析“这段代码怎么用”时能引用到真实源码而不是瞎猜。
再一个关键操作是 Agent 模式的权限设置。TRAE 的 Agent 模式默认能读文件、改文件,但执行终端命令通常需要授权。像我前面说的,如果希望它能自己跑 CMake 和编译,就在设置里放开终端执行权限。第一次执行命令时它会弹确认,确认后这个会话内就不再反复问了。
提示:权限给大一点可以,但有一个前提——你这个工程在跑 AI 之前必须已经提交过一次 Git 版本。因为 Agent 模式改错了文件,最坏情况下
git checkout能一键回滚。没有版本控制就让 AI 任意改文件,等于裸奔。
3. 从空目录到第一个窗口:完整实操记录
这一节是真正的硬核部分。我带你完整过一遍我是怎么用 TRAE 把一个空目录变成能跑起来的窗口的。注意我不是只给结论,而是把我当时发给 TRAE 的指令原样贴出来,再解释为什么这么发。
3.1 第一轮对话:先把工程骨架搭出来,再谈界面
我打开 TRAE,新建会话,输入的第一段话是这样的:
当前目录是空的。请帮我搭建一个 Windows 桌面 C++ 工程的骨架,使用 CMake 构建,目标平台 x64,字符集使用 Unicode。 1. 创建 CMakeLists.txt,引入 3rdparty/nim_duilib 作为子目录; 2. 创建 src/main.cpp,里面新建一个 MainWnd 类,继承 WindowImplBase,重写 GetSkinFolder、GetSkinFile、GetWindowClassName; 3. 先不要写任何业务逻辑,保证项目能编译通过即可。这段提示词有几个关键点:明确“用 CMake”“x64”“Unicode”,这是 duilib 项目的三个硬性要求;明确“引入第三方库作为子目录”,省得 AI 自作主张去下载一个它不知道的版本;明确“先编译通过再谈业务”,算是给 AI 划了一条边界线。
TRAE 的 Agent 模式会依序创建文件、尝试运行 cmake 配置、编译。如果过程中少了 Windows 特有的库,它往往会自己补上。第一次跑通后,项目里会出现一个空白的主窗口类,就等着我往里填界面了。
3.2 让 AI 生成 XML 布局:这一步效率最高
骨架有了以后,我继续发指令:
请生成一个登录页 XML 布局,存到 skin/login.xml。 布局要求: - 外层是 VerticalLayout,宽度铺满,背景白色; - 顶部是标题文本“欢迎使用”,字号 20,加粗; - 中部是编辑框(name="edit_user")和密码框(name="edit_pwd"); - 底部是登录按钮(name="btn_login")和注册按钮(name="btn_reg"); - 所有控件需要合理设置高度和 margin,用来做简单垂直居中。TRAE 生成的 XML 大约长这样(这是我接入自己工程后手动微调过的版本):
<?xml version="1.0" encoding="utf-8"?> <Window size="400,300" caption="0,0,0,0" roundcorner="6,6"> <VerticalLayout bkcolor="#FFFFFFFF" padding="40,40,40,40"> <Label name="lbl_title" text="欢迎使用" height="40" font="1" align="center" textcolor="#FF333333" /> <Edit name="edit_user" height="32" margin="0,20,0,0" prompt="请输入用户名" bkcolor="#FFF5F5F5" /> <Edit name="edit_pwd" height="32" margin="0,12,0,0" password="true" prompt="请输入密码" bkcolor="#FFF5F5F5" /> <HorizontalLayout margin="0,28,0,0" childpadding="16"> <Button name="btn_login" text="登录" height="34" bkcolor="#FF4A90D9" textcolor="#FFFFFFFF" /> <Button name="btn_reg" text="注册" height="34" bkcolor="#FFCCCCCC" textcolor="#FF333333" /> </HorizontalLayout> </VerticalLayout> </Window>不得不说,这种带属性约束的描述性需求,AI 完成度极高。整个 XML 总共用时大约一分钟,控件基本没跑偏。这种结构清晰、属性有限、规则固定的东西,就是 AI 的舒适区。后面我会专门讲为什么提示词里要写“height”“margin”这种具体属性——因为不写具体属性,AI 容易自己发挥,生成一堆用不上的花哨布局。
3.3 事件绑定与逻辑跳转:让按钮真正干活
布局只是皮,业务才是魂。XML 里控件已经带好了 name,接下来的事就是让名字和 C++ 逻辑接上。
继续给 TRAE 发指令:
请修改 src/main.cpp 中的 MainWnd: 1. 在 OnClick 方法里处理 btn_login 和 btn_reg 的点击事件; 2. btn_login 点击后弹出消息框“登录成功”,暂时不做真实验证; 3. btn_reg 点击后弹出消息框“注册功能待实现”。 注意使用 DuiLib 的消息框函数,不要用 MessageBox。duilib 的消息框一般都走MessageBox或者带皮肤的RichMessageBox,名字因分支版本略有差异,TRAE 在翻 nim_duilib 源码以后能自动匹配到正确的调用方式。这一点也是我推荐把第三方源码放进工程的原因——AI 是能“看到”真实 API 的,只要你不切掉它的视线。
生成的OnClick逻辑大概是这样:
void MainWnd::OnClick(TNotifyUI& msg) { if (msg.pSender == nullptr) { return; } CDuiString strName = msg.pSender->GetName(); if (strName == _T("btn_login")) { ::MessageBox(m_hWnd, _T("登录成功"), _T("提示"), MB_OK); } else if (strName == _T("btn_reg")) { ::MessageBox(m_hWnd, _T("注册功能待实现"), _T("提示"), MB_OK); } }在这里我没有真的用 duilib 自定义消息框,而是让 AI 先落一个 MessageBox 占位。你让 AI 第一版就做“完全体”,各种自定义皮肤消息框、校验逻辑、错误提示全上,你会得到一堆需要调试的组合拳。先把流程打通,再逐渐替换细节,这是我和 AI 协作的最高效节奏。
4. 提示词与上下文管理:让 AI 写的 C++ 不再“像代码但没用”
很多人让 AI 生成的 C++ 代码“看着专业,一编译全错”,根子不在 AI,在对话方法。这里我分享一下从实操里总结出来的提示词套路。
4.1 高质量提示词的标准结构:角色、任务、约束三步走
我用下来最顺手的模板结构是:
角色:你是一名精通 C++ 桌面端开发的工程师,尤其熟悉 duilib / nim_duilib 框架。 任务:把当前工程里的主窗口宽度调整成 960x640,并在右侧增加一个状态栏区域。 约束: - 只能修改 src/ 目录下的文件,不许动 3rdparty; - 布局文件用 XML 修改,逻辑代码用 C++ 修改; - 改完以后不要立即编译,先汇报改动了哪几个文件。角色放在最前面,目的是给它设定“解题风格”上下文。任务一句话说清楚要什么。约束是灵魂——它决定了 AI 不会放飞自我去动它不该动的东西。尤其是“不许动第三方库”这条,能省掉一堆“版本不符莫名其妙编译失败”的破事。
4.2 让 AI 参考已有代码而非凭空想象
C++ 的 API 太多变,同样一个“获取窗口句柄”在不同库里有四五种写法。与其让 AI 猜,不如直接指路:
参考 3rdparty/nim_duilib/examples/BasicDemo 里 MessageBox 的调法,把登录提示改成那个风格。这句话会让 AI 在对话中把先例代码读一遍,然后照着写。哪怕那个示例代码质量一般,它写出来的东西也会和你的工程风格高度一致。这一点比任何长篇大论的提示词都管用。你等于把菜谱直接放到它面前,而不是让它回忆一个从来没见过的菜名。
4.3 多轮对话的上下文污染:该重置时就重置
用 TRAE 连续对话超过二三十轮以后,AI 经常开始走神。最典型的症状是:你说“改一下登录框宽度”,它把整个登录窗口的布局都重写了。这就是上下文污染——前面的对话里太多修改记录,导致 AI 分不清当前任务边界。
我现在的习惯是:一个明确的小目标完成并编译通过后,立即开新会话。新会话第一句话先粘贴当前工程的关键结构:
当前工程基于 nim_duilib,主窗口类在 src/main.cpp,布局文件在 skin/main_window.xml。 今天要做的事:给按钮 btn_search 增加点击事件,弹出一个下拉菜单。这段话叫作“语义快照”,相当于把 AI 的短期记忆重置到了最新状态。信息量小、目标明确,比在旧会话里拉扯十几轮高效得多。
5. 编译、链接、运行时的排错链路:AI 离不开的基本功
不管 AI 多强,最终编译和跑起来的还是 C++ 代码。排错能力是底线能力。我在这个项目里遇到的错基本集中在三类,下面逐个拆。
5.1 最常见编译/链接错误及处理思路
先放一张我在开发过程中频繁遇到的错误对照表,方便你快速定位:
| 报错现象 | 根因 | 处理方式 |
|---|---|---|
fatal error C1189: Please define _AFXDLL...或不相关的宏冲突 | MFC 和 duilib 共存 | 把 MFC 使用从“在共享 DLL 中使用 MFC”改为“不使用 MFC” |
error C2664: 无法将参数 3 从“const wchar_t *”转换为“LPCTSTR” | 字符集不是 Unicode | 工程属性里把字符集切到“使用 Unicode 字符集” |
LNK2019 unresolved external symbol出现在 duilib 相关函数上 | 没链接 duilib.lib,或库路径没配 | 在链接器附加依赖项里加上 duilib.lib 完整路径 |
LNK2005 已经在 duilib.lib 中定义 | 有的 cpp 直接包含了 duilib 源码而不是引用 lib | 统一用库文件引用方式,别混用源文件编译 |
AI 生成代码时经常会忘记“字符集 Unicode”这个前置条件。你让它写字符串处理,它下意识就用std::string,而 duilib 内部接口几乎都是宽字符,结果就是一堆C2664和C2665。所以我每次都把 Unicode 放在工程和提示词的双重约束里,从源头避免。
5.2 运行时窗口黑屏、控件不显示、点击无响应
编译过了,程序能运行,但界面是黑的——这种问题最容易让人抓狂,因为不报错。
我排查这类问题的顺序基本固定:先看 XML 路径。GetSkinFolder返回的目录和GetSkinFile返回的文件名组合起来必须能找到真实文件。注意GetSkinFolder是相对路径,它基于程序的工作目录去找资源。如果你在 VS 里直接调试,工作目录默认是工程目录,但你可能把 skin 放在下一级了。最省心的办法:在CMakeLists.txt里设置好运行时工作目录,或者把skin目录拷贝到与 exe 同级目录。
再看 XML 里的控件 name 是否与代码里GetName()比对的名字完全一致,包括大小写。我对这件事吃过亏:XML 里写btn_Login,代码里写btn_login,死活点不动,查了半天。
最后检查 XML 属性。duilib 对属性的大小写是敏感的,bkcolor写成了bkColor都可能不生效,而且不会报任何错误。这种问题让 AI 找特别合适,把皮肤目录传给它,让它对照官方示例的 DTD 或 schema 检查,通常一轮就能定位。
5.3 当 AI 输出的代码调不通时,怎么“逼供”它
遇到 AI 生成的代码多次编译不过,最忌讳的就是泛泛说“还是有错,帮我改”。正确的做法是压缩信息密度:
- 把完整编译错误日志粘给它,不要你人工过滤“你认为重要的部分”。AI 自己会挑;
- 指认上次改动范围:“错在你刚才新增的
SetIcon和SetToolTip两处附近”; - 给出一个它绝对无法耍滑头的边界:“如果你修完还是编译失败,就回退到上一版能编译的代码,不要再继续叠加新的设计”。
第三条特别重要。AI 有时候会陷入“越改越乱”的死循环,不断往代码里加补丁。这时候给它一个“回退”选项反而能打破循环,让项目回到可编译状态再做增量修改。这和人类工程师修改代码的思维完全相同——先保证主干呼吸,再谈枝节美化。
6. 从跑通到交付:工程化注意的三个细节
窗口能弹出来、按钮有反应,这算阶段性胜利,但距离一个能交付的桌面工具还有距离。最后分享几个我在把项目做扎实时注意的细节。
6.1 资源与皮肤管理:别把图片路径写死
duilib 的图片、字体资源通常放在皮肤目录里。AI 生成的 XML 里,资源引用往往用相对路径,比如file="logo.png"。这个习惯要维持住,别写成C:\Users\xxx\Desktop\logo.png。一旦写死绝对路径,换台机器就崩。
我的做法是在skin下再拆子目录:
skin/ ├─ main_window.xml ├─ common/ # 通用图标、按钮背景 ├─ login/ # 登录窗口专用资源 └─ fonts/ # 自定义字体引用资源时统一写成相对路径:file="common/btn_bg_normal.png"。这个规则同样要让 AI 知道,否则它会图省事把所有图片堆到根目录。
6.2 DPI 缩放:现在的新电脑都是高分辨率屏
duilib 本身支持 DPI 感知,但你需要主动启用它。如果工程没有做 DPI 适配,在 2K、4K 屏上界面会糊成一片,按钮大小错乱。可以给工程加一个 manifest 或者在入口处调用相关 API 来声明 DPI 感知。AI 不一定每次都会主动想到这个,所以我在提示词的习惯是:
目标平台为 Windows 10/11,需要支持高 DPI 缩放,请检查入口和 Window 设置。6.3 Git 版本控制:AI 改代码的保险绳
前面提过一嘴,这里再强调。AI 写代码越来越像真人同事入职,而 Git 就是你给这位同事配的“后悔药”。我每完成一个独立功能点就提交一次:比如“登录窗口 XML 定稿”“主窗口事件打通”“修复 DPI 缩放”。提交信息写清楚当时的状态,万一后续改崩了,可以准确回退到任意里程碑。
这个习惯听起来老土,但在 AI 辅助开发场景里价值极大。因为 AI 修改文件的速度快、范围广,往往一轮对话会动四五个文件,没有版本记录的话,你根本不知道你丢掉的是什么。
写在最后的个人体会:用 TRAE 写 nim_duilib 项目,最舒服的地方不是“它帮我写完了”,而是“它把 C++ UI 开发里那部分重复、模板化、毫无乐趣的活儿接走了”。XML 布局、按钮事件、工程配置、资源路径整理……这些活以前占了我一天里一大半时间,现在交给 AI 十分钟完成,我把省下来的时间全用在了想清楚业务交互和界面表达上。
如果你也想试,我建议从一行代码都不写、完全让它搭一个窗口开始,体验一下整个闭环,再逐步放权。磨刀不误砍柴工,先把工具链和环境理顺,后续的 C++ 桌面 UI 开发会轻松非常多。