简介:uCGUIBulider 4.0.0.0 是面向嵌入式系统的图形用户界面构建工具,配合轻量级图形库 uCGUI 使用,主要帮助微控制器开发者通过拖放控件、设置属性与预览效果,自动生成界面代码,从而提升 UI 开发效率。本次提供的压缩包共 430 个文件,大小约 7.87MB,包含 215 个头文件、19 个库文件、19 个可执行程序、18 个批处理脚本、12 个动态链接库,以及工程备份、图标与示例图片等辅助资源;头文件与库支撑编译链接,可执行程序和批处理便于启动配置与自动化处理,整体结构完整,便于在 Windows 8 64 位系统中直接使用。目前已有 178 人学习下载,适合嵌入式初学者快速搭建界面原型,也能为中高级开发者提供控件封装、代码生成机制等参考,用于定制显示方案、排查图形驱动问题或扩展自主 UI 框架。压缩包内还包含大量头文件与库文件的完整配套关系,可帮助理解工具生成代码的底层逻辑。
1. uCGUIBulider 4.0.0.0 到底解决了什么问题:把界面设计从手写代码里解放出来
做屏显开发的工程师大概都有过这样的阶段:调一个带按钮、进度条、文本显示的界面,先在初始化函数里一个控件一个控件地创建,算坐标、调尺寸、注册回调,编译烧录后控件位置不对,再回到代码里改坐标,重新编译看效果。一个下午就耗在“改数字—看效果”的循环里。uCGUIBulider 4.0.0.0 这类图形界面设计器,就是拿来终结这种循环的。它把对话框、按钮、文本、滚动条这些控件的布局、尺寸、ID、事件回调都变成可视化操作,拖拽生成 C 代码,再把它合进你的嵌入式工程。
这款工具解决的不是“能不能画界面”的问题,而是“界面代码怎么写才不容易返工”的问题。4.0.0.0 是它的一个重要版本分支,生成代码的结构比早期版本更接近现代 GUI 库的对话框管理模式,资源表和回调函数的组织方式清晰,适合从零搭建人机交互界面,也适合老项目里把零散控件代码统一收编。适合的读者是:在用经典嵌入式 GUI 库做彩屏应用、手里有一块能跑 GUI 的目标板、想缩短界面开发周期的一线开发人员。接下来以我实际用过这个版本的经验,把工具链、生成代码的结构和移植踩坑一起讲清楚。
2. 工具链与运行环境:从安装到模拟器跑出第一个界面
2.1 先分清:它是“GUI 设计器”,不是“GUI 运行库”
uCGUIBulider 的定位是配套 GUI 库上位机设计工具,它自己不负责在目标板上绘制像素。它做三件事:用画布模拟目标屏;把控件变成可拖拽对象;最终生成 C 源码供你的工程编译。4.0.0.0 对应的 GUI 库分支里,对话框的概念被强化了:一个界面不再是一堆分散的创建函数调用,而是一个对话框描述块,包含资源表、创建函数和回调函数。
我在拿到这个版本时踩过的一个认知弯路是:先去学它的每个菜单、每个属性怎么填,结果花了很多时间在界面上,生成的代码却没有真正用起来。后来一位做显示方案多年的同事提醒我,这个工具的正确用法是先搭一个极小的对话框,立刻生成代码放进工程跑通,然后再逐步加控件。因为生成代码的结构相对固定,你真正需要理解的只有三个文件:对话框的 C 文件、对应的头文件、资源相关 C 文件。
选型理由也很简单:如果手头已经定了某款嵌入式 GUI 库,配套设计器是省事的选择。它生成的代码里,对话框管理和回调框架已经按库的原生习惯搭好,相比自己写初始化函数,不太容易出现“控件创建了但消息进不了回调”这类低级问题。4.0.0.0 这种版本号看起来老,但核心工作流程和当前主流设计器一脉相承,老版本资料多、坑也都被前人踩平了,反而适合入门。
2.2 安装与最小验证:用模拟器先跑通
常见做法是先装好设计器,再跑它的模拟器工程。解压工具包后,目录里通常有 PC 模拟器相关的工程文件,它把同一套 GUI 库编译到 Windows 桌面环境里,界面显示在一个模拟窗口,不碰硬件就能验证布局逻辑。
我一般会这么操作:
# 假设解压目录为 C:\tools\ucgui # 1. 进入模拟器工程目录 cd C:\tools\ucgui\Simulation\VS2019 # 2. 打开工程文件(.vcxproj),用 Visual Studio 编译 # 3. 编译通过后运行,会弹出一个显示 GUI 界面的窗口这个步骤里最重要的一处设置是“画布分辨率”。模拟器默认可能给你一个比较大的窗口,比如 1024x768,但你的目标屏可能是 480x272。如果不改,所有控件的坐标都按大屏生成,真机上就会出现显示不全的问题。正确做法是在设计器新建工程时就把分辨率设成目标屏的像素值,模拟器窗口按比例缩放。
参数说明:模拟器只是验证交互逻辑的工具,不要指望它复现真机的颜色深度和刷新率。颜色深度由 GUI 库的底层驱动配置决定,设计器里选的颜色在真机上可能被量化成另一档,这在做渐变和半透明效果时要格外注意。
2.3 生成产物放进工程:文件与依赖清单
一个典型的 4.0.0.0 工程保存后,你会得到一组文件。用表格列一下各自的作用:
| 文件 | 内容 | 是否必须手动改 |
|---|---|---|
| XxxDlg.c | 对话框创建函数、回调函数、控件资源表 | 回调里加业务逻辑 |
| XxxDlg.h | 对话框和控件的 ID 宏定义、函数声明 | 一般只读 |
| XxxResource.c | 位图、字体等资源数组 | 不建议手动改 |
| Simulation 工程文件 | 给模拟器编译用的壳 | 无需关注 |
把这些文件加入目标工程时,有一点容易被忽略:头文件里的 ID 宏是全局的,不同对话框之间的控件 ID 可以重复,因为回调函数是按对话框维度分派的;但对话框本身的 ID 必须全局唯一,否则消息分发时可能串。工程里如果已经手写过界面代码,建议先把旧的创建函数注释掉,再链接新生成的对话框,避免初始化时控件被创建两次。
3. 拖出第一个可用界面:新建、控件布局与属性设置要点
3.1 画布与分辨率:先确认目标屏的像素
打开设计器新建对话框,第一个要填的就是画布尺寸。这里我建议把“客户区”和“整个屏幕”分开理解:屏幕坐标系原点在左上角,x 向右增加,y 向下增加;对话框客户区通常不带标题栏,原点也在左上角。对于全屏应用,这两个区域像素值一致;如果目标 UI 有顶部状态栏,画布就应只放内容区。
实际操作中,我会把目标屏的分辨率写在便签上再新建工程。假设屏幕是 800x480,画布就填 800 和 480。很多“模拟器里好好的,板子上控件跑飞”的案例,根因就是画布尺寸和实际屏不一致,生成代码里的绝对坐标是按画布算的。
参数说明:坐标数值是像素单位,设计器里用整数对齐网格。用网格对齐比肉眼居中靠谱得多,尤其做多语言的文本按钮时,中英文长度不同,设计时就要把按钮的宽度留够,靠网格把左右留白留均匀。
3.2 添加控件:从按钮到文本的常用属性
控件栏里最常用的三件套是按钮、文本、进度条/滑动条。拖一个按钮到画布上,右侧属性面板会列出它的关键项:控件 ID、文本内容、坐标与尺寸、字体、对齐方式、是否可见、是否禁用。
控件 ID 是重点。设计器生成的 ID 类似ID_BUTTON_0,放进工程后你会在回调里拿它判断哪个按钮被按下。建议不要用自动生成的默认 ID 一路用到底,而是改成有业务含义的名字,比如ID_BTN_START、ID_BTN_STOP。改两个字母的事,调试时能省下反复查表的时间。
/* 生成的头文件里,ID 宏长这样 */ #define ID_BTN_START (GUI_ID_USER + 0x10) #define ID_BTN_STOP (GUI_ID_USER + 0x11)这里GUI_ID_USER是库保留给用户自定义控件的起始偏移,设计器生成的 ID 都从此往后排。注意控件 ID 和对话框 ID 是两套体系:对话框 ID 用在创建函数和消息路由上,控件 ID 只在回调内部判断来源。
Tab 键顺序影响焦点切换的方向。做按键式交互(非触摸)时,用户按上下左右或者 Tab 键会按这个顺序在控件间移动焦点。属性面板里通常有“Tab 顺序”或“Z 序”的设置入口,数字小的先获得焦点。建议按照视觉从左到右、从上到下的顺序编排,否则真机上焦点乱跳,操作体验会很差。
3.3 生成工程代码:保存路径与命名规则
控件摆好后,选择“保存”并指定文件名。对话框的名字会成为生成代码的实体名前缀。比如我把对话框命名为MainMenu,生成的就是MainMenu.c和MainMenu.h,函数名也会基于这个前缀。
命名规则这块有血泪经验:文件名尽量用英文大写开头的驼峰,不要叫test1、123这种,也不要和别的对话框重名。我见过一个项目里两个人各建了一个都叫Setting的对话框,生成代码后原封不动放进工程,链接阶段直接报重复定义。解决办法倒也简单:在设计器里把其中一个的对话框名改成SettingAdv,重新生成。
保存路径建议单独建一个ui目录,和主程序源码分开。因为生成工具如果升级或者重装,你通常只会重新生成指定文件,不会动主程序;目录独立后,也能一眼看出哪些文件是工具生成的、哪些是自己维护的,避免误改。
3.4 把生成文件接入现有工程:一个最小集成清单
代码生成之后,接入工程并不是简单地“把文件加进编译列表”就完事。我一般按下面的顺序做:
# 1. 把 MainMenu.c / MainMenu.h / MainMenuResource.c 拷贝到 ui 目录 # 2. 在工程里添加这三个文件参与编译 # 3. 在主程序初始化 GUI 库之后创建对话框对应的主程序调用大致是:
#include "MainMenu.h" void app_ui_init(void) { /* 先确保 GUI 库已初始化完成 */ GUI_Init(); /* 创建对话框:返回 -1 表示创建失败 */ GUI_ExecDialogBox( _aDialogCreate, /* 资源表,生成在 .c 文件里 */ GUI_COUNTOF(_aDialogCreate), _cbDialogMainMenu, /* 回调函数 */ 0, 0, 0 /* 0 表示使用默认位置 */ ); }注意_aDialogCreate和_cbDialogMainMenu都是声明在生成的.c文件里的内部符号,你在主程序里可以直接引用,但它的定义不在头文件里,而是通过GUI_ExecDialogBox传递。如果编译报未声明,检查一下头文件里是否已经包含了对应的 extern 声明;某些版本会把这些符号声明在对话框头文件里,但实际定义只在 C 文件里,遇到链接错误时优先排查有没有把 C 文件加进编译列表。
4. 读懂它生成的 C 代码:回调、消息与集成进工程的方法
4.1 资源表:控件描述统一放在 C 数组里
打开生成的MainMenu.c,你会发现控件的创建代码不是一行一行散开的,而是集中在一个静态数组里。这就是对话框的资源表,设计器拖拽产生的所有控件信息都被结构化成数组元素,创建时由 GUI 库统一解析。
static const GUI_WIDGET_CREATE_INFO _aDialogCreate[] = { { FRAMEWIN_CreateIndirect, "MainMenu", ID_MAINMENU, 0, 0, 800, 480, 0 }, { BUTTON_CreateIndirect, "Start", ID_BTN_START, 40, 60, 120, 40, 0 }, { BUTTON_CreateIndirect, "Stop", ID_BTN_STOP, 40, 130, 120, 40, 0 }, { TEXT_CreateIndirect, "Status", ID_TEXT_STATUS, 40, 40, 200, 20, 0 }, { LISTBOX_CreateIndirect, "", ID_LISTBOX_LOG, 200, 40, 320, 200, 0 } };每个元素的字段含义:控件创建函数、控件文本、控件 ID、x、y、宽、高。这里FRAMEWIN_CreateIndirect创建的是带边框的框架窗口,它也算一个控件,占画布整个区域;其余按钮文本都放在它内部。
参数说明:如果你要手动在这个数组里加一个控件,必须保证类型、ID、坐标都合法,同时还要在回调里加响应分支。设计器生成后我不建议手动改数组——它会在下一次“重新生成”时被整体覆盖,你的手改就没了。要改布局就回设计器拖,要加控件也回设计器加,手动维护这个数组是性价比最低的事。
4.2 创建函数与回调注册:一张表连着两个函数
资源表下面是两个关键函数:一个是创建执行入口,另一个是回调。它们的协作方式可以用一句话说清:创建入口把资源表交给 GUI 库,控件逐条创建;回调负责接收用户操作产生的通知消息。
void MainMenu_Exec(void) { GUI_ExecDialogBox( _aDialogCreate, GUI_COUNTOF(_aDialogCreate), _cbDialogMainMenu, 0, 0, 0 ); } static void _cbDialogMainMenu(WM_MESSAGE * pMsg) { switch (pMsg->MsgId) { case WM_NOTIFY_PARENT: /* 控件通知父窗口:按钮按下、值改变等 */ break; case WM_PAINT: /* 需要自绘时才用得着 */ break; default: WM_DefaultProc(pMsg); break; } }注意_cbDialogMainMenu被声明成 static,对外只暴露MainMenu_Exec。这意味着你无法在别的文件里直接调用回调,只能通过消息机制触发它,这是库设计的正常行为,不是缺陷。
WM_NOTIFY_PARENT是控件向父窗口发通知的核心消息。父窗口的 ID(这里是ID_MAINMENU)和控件 ID 在这个消息里会一起被携带,你在回调里要区分是谁发的。
参数说明:GUI_ExecDialogBox是模态调用,它内部会进入消息循环,直到对话框被删除。如果你的主循环里已经有事件分发机制,要考虑是否改用非模态创建方式,否则两个消息源会互相抢占。
4.3 回调里加业务逻辑:按键分支怎么写得顺手
在实际工程里,回调函数的正文几乎全放在WM_NOTIFY_PARENT分支里。我会在每个控件通知到来时先取控件 ID,再判断通知码:
case WM_NOTIFY_PARENT: { int id = WM_GetId(pMsg->hWin); int code = pMsg->Data.v; switch (id) { case ID_BTN_START: if (code == WM_NOTIFICATION_CLICKED) { /* 用户按下了 Start 按钮 */ app_engine_start(); } break; case ID_BTN_STOP: if (code == WM_NOTIFICATION_CLICKED) { app_engine_stop(); } break; default: break; } break; }WM_GetId拿到的是发消息控件的 ID,pMsg->Data.v里存的是通知码。常用的通知码有WM_NOTIFICATION_CLICKED表示点击,WM_NOTIFICATION_RELEASED表示释放,WM_NOTIFICATION_VALUE_CHANGED表示值改变(滑动条、列表框用到)。
我建议把业务处理拆成app_engine_start()这种独立函数,不要直接在回调里写大段逻辑。原因很直接:回调运行在 GUI 库的上下文里,耗时操作会影响界面刷新,把逻辑拆出去后,界面层和业务层可以分别做超时和异常处理,调试也更快。
4.4 手动改代码的边界:哪些能改,哪些必须回设计器
这是用这个工具最容易翻车的地方。我的准则是:布局结构全回设计器,业务逻辑留在回调。具体来说:
能手动改的:回调函数里新加的消息分支;事件处理里调用的业务函数实现;对话框外部的变量和宏。这些内容不在资源表流程里,设计器再次生成时不会覆盖回调函数里已有的业务代码——生成器保留的是case分支骨架,函数体内容它会原样保留。
不能手动改的:资源表数组里的控件坐标、尺寸、ID;对话框 ID 宏;头文件里的控件 ID 定义。这些一旦手改,下次在设计器里打开这个.c时,工具按资源表反解析,坐标和属性全乱,你在设计器里看到的内容就和你手动改的完全对不上。
我在项目里遇到过一种常见反模式:为了微调一个按钮的坐标,直接在.c文件里把40, 60改成42, 62,当时确实见效了,但后来设计器重新生成时,按钮又跳回左上角,还连累了旁边几个控件的位置。那次之后,所有布局改动强制回设计器改完再生成,再没有因“半手动半生成”造成的布局翻车。
5. 移植和运行避坑:模拟器正常但板子花屏的五个典型问题
5.1 真机白屏或控件不全:GUI 内存堆给小了
现象:模拟器里界面完整,烧到板子上要么白屏,要么只有一部分控件显示,运行几秒后还可能死机。
原因:设计器在模拟器上跑用的是 PC 的大内存,而你板上的 GUI 内部堆可能需要手动配置。对话框的资源表解析、控件对象创建、位图缓冲都要从堆里分配,堆不够时创建函数静默失败,界面就会少控件。
解决:检查初始化代码里的堆大小配置,常见入口是GUI_ALLOC_AssignMemory或类似宏,在 GUI 初始化前分配一块内存。我一般会按每个对话框约 2~4KB 估算,再留 50% 余量,实测不够再加。改完配置重新编译烧录,同时把打印打开的GUI_DEBUG消息也开一下,堆不足时系统会输出定位信息。
5.2 中文显示成方块:字库与编码不匹配
现象:按钮文本和文本控件里的中文在模拟器显示正常,在板子上变成一排方块或乱码。
原因:设计器生成的代码里中文是按开发机的编码保存的,而目标板的 GUI 库里如果没有加载对应编码的字库,按字节索引字形表就会查不到,显示成方块。模拟器能显示是因为模拟器工程里挂了完整 PC 字体。
解决:用工具包里的字体转换器把需要的中文文本做成点阵或矢量字库,生成 C 文件加入工程,并在对话框设计器里给对应控件的字体属性指定这个中文字库。同时确认源文件保存的编码和字库建立时使用的字符集一致,常见是 UTF-8 和 GB2312 混用导致的随机乱码。
5.3 触摸点击位置偏移:校准参数对不上
现象:点按钮 A 没反应,点 A 左侧空白处反而触发了 A;在屏幕边上偏移尤其明显。
原因:触摸屏的采样坐标和显示坐标之间存在线性偏差,需要校准参数来换算,未经校准或参数和当前面板不匹配时就会出现整体偏移。
解决:先做一次触摸校准,把四个角落和中心的校准值写进 NVM 或配置文件。校准通过后,回到设计器检查画布分辨率是否和屏一致。分辨率一致但边缘仍偏,多半是校准参数只做了中心点,没有做边缘拟合,这种需要换带多点校准的触摸驱动,插件板上的临时方案不建议量产。
5.4 控件互相遮盖、焦点乱跳:Z 序没整理
现象:后加的控件盖住了之前的按钮,实际可点击区域被遮挡;用键盘操作时焦点跳到看不见的控件上。
原因:设计器的控件分层按添加顺序,后添加的在顶层;Tab 顺序自动按创建顺序排,如果你添加控件的顺序和视觉顺序不一致,焦点流转就不符合操作预期。
解决:在设计器里调 Z 序,把底层背景、中间内容、顶层操作按钮分层摆好;再手动修 Tab 顺序,让它按“从上到下、从左到右”排列。改完重新生成代码,编译后先在模拟器里走一遍键盘焦点,再上真机。
5.5 编译报重复定义:对话框名撞车
现象:把两个话框的生成代码加进工程后,链接报_cbDialogXxx重复定义,或者某个对话框 ID 被重定义。
原因:不同对话框生成时用了同样的前缀名,或两个版本的设计器生成风格不同,导致符号冲突。常见于多人协作时各建各的界面文件,最后合并到一起。
解决:在设计器里把文件名和对话框名改到全局唯一,重新生成。改符号的时候顺手检查一下头文件里控件 ID 有没有和现有手写代码的宏重名。这里有个小技巧:生成的头文件里 ID 一般用GUI_ID_USER做偏移基准,手写代码用类似0x2000、0x3000的裸数字时,两边范围可能重叠,合并时统一改成带前缀的宏名最省心。
6. 进阶:把位图与字库资源按需编进工程
界面做得像样,离不开图标和自定义字体。设计器支持引用位图资源和外部字体,但资源转换这一步常被新手忽略。工具包里一般带位图转换器和字体转换器,它们的输入输出套路一致:输入原始图像或字体文件,输出 C 文件,里面是一个或多个static const数组,供 GUI 库直接使用。
以位图为例,命令行方式大致是这样:
# 把 PNG 图标转换为 16bpp RGB565 数组,输出 icon.c 和 icon.h bitmapconv -in icon.png -out icon.c -format RGB565生成后,在生成的对话框资源表里,把位图控件的属性改成引用这个新资源。我的习惯是把所有用到的位图集中放在一个文件夹,统一命名,避免生成多个碎片文件。
最后一件事是养成习惯:每次在工具里修改完界面,生成 C 文件后先在模拟器里跑一次对话框,确认布局无误,再放进真机工程编译。不要直接生成就往板子上烧,很多翻车现场都是生成后没验证就投板。希望这个流程能帮你的界面开发少走几次弯路。
本文还有配套的精品资源,点击获取