简介:PrgFormDesigner 是一款采用 C# 编写的可视化界面设计工具,面向使用 xHarbour、wxHarbour、dBase、FoxPro 等传统数据库及 Harbour 系列框架的开发者。借助拖拽组件、属性面板与多布局方式,可快速生成从简单表单到复杂多窗口界面,避免因环境差异频繁更换工具,节省学习与切换成本。压缩包为 7z 格式,共 2000 个文件、约 501MB;包含 782 个头文件、536 个说明文档、447 个 C 语言源文件与 105 个 C++ 源文件,另有 77 个配置文件及少量 PDF、Word、PPT 资料,目录结构清晰,便于按模块阅读与二次开发。目前已有 61 人学习下载。源码覆盖表单设计器核心实现,包括组件封装、属性绑定、布局管理和面向不同环境的适配层;无论是希望快速掌握界面工具实现原理的初学者,还是计划定制表单生成器、扩展传统数据库开发链路的进阶用户,都能从中获得可参考的工程架构与实用细节。 接手老系统的界面改动,大概是所有做桌面软件的人都绕不开的事。前两年我维护一套跑了快二十年的仓库管理程序,代码是 xHarbour 写的,界面全在 PRG 文件里,密密麻麻的 @ x,y SAY 和 GET。客户提需求从来不管什么坐标和字体,一句"把保存按钮放到右上角",我就得在坐标格子里数半天,然后编译、运行、截图、再调,一上午就搭进去了。这种日子过够了之后,我开始专门找面向 xBase 系语言的可视化界面工具,最终在一个开源仓库里翻到了 PrgFormDesigner:一套用 C# 写的界面生成软件,支持 xHarbour、wxHarbour、dBase、FoxPro,而且源码完整。这篇文章把我读源码过程中的理解、二次开发实验和踩过的坑整理出来,给同样被困在老系统里的朋友一个参考。
先说清楚它解决什么问题:不是帮你重写业务,而是把"画界面"这件事从纯手工数格子变成拖拽生成。实际用起来,它更像 FoxPro 时代的设计器——左边控件工具箱、中间画布、右边属性面板,但生成的目标代码是跨方言的 PRG。这种定位非常聪明,因为老系统的核心痛点是界面维护成本太高,而业务逻辑代码大多数情况是好的,没必要推翻。
1. 老系统的界面困局:为什么还要给xBase写一个"新"设计器
1.1 被历史选中的FoxPro和xHarbour还在生产线上
很多人以为 FoxPro、dBase、xHarbour 这类语言早就进博物馆了,但真到工厂、医院、物流仓储里逛一圈就会发现,大量十几二十年的业务系统仍跑在这些平台上。这些系统数据表设计合理,业务规则沉淀多年,客户根本不可能推倒重来,企业也不愿意承担重写的成本。于是"继续维护"成了唯一选项,而界面恰恰是维护成本里最让人头大的部分。
我接手的那套系统就是这样,窗体逻辑分两层:一层是业务过程调用,另一层是界面定义。界面定义几乎清一色是这种写法:
@ 6,20 SAY "客户编号" @ 6,30 GET cId PICTURE "XXXXXXXX" @ 8,20 SAY "客户名称" @ 8,30 GET cName PICTURE "XXXXXXXXXXXXXXXX" @ 10,20 BUTTON "保存" ACTION Save() @ 10,35 BUTTON "取消" ACTION Cancel() READ外行人看这段代码觉得挺整齐,只有维护过的人知道,@ 后面的行号和列号是按字符格算的,跟屏幕上实际像素位置有偏差;加一个控件难,删一个控件更难,字体一换整个布局就乱。更重要的是,你没法在运行之前"看到"界面,所有问题都得编译后肉眼确认。
1.2 老IDE的断代问题与独立设计器的价值
按理说 VFP 自带表单设计器,xHarbour 也有配套工具,但这些工具的问题在于绑定在特定 IDE 版本上,很多老 IDE 在 Windows 10/11 上跑不起来,或者装起来极其麻烦。更麻烦的是不同方言的工程结构不一样,一个工具往往只管一种语言,换方言就得换工具。PrgFormDesigner 这类独立设计器的价值就在这里:它把设计端从目标运行时里解耦出来,用 C# 的成熟 UI 框架承载绘制能力,最终生成 PRG 形式,让老工程继续用原来的编译链,界面设计却能享受现代工具体验。
这里多说一句选型逻辑:为什么是 C#?因为 WinForms 本身就自带一套完整的设计器基础设施——控件工具箱、属性面板、拖拽布局都现成,拿来做代码生成器的宿主再合适不过。对 .NET 开发者来说,改源码的门槛也低,不需要去啃老语言生态环境里那些偏门的私有实现,这也是我敢拿这套源码做定制的原因。
2. 中间控件模型与方言适配:PrgFormDesigner的架构拆解
2.1 画布上放的不是控件,是"生成意图"
一开始我差点被源码里的类名带偏,以为它就是把 C# 控件直接翻译成 PRG。读下去才发现,它的设计思路要清晰得多:你在画布上拖出来的每一个元素,都会被存成一个与目标语言无关的中间控件模型,比如 PrgControl,里面记录类型、标题、行列坐标、可见性、绑定数据源、事件和属性字典。
var ctl = new PrgControl { Type = ControlType.Button, Text = "保存", Row = 10, Col = 20, Event = "Save()" }; form.Controls.Add(ctl);所有后续操作——属性面板修改、画布摆放、代码生成——都围绕这个模型进行。画布负责把它画出来,属性面板负责改它的字段,代码生成器则负责在最后把它转成对应方言的文本。中间层的好处一眼就能看出来:支持新的方言只需要加一个生成器,不需要改画布和属性面板;支持新的控件类型,也只需要增加模型类型和对应的绘制、生成两段逻辑。
2.2 方言差异不是小问题,是适配层存在的原因
xHarbour、wxHarbour、dBase、FoxPro 都属于 xBase 系,彼此语法相近,但细节差异足够让你头疼。比如经典 FoxPro/dBase 习惯用 @ SAY/@ GET + READ 这种面向字符屏的写法;xHarbour 工程常用 DEFINE WINDOW + ACTIVATE WINDOW 的窗口模式;wxHarbour 则是基于 wxWidgets 绑定的一套面向对象类库,窗口和控件都是对象,事件靠方法回调。同一个"保存按钮",在不同方言里生成出来的代码完全是不同形态。
| 方言 | 窗口定义 | 控件典型写法 | 事件模型 |
|---|---|---|---|
| FoxPro/dBase | 表单文件 + READ | @ SAY/@ GET + PICTURE | VALID / WHEN 子句 |
| xHarbour | DEFINE WINDOW | @ SAY/@ GET 或控件类 | 消息或方法回调 |
| wxHarbour | wxWidgets 窗口类 | 对象实例化 | 事件回调方法 |
PrgFormDesigner 的处理方式是给每种方言一个独立的代码生成器类,对外暴露统一的 Generate(FormModel) 接口。这样主流程完全不用关心目标语言是谁,只负责把中间模型喂进去,生成器自己决定输出 @ SAY 还是 DEFINE WINDOW 还是 wxHarbour 的类实例。这也是我读源码时觉得最值得学的地方:做跨语言工具,先做模型抽象,别急着写各种 if 分支。
2.3 坐标换算:设计时用像素,生成时用字符格
中间模型里必须保留两套坐标系统。设计器里的拖拽操作基于像素坐标,鼠标在哪、控件画多大,都是像素语义;而生成出去的 PRG 代码,@ x,y 里的 x 和 y 是字符格坐标,行高、列宽由运行环境的字体决定。两套坐标系如果只存一套,生成结果几乎肯定会错位。
源码里坐标换算的思路是:建立一个"字符网格"的抽象,把像素坐标按照当前设计器的字体度量除以行列字符尺寸,向下取整得到字符坐标;反向操作则用于把控件树渲染回画布。这个换算不是简单的除法,中文环境下还要考虑中文字符宽度等于两个西文字符宽度的问题,不然标签和输入框就会错位。我实际测试时就吃过这个亏,后面专门做了验证,详见第 4 章的坑。
3. 从画布到PRG文件的完整生成链路
3.1 四步走的生成流程
我把设计器的生成过程拆成四步,方便读者理解代码走向。第一步,遍历画布上的控件树,收集所有属性、事件和数据源绑定,形成一个完整的 FormModel;第二步,把模型中记录的像素坐标统一换算成目标方言使用的字符坐标或者窗口尺寸;第三步,生成窗体骨架,包括窗口的定义、尺寸、标题、初始化段;第四步,按控件类型逐个输出按钮、标签、文本框、表格等控件语句,并把事件处理程序以桩代码形式生成到文件末尾。
以 FoxPro/dBase 风格为目标时,第三步会输出类似 SET TALK OFF、窗口初始化这些语句,第四步则拼接出整段的 @ SAY/@ GET/READ;换成 xHarbour 目标时,生成的骨架会变成 DEFINE WINDOW 结构,事件和控件按窗口类的写法组织。主流程里几乎没有业务语义,它只负责"忠实表达界面",这样生成的代码粘回老工程时,业务逻辑不会被污染。
3.2 事件桩的命名和挂接约定
生成器不能凭空知道"保存按钮点了要干什么",所以它只能生成合理的事件壳。这套工具的约定是控件名加事件类型作为方法名,比如 btnSave 的 Click 事件生成 btnSave_Click(),并在方法里留一行 TODO 注释。生成的事件桩默认写到独立文件里,而不是混在窗体定义文件里,用 #include 或按工程习惯手动挂接,这样下次重新生成界面时不会把手工写好的业务代码覆盖掉。
这一点我认为是整个工具可用性的关键。如果生成器直接把事件桩和一个窗体定义文件揉在一起,用户每次调整界面再生成一次,手写逻辑就全没了。把生成代码和手写代码分离,是这类代码生成工具必须遵守的底线,我在第 4 章会说一个相关的反面案例。
3.3 PICTURE子句和其他属性的映射
文本框的各种限制在 PRG 里靠 PICTURE 子句表达,比如只允许数字用 "999",定长字符用 "XXXX"。设计器属性面板如果直接开放 PICTURE 输入,对老手很方便,但新手容易填错。这套工具的做法是提供常用格式的预置项,同时在高级属性里允许手工修改。
映射时还有一个容易被忽略的点:不同方言对 PICTURE 的支持不完全一致,生成器的属性映射表里专门做了兼容处理,遇到目标方言不支持的格式会降级为普通输入框并给提示,而不是生成一句编译不过的代码。这个降级策略很实用,老方言的能力边界就摆在那里,硬生成不可能运行的效果不如老老实实降级。
4. 实际使用中容易翻车的三个地方
4.1 字体不一致导致控件错位
第一坑:设计器用的字体和老系统运行时的字体不一致。这类生成工具默认显示效果是开发机上的字体,但老工程很多生产技术环境的字体还是点阵宋体或者 MIS 字体,字符格尺寸完全不同。结果是设计器里看起来整整齐齐的界面,跑到老环境里标签重叠、输入框错位。
我的处理办法是:在设计器里先把字体调到目标环境一致的字体,再做一个字体度量换算的验证界面,把字符格坐标实测出来。如果你只是偶尔用一下这套工具,可以直接在模型里给字体度量加一个手动系数,测试时用真实环境截图对比。这种问题不是逻辑错误,但比逻辑错误更隐蔽,因为它只有到目标机器上才暴露。
4.2 中文工程文件的编码问题
第二坑跟中文工程特别相关。老 xBase 工程的源码文件多半是 GBK/GB2312 编码,编译环境的代码页也按这个走;而 C# 默认输出的是 UTF-8 文件,甚至带 BOM。直接把生成结果塞回老工程,轻则代码里中文注释全部乱码,重则整个编译都报错。
解决办法是在生成器的输出环节显式指定编码,让所有生成的 .prg 文件按目标工程的代码页写入。这个选项通常在设置里,默认值千万别迷信,先看一眼老工程编码再决定。我当时就是没检查,生成了一堆带 BOM 的 PRG,结果编出来的界面中文全是乱码,排查了快一个小时才发现是编码问题。
4.3 事件代码和手工代码的穿插问题
第三坑是最容易把项目搞乱的:有人觉得事件桩太简陋,直接在生成文件里补了完整业务逻辑,结果下一次拖拽调整界面重新生成,整段逻辑被覆盖,连个备份都没有。务必记住一个原则:生成器的产出必须当成"只读文件"对待,事件桩只做跳转或直接留 TODO,真正业务逻辑放到独立的手工维护文件里。
如果工具本身没有提供这种分离机制,我强烈建议二次开发时给生成文件加一个标识头——文件第一行写"此文件由设计器自动生成,请勿手工修改",同时把事件桩统一放到 *_events.prg 这种固定后缀的文件里,靠 include 方式挂接,把误操作概率降到最低。
5. 源码二次开发:我实际做的几处定制
5.1 先找到代码生成的扩展点
读源码时别一个类一个类地读,先找生成器接口和控件模型。给现有生成器类画一张继承关系图,再看属性映射表在哪个类里维护,这就是改代码的两扇门:想加新的生成目标,继承生成器接口;想加控件类型,扩充控件模型和属性面板的注册表。我第一次定制时只加了两个方法,就成功让一个原有控件多输出一条初始化语句,整个流程没有动主界面代码,这种体验说明分层设计做得比较干净。
5.2 给现有控件补充方言输出
以我加的日期字段为例。在控件模型里新增 DatePicker 类型,属性面板注册它后会自动显示日期格式属性;生成器里新增一个分支负责输出目标方言的日期输入控件——如果目标方言不支持原生日期控件,就降级成文本框加日期合法性校验语句。这套扩展流程最麻烦的是属性面板要重新编译注册,你在 C# 源码里加完类型后,记得刷新程序集重新打开工程,属性面板的缓存如果不去掉,新控件不会立刻出现。
5.3 接入老工程的组织建议
最后说接入方式。别一上来就让所有开发人员用新设计器改线上模块,风险太大。我建议先拿一个低频改动的简单窗体做试点,生成代码后放在独立的 include 文件里,和手写部分物理隔离;跑通一个窗体后,再总结出这套工具在这个工程里的标准用法——字体、编码、命名约定、事件挂接方式,写成一份内网文档。工具本身再有潜力,真正决定它能走多远的,是能不能和现有工程的编码习惯安全接轨。
我自己用下来最大的感受是:这类 xBase 老系统不是没有救,缺的恰恰是这种不改变运行时、只优化编辑体验的"中间工具"。PrgFormDesigner 的整套设计——中间模型、方言生成器、坐标换算、事件桩分离——单独拆出任何一块,对做代码生成类工具的人都有参考价值。如果你手上也有类似的老工程,与其硬着头皮继续数格子,不如花一个下午把这套源码跑起来,改一个简单界面感受一下,或许你就再也不想回到那个对着坐标发呆的下午了。
本文还有配套的精品资源,点击获取