简介:Quick Report 4.05 for BCB6 D7 是一套面向 Borland C++ Builder 6 与 Delphi 7 的经典报表生成控件及完整源码包,重点解决了自定义纸张尺寸下的打印布局与输出精度问题,适合使用 BCB6/D7 开发桌面应用、需要集成专业报表与打印功能的开发者。压缩包仅 247KB,包含 65 个文件,以 34 个 Pascal 单元(.pas)和 15 个窗体定义(.dfm)为主,另有 .dpk/.bpk 工程文件、资源文件(.res/.dcr)及少量 C++ 桥接代码,模块划分清晰,便于按需引用或二次封装。已有 280 人学习下载。资源附带了 Quick Report 4.05 全部源代码,可深入查看从基础打印到 PDF/XML 导出等扩展功能的具体实现;同时提供 qreport.dcr、qrabout.dfm、qrprev.dfm 等资源文件,内含组件图标、关于对话框、预览界面等原始设计定义。对于仍在维护 BCB6/D7 老项目、或希望基于经典报表引擎定制打印逻辑的开发者而言,这份包含可编译工程与完备窗体的控件包,既可用于直接集成,也可作为理解早期 Delphi/C++ Builder 报表架构的参考资料。 QuickReport 4.05,一看版本号就暴露年龄了。在 BCB6(C++ Builder 6)和 D7(Delphi 7)这两个 IDE 横扫桌面开发的年代,QuickReport 就是它们身上标配的报表组件,装完 IDE 自带 QReport 页的那种。前阵子一个跑了十来年的老系统要求加一张按部门统计的工资表,客户明确说继续用 QuickReport 4.05,原因是同事都会、不用买授权。没办法,我把这套东西重新翻出来,从安装到出报表又完整走了一遍,中间还踩了几个坑。这篇文章就把这些实操内容整理出来,给同样在维护老 VCL 项目的朋友做个参考。
QuickReport 解决的核心问题是什么?在 VCL 程序里把数据库记录组织成可预览、可打印、可导出的报表。它适合正在维护 BCB6/D7 老代码的开发者,也适合刚接触 VCL、想理解报表数据循环输出的入门者。整个 API 不复杂,但有些内部逻辑不搞清楚会绕不少弯路。
1. 为什么到今天还要用 QuickReport 4.05
1.1 当年它几乎是 VCL 报表的代名词
QuickReport 出现的时间点很有意思。2000 年前后桌面应用爆发式增长,银行、物流、仓管、财务这些行业对清单打印的需求大得惊人。当时的 Delphi 和 C++ Builder 装机量很高,而 QuickReport 一度被 Borland 集成进 IDE 的分发包里,开发者打开组件面板就能找到,不需要额外装任何东西。
数据感知是那个时代组件设计的核心思路。TQRDBText 直接绑定 DataSource 和 DataField,报表引擎自动遍历数据集,每条记录输出一行 Detail Band。开发者不用写绘制代码,不用管分页,甚至不用管打印机 DC,这在当时把报表开发门槛压到了极低。对比后来有些同事拿着 .NET 的 ReportViewer 调了半天还没出来一张单,你就会理解 QuickReport 为什么能活那么久。
1.2 老项目不换报表引擎的真实原因
很多人问我:为什么不建议客户迁移到 FastReport 或者 Rave?我通常先反问一个问题:你的老系统里有多少张报表?如果答案是几十上百张,那迁移成本根本不在报表本身,而在每一张报表对应的历史客户数据、打印格式、验收记录。QuickReport 4.05 在老项目里最大的优势就是稳定和可控,它不依赖额外的 DLL,二进制组件可以直接注册进 BCB6/D7,线上环境部署就是把编译好的 EXE 拷过去,没有一堆运行时依赖要处理。
而且 4.05 这个版本有源码,这一点很关键。遇到打印走样、分组小计不对这种问题,你可以直接打开 qrexpr.pas 看表达式解析逻辑,打开 qrprntr.pas 看打印机处理逻辑。这个源码在手的效果,等于你有一本自带注释的底层文档,出问题不用等厂商更新,自己就能定位。
当然它的短板我也要说清楚:复杂报表做起来累、图形图表几乎等于没有、单元格合并这类操作很反人类。但现实是,传统行业里绝大多数业务报表就是清单、明细、汇总,根本用不到那些花哨功能。在 BCB6/D7 这个技术栈里,QuickReport 4.05 依然是投入产出比最高的选择。
2. 安装环节:BCB6 和 D7 的差异处理
2.1 拿到安装包后的第一件事
QuickReport 4.05 的压缩包解压后,一般是 Source 目录加一堆 .pas、.dfm、.dcr 文件,偶尔带一个 install.txt 或 README。先别急着点安装,第一步是把整个目录放到纯英文路径下,比如 C:\QuickReport。千万别放中文目录或者带空格的目录,BCB6 的编译器在解析这些路径时问题很多,报错信息还很隐秘,你根本想不到是路径的锅。
然后打开 IDE,确认版本。BCB6 和 D7 虽然都是 VCL,但组件包机制有一点差别:Delphi 7 走的是标准 .dpk 流程,BCB6 则是通过组件包编译后自动生成 .hpp 头文件。遇到老外写的纯 Delphi 源码,BCB6 偶尔会提示找不到某些 .hpp,这通常不是代码问题,而是头文件生成失败或者 Library Path 没有配置到位。
提示:安装前先关闭当前打开的所有工程,并备份 IDE 的 Lib 目录。我习惯用压缩软件打一个带时间戳的 zip,装挂了五分钟就能回滚,这个习惯后来救过我好几回。
2.2 在 BCB6 里注册组件的完整步骤
我建议走 Component 菜单而不是手动改包文件,操作是:
- 打开 C++ Builder 6,点击 Component -> Install Component。
- 在弹窗里选择 Into existing package,Unit file name 一栏浏览选择安装包 Source 目录里的主组件单元。不同版本文件名不太一样,常见的是 QuickRpt.pas 或 qrpt.pas,以 README 标注为准。
- Package file name 填一个自己定义的包名,比如 dclQuickReport40.bpk,输出目录放在 C:\QuickReport 下。
- 点 OK 让 IDE 开始编译。如果中途报错找不到 .dcr 或 .res,去 Tools -> Environment Options -> Library 里把 Source 路径追加到 Library Path,再重新编译。
- 编译完成后,到 Component -> Install Packages 里确认新包已被勾选,确定后组件面板会刷出一个 QReport 页。
这一步最常见的翻车点有两个:一是路径带空格导致编译失败,二是包名和已有组件冲突。第二个问题一般出现在机器上装过其他版本 QuickReport 的情况,解决办法是先卸载旧包再装新的,别图省事直接 Add。
2.3 D7 下的安装更省事
Delphi 7 里安装这个组件会顺畅很多。版本包里如果直接带 .dpk 文件,双击打开,在 Package 窗口点 Compile,再点 Install,组件面板就会出现。如果打开 dpk 时报找不到单元,同样是 Library Path 的问题,按照上面说的路径流程处理一遍即可。
BCB6 和 D7 装完以后,除组件页名称一样,其他没有太大区别。唯一的细节是:用 BCB6 的工程里,如果代码中直接 include 了 QuickReport 的某个头文件,要注意头文件名的大小写和实际 gen 出来的一致,Windows 下虽然不区分大小写,但个别老版本的头文件缺失时,编译错误会非常费解。
3. 报表的核心逻辑:Band 与数据集之间怎么配合
3.1 把 Band 想象成一张表的若干行区域
QuickReport 设计上最核心的概念就是 Band,也就是报表区域。你可以把一张报表想象成一张多行的稿纸,每个 Band 对应稿纸上的一行区域,只是有的行重复出现,有的行只出现一次。QuickRep 是报表的外壳,上面摆的 TQRBand 控制每个区域的输出时机。
常用 Band 类型对应关系如下:
| Band 类型 | 出现时机 | 典型用途 |
|---|---|---|
| PageHeader | 每一页顶部 | 页眉、公司名称、页码 |
| Title | 整份报表只出现一次 | 报表总标题 |
| ColumnHeader | 明细区上方 | 列名表头 |
| Detail | 每条记录重复一次 | 数据明细行 |
| GroupHeader | 每组开始处 | 分组标题、分组字段 |
| GroupFooter | 每组结束处 | 小计、人数统计 |
| Summary | 整份报表结束处 | 总计、全表汇总 |
| PageFooter | 每一页底部 | 页码、打印日期 |
添加 Band 的方法是右键 QuickRep,在弹出菜单里选择对应 Band 类型。刚开始用的时候很容易把所有区域都放在 PageHeader 里,导致数据行一多就重复打印表头,把 Band 语义理解对之后这个问题就会自然消失。
3.2 数据循环是怎么发生的
QuickRep 的 DataSet 属性指向一个 TDataSource 后,预览时报表引擎会自动遍历这个数据集的全部记录,每取出一条记录就渲染一次 Detail Band。这个循环是引擎内部处理的,完全不用手写 while not eof。
主从报表则多一层联动。父表挂在 QuickRep.DataSet 上,子表通过 QRSubDetail 的 DataSet 指定,同时把 QRSubDetail 的 Master 属性指向 QuickRep。运行时,父表每输出一条记录,子表会重新查询一次并按关联字段输出明细。新手最容易犯的错是把子表也挂到 QuickRep.DataSet 上,这样两张表的数据会交叉成笛卡尔积,行数爆炸,排查起来非常迷惑。
3.3 常用组件选型速查
| 组件 | 作用 | 使用位置 |
|---|---|---|
| TQRLabel | 静态文本 | 标题、固定文字 |
| TQRDBText | 绑定字段数据显示 | Detail Band |
| TQRExpr | 表达式计算 | 小计、合计、拼接 |
| TQRShape | 画线、画矩形 | 边框、分隔线 |
| TQRMemo | 多行静态文本 | 合同条款、说明 |
| TQRDBImage | 数据库图片 | 照片、签名 |
| TQRGroup | 分组头 | GroupHeader |
| QRSubDetail | 子报表明细 | 主从报表 |
3.4 QRBand 排序的隐藏规则
Band 之间的相对顺序其实会影响渲染结果。设计器里可以随便拖,但实际输出时 QuickReport 会按枚举顺序排列,不在设计顺序排列。我那次做工资表,把 Summary Band 拖到了 PageFooter 下面,结果合计一直不出现,后来把 Summary 拖到 PageFooter 上方才正常。
4. 实操:一张带分组小计的工资表
4.1 先想清楚需求和数据结构
需求很简单:打印员工工资单,按部门分组,每个部门结束显示该部门的人数和工资小计,最后整份报表显示全部合计。这个需求非常适合入门,因为它覆盖了 Band、分组、表达式计算、运行时过滤四个核心点。
先准备一份 SQL,注意分组字段必须排序:
SELECT dept_name, emp_no, emp_name, base_salary, bonus FROM employee ORDER BY dept_name, emp_no;这里有一个非常重要的细节:QuickReport 的分组逻辑是“相邻相同才归为一组”,不是全局分组。如果数据没有按分组字段排序,同一个部门会分散成好几个不连续的组,小计也会跟着错乱。我见过不少人在问为什么分组统计不对,最后发现全是 SQL 里少了 ORDER BY。
4.2 一步一步搭报表
在窗体上放好 TADOConnection、TADOQuery、TDataSource,查询指向上面那条 SQL,DataSource 指向 TADOQuery。然后把 QuickRep 的 DataSet 设为这个 DataSource。
接下来按顺序添加 Band:
- 右键 QuickRep,添加 Title Band,放一个 TQRLabel,Caption 设成“员工工资表”,字体选宋体加粗 16 号。
- 添加 ColumnHeader Band,放五个 TQRLabel 分别写“部门”“工号”“姓名”“基本工资”“奖金”。
- 添加 Detail Band,放五个 TQRDBText,DataField 分别对应五个字段。
- 在 QuickRep 的 DataGroup 属性里新增一个分组项,设置分组字段为 dept_name。
- 添加 GroupHeader Band,放一个 TQRDBText 绑定 dept_name,再放一个 TQRLabel 写“部门:”。
- 添加 GroupFooter Band,放 TQRExpr 表达式分别写:
- 人数:
count - 小计:
sum(base_salary + bonus)
- 人数:
- 添加 Summary Band,放 TQRExpr 表达式:
- 总人数:
count - 全表合计:
sum(base_salary + bonus)
- 总人数:
- 最后加 PageFooter,放 TQRLabel 写“第 ”加 TQRExpr
PageNumber,再写“ 页”。
这里要注意 TQRDBText 的 AutoSize 和 Autostretch 属性。AutoSize 决定控件是否随字体自动变大,Autostretch 决定行高是否随内容自动增高。在 Detail Band 里如果字段内容可能换行,通常要把 Autostretch 设为 True,否则长文本会被截断,打印出来数据不完整。
4.3 运行时动态过滤和预览
实际场景里往往需要按条件筛选,比如只打印某个部门。代码在 BCB6 下这样写:
ADOQuery1->Close(); ADOQuery1->SQL->Text = "SELECT dept_name, emp_no, emp_name, base_salary, bonus " "FROM employee WHERE dept_name LIKE :Dept ORDER BY dept_name, emp_no"; ADOQuery1->Parameters->ParamByName("Dept")->Value = "%" + EditDept->Text + "%"; QuickRep1->Preview();用 Preview 而不是 ShowModal,这样用户在预览窗口里可以直接选打印机、设份数、缩放查看,交互是现成的。还有一个 Prepared 属性,数据量大的时候先在代码里调用QuickRep1->Prepare()再 Preview,可以避免预览时重复取数,速度提升很明显。
4.4 边框和外观整理的几个小经验
我习惯在 ColumnHeader Band 底部放一条 TQRShape 的横线,形状选 QRSRectangle,宽度设为 0.6,搭出来的稿纸感很清爽。明细行之间的分隔线不建议用控件边框,因为 TQRDBText 的边框跟随控件大小,缩进不好控,用 TQRShape 画线最灵活。
字体这块务必统一设置。BCB6 自带的默认字体在某些中文 Windows 上会把中文字段渲染成乱码或方块,把报表上所有控件的 Font.Charset 设置成 GB2312_CHARSET,Font.Name 设置为“宋体”或“微软雅黑”,基本就不会再出中文问题。
5. 实际排坑记录和日常维护经验
5.1 编译与安装类问题
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
| 编译时报找不到 qr*.pas | Library Path 没配置 | 把 Source 目录追加进 Library Path |
| 安装后组件面板没有 QReport | 包未勾选或编译失败 | 检查 Install Packages 列表 |
| BCB6 下提示找不到 .hpp | 头文件未生成成功 | 删除包后重新编译,确认路径无空格 |
| 打开 .dpk 报单元缺失 | 单元文件路径未加载 | 检查 Environment Options 的 Library Path |
5.2 预览和打印不符,先查纸型
预览和打印不一致是我遇到最多的投诉。排查顺序非常固定:先看 QuickRep 的 Page 属性,PaperSize 是 A4 还是 Letter,Length 和 Width 是否和物理纸张一致。很多公司局域网里的共享打印机默认是 Letter,而虚拟预览用的是 A4,出来的内容就会“看起来对不上”。
我的做法是在 BeforePrint 事件里强制设一次纸型:
void __fastcall TFormReport::QuickRep1BeforePrint(TObject *Sender) { QuickRep1->Page->PaperSize = A4; QuickRep1->Page->Length = 14000; QuickRep1->Page->Width = 9900; }这样不管用户在系统里选了什么打印机,报表生成时始终保持同一个页面基准。这个习惯极大降低了“在自己机器上正常、客户机器上错乱”的概率。
5.3 分组错乱和小计为 0
分组错乱九成是 SQL 没排序,前面已经说过,把 ORDER BY 加上就好。小计为 0 则要看字段类型和表达式写法。TQRExpr 里 sum 只接受数值类型,如果字段是字符串类型,先转换再计算。注意空字段问题,sum 遇到空行会直接为 0,不会报错,所以要先确认数据源里没有脏数据。
5.4 一个容易被忽略的 OnNeedData 问题
QuickReport 支持通过 OnNeedData 事件动态生成数据,我用它做过拼接数据的临时表报表。但 OnNeedData 事件一定不能放耗时操作,比如打开数据库连接、大循环、网络请求。报表引擎逐条取数时这个事件会被反复调用,性能问题会被放大几十倍,预览直接卡死。如果有耗时逻辑,提前在内存里准备好数据,事件里只做赋值。
5.5 值得坚持的一个维护习惯
最后分享一个个人习惯:拿到 QuickReport 4.05 之后,先花半天时间把源码里的 qrexpr.pas、qrprntr.pas、qrextra.pas 三个文件通读一遍。这套代码放在今天看不算完美,但胜在逻辑清楚,很多“怪问题”翻一遍源码就能定位到具体语句。对还在维护 BCB6/D7 老项目的团队来说,这个阅读成本换来的排障速度,远比表面上省下的几小时更有价值。
本文还有配套的精品资源,点击获取