简介:QuickReport for Delphi 2010 是面向 Delphi 2010 开发者的报表组件安装包,主要解决 VCL 桌面应用中复杂报表的设计、预览与打印需求,适合已具备一定 Delphi 基础、需要为业务系统快速补充报表模块的中级开发者。压缩包共 263 个文件,约 4.74MB,以 pas 源码单元、dcu 编译单元、hpp 头文件、dfm 窗体资源为主,另含 bpl 运行包、dcp 编译包、res 资源及少量示例与说明文档,覆盖组件源码、设计期包与运行期包等完整构成。目前已有 315 人学习下载。借助包内组件,读者可在 IDE 组件面板中直接拖放设计多列布局、页眉页脚、子报表、条形码与自定义公式,并实现实时预览及 PDF、HTML、Excel 等格式导出,从而省去从零搭建报表引擎的成本,把精力集中在业务逻辑与报表样式打磨上。
1. QuickReport 在 Delphi 2010 里到底还能不能打:一份老报表组件的复活笔记
手上有个 Delphi 2010 维护项目,客户要求把十年前那套单据打印原样跑起来,源码里满屏的TQuickRep、TQRBand、TQRExpr,编译一过就报找不到单元。这不是个例,Delphi 2010 时代大量遗留系统还在跑,而 QuickReport 作为当年最顺手的报表组件,官方支持早就断档,社区里能搜到的包要么版本对不上,要么装完 IDE 直接起不来。这份 quickreport for delphi2010 资源包解决的正是这个断层:它把适配 Delphi 2010(Unicode 版 RTL、VCL 窗体体系)的 QuickReport 源码与设计期包整理成可直接编译安装的形态,让老报表在新 IDE 里重新变成可拖拽、可预览、可打印的组件。适合两类人:一是接手遗留 Delphi 项目、必须原样复现打印格式的维护工程师;二是想研究经典报表引擎分页与带区布局机制的技术人。下面按「先跑通、再改参数、最后避坑」的顺序拆。
2. 装包之前先搞懂:QuickReport 的带区模型与 Delphi 2010 的适配点
QuickReport 的核心不是「画表格」,而是「按带区(Band)纵向堆叠、按数据集游标驱动分页」。理解这一点,后面所有参数设置才有依据。Delphi 2010 的特殊性在于它全面转向 Unicode 字符串(string即UnicodeString),而老版 QuickReport 大量使用AnsiString和PChar做文本测量与打印输出,直接编译会在字符串转换和 API 声明上翻车。资源包做的适配,本质就是把这些边界改到 Unicode 语义下。
2.1 带区堆叠模型:报表页是怎么被「拼」出来的
一张 QuickReport 页面由若干TQRBand从上到下排列组成,每个 Band 有BandType属性决定它何时出现、出现几次。常见类型与触发时机如下表,这张表建议打印出来贴在显示器边上,调报表时对着看。
| BandType | 触发时机 | 典型用途 |
|---|---|---|
| rbTitle | 整份报表开头一次 | 报表大标题、单位信息 |
| rbPageHeader | 每页顶部 | 列标题、页码 |
| rbDetail | 数据集每一条记录一次 | 明细行 |
| rbGroupHeader | 分组字段变化时 | 分组小计标题 |
| rbGroupFooter | 分组结束时 | 分组汇总 |
| rbPageFooter | 每页底部 | 页脚、制表人 |
| rbSummary | 整份报表结尾一次 | 总计、签章区 |
关键在于:Band 的高度是固定的,内容超长不会自动撑高,而是被裁剪。所以明细行里放备注字段时,要么限制字数,要么用TQRRichText并接受它不参与自动分页的事实。我见过太多人把长文本塞进TQRLabel,打印出来只剩半行,回头怪组件有 bug——其实是模型没吃透。
分页由TQuickRep的Page属性组控制:PageSize选纸张(A4、Custom),Orientation选横竖,Margins设四边留白,Columns设分栏数。真正决定「一页能放几行明细」的是可用高度除以rbDetail的Height,而可用高度 = 纸张高度 − 上下边距 − 页眉页脚高度。这个除法结果取整就是每页行数,QuickReport 内部就是这么算的,没有魔法。
2.2 Delphi 2010 适配的三个真实改动点
资源包里能编译通过,靠的是三处针对性修改,这也是判断一份 quickreport 包是否「真适配 2010」的试金石。
第一处是字符串类型。老代码里TQRLabel.Caption相关处理用AnsiString拼接,Unicode 下中文会变问号或乱码。适配做法是把内部缓冲统一为UnicodeString,涉及StrPCopy、StrLen的地方换成StrPCopy的宽字符版本或直接用string赋值。第二处是打印 API 声明。Windows GDI 的TextOut、ExtTextOut在老单元里按PAnsiChar声明,Unicode 下要改成PWideChar对应版本,否则打印中文直接丢字。第三处是设计期包注册。Delphi 2010 的 IDE 对包(.bpl)的加载顺序和Register过程更敏感,资源包把设计期单元与运行期单元拆成两个包,运行期包不依赖 IDE 单元,避免「装完能编译但 IDE 一打开报表就崩」的经典问题。
提示:判断一个 quickreport 包是否适配 2010,最快的办法是看它有没有把运行期与设计期拆包,以及
QRLabel打印中文是否正常。这两点过了,基本可用。
2.3 安装步骤:从解压到组件面板出现
下面这套流程我在三台不同环境的机器上走过,顺序别乱。先确认 Delphi 2010 已正常启动过一次(首次启动会初始化注册表与包缓存),然后关闭 IDE 再操作文件。
# 假设资源解压到 D:\qr2010,目录内应有 Source 与 Packages 两个子目录 # 步骤1:把 Source 加入编译搜索路径(稍后在 IDE 里做,这里仅确认文件存在) dir D:\qr2010\Source\*.pas dir D:\qr2010\Packages\*.dpk文件确认齐全后,打开 Delphi 2010,走Component → Install Packages → Add,先加载运行期包(通常命名含Runtime或R),再加载设计期包(含Design或D)。加载设计期包时 IDE 会提示是否重新编译,选是。编译若报「找不到单元」,回到Tools → Options → Library,把D:\qr2010\Source加进Library path,注意要加在系统路径之前,避免命中 IDE 自带的旧版 QuickReport 残留。
// 验证安装:新建 VCL Forms Application,窗体上放一个 TQuickRep // 若组件面板 QuickReport 页出现且能拖出 TQuickRep,说明运行期与设计期都注册成功 procedure TForm1.FormCreate(Sender: TObject); begin QuickRep1.Page.Orientation := poPortrait; // 纵向 QuickRep1.Page.PageSize := A4; // A4 纸 QuickRep1.Page.Margins.Top := 10; // 上边距,单位毫米 QuickRep1.Page.Margins.Bottom := 10; QuickRep1.Preview; // 直接预览,验证渲染链路 end;这段代码的作用是把最小可运行链路跑通:设置纸张方向、尺寸、边距,然后调Preview触发完整渲染。参数单位是毫米,Margins四边独立设置,Preview会拉起内置预览窗体,能预览就说明 GDI 打印链路和 Unicode 文本输出都正常。如果预览空白,先查DataSet是否赋值、rbDetail是否真的绑定了字段,而不是急着怀疑包有问题。
3. 把数据接进带区:数据集绑定、表达式与分组汇总的实操
装好只是起点,报表能不能用,取决于数据怎么流进带区、字段怎么排版、汇总怎么算。这一章按「绑数据 → 排字段 → 做汇总」的顺序推进,每步都给可抄的配置和代码。
3.1 数据集绑定:Master 与 Detail 的两种接法
QuickReport 的TQuickRep.DataSet属性指向主数据集,rbDetail的BandType为rbDetail时,每取一条记录就渲染一次。这是最简接法。当报表是「主表 + 明细表」结构(比如一张单据带多行商品),需要主从联动,做法是把主数据集赋给TQuickRep.DataSet,明细 Band 上放一个TQRSubDetail,其Master属性指向TQuickRep,DataSet指向明细数据集。渲染时主表每走一条,明细数据集按MasterFields自动过滤并循环。
// 主从报表绑定示例:主表 qryOrder,明细表 qryOrderItem procedure TForm1.SetupMasterDetail; begin // 主数据集 QuickRep1.DataSet := qryOrder; // 明细子带区 qrSubDetail1.Master := QuickRep1; qrSubDetail1.DataSet := qryOrderItem; // 主从关联字段,两个数据集必须有同名字段或显式指定 qryOrderItem.MasterSource := dsOrder; // dsOrder 的 DataSet 指向 qryOrder qryOrderItem.MasterFields := 'OrderNo'; // 主表关联字段 qryOrderItem.IndexFieldNames := 'OrderNo'; // 明细表索引,提升过滤速度 end;逻辑说明:qrSubDetail1.Master必须指向TQuickRep而非主数据集,这是 QuickReport 的约定,指错了明细不会跟随主表翻页。MasterFields与IndexFieldNames配合,前者告诉明细表按哪个字段过滤,后者让明细表在该字段上有序,避免大数据量下每条主记录都全表扫描。参数上,MasterFields支持多字段用分号分隔,复合主键场景直接写'OrderNo;LineNo'。
3.2 字段排版:TQRExpr 与 TQRLabel 的分工
TQRLabel放静态文字(列标题、固定说明),TQRExpr放动态字段与计算表达式。TQRExpr.Expression里写字段名直接取当前记录值,写SUM(...)做汇总,写IF(...)做条件显示。表达式语法是 QuickReport 自带的,不是 Pascal,别把=当比较符,比较用=在表达式里是赋值语义,条件判断要用IF(条件, 真值, 假值)且条件里用=表示相等——这点和直觉相反,我第一次用就翻车,写成了 Pascal 的:=。
// 在 rbDetail 上放 TQRExpr,设置其 Expression 属性 // 取字段值 qrExprAmount.Expression := 'qryOrderItem.Amount'; // 金额格式化:保留两位小数并加千分位 qrExprAmount.Mask := '#,##0.00'; // 条件显示:数量为 0 时显示「赠品」 qrExprFlag.Expression := 'IF(qryOrderItem.Qty = 0, ''赠品'', '''')';Mask属性是排版的关键,#,##0.00表示千分位加两位小数,0.00表示至少一位整数,#表示该位有值才显示。中文常量在表达式里要用单引号包裹,且因为表达式本身是字符串,Pascal 里写要双写单引号转义。TQRExpr的AutoSize建议关掉,固定宽度避免长数字把列挤歪。
3.3 分组汇总:GroupHeader 与 SUM 的配合
分组报表的结构是:rbGroupHeader放分组标题,rbDetail放明细,rbGroupFooter放小计。分组依据由TQRGroup组件的Expression属性决定,写分组字段名即可。小计用TQRExpr的SUM表达式,作用域自动限定在当前分组内。
// 按客户分组,组头显示客户名,组尾显示该客户金额小计 qrGroup1.Expression := 'qryOrder.CustomerName'; qrGroup1.Master := QuickRep1; // 组头里的客户名 qrExprCustomer.Expression := 'qryOrder.CustomerName'; // 组尾小计:SUM 自动按当前分组累计 qrExprGroupSum.Expression := 'SUM(qryOrderItem.Amount)'; qrExprGroupSum.Mask := '#,##0.00'; // 整份报表总计放在 rbSummary qrExprTotal.Expression := 'SUM(qryOrderItem.Amount)';这里有个容易忽略的点:SUM的作用域取决于它所在的 Band。放在rbGroupFooter里就是组内累计,放在rbSummary里就是全表累计,同一个表达式字符串在不同 Band 里结果不同。如果组尾小计算出来是全表值,八成是TQRGroup.Expression没设或设错,导致分组没生效、所有记录落在同一组。调试时先把rbGroupHeader的标题打出来看分组是否真的切换,再查小计。
4. 打印与预览的边界:分辨率、中文字体与多栏输出的参数调法
报表在屏幕上好看,打出来一塌糊涂,是 QuickReport 最常见的落差。根因集中在三处:屏幕像素与打印机 DPI 的换算、中文字体的度量差异、多栏布局的列宽分配。这一章把这三块的参数讲透。
4.1 DPI 换算:为什么预览和打印对不上
QuickReport 内部用「逻辑单位」布局,最终输出时按打印机 DPI 缩放。屏幕通常 96 DPI,激光打印机 600 DPI,同一段文字在预览里占的物理宽度和打印出来可能差一截,尤其是用TQRLabel手动拖出来的固定宽度。解决办法是尽量用相对布局:列宽用百分比思路分配,字体用点(pt)而非像素,TQRLabel的AutoSize在列标题上打开、在数据列上关闭并配合Alignment对齐。
// 统一字体设置,避免预览与打印字体替换 procedure TForm1.ApplyPrintFont; begin qrLabelTitle.Font.Name := '宋体'; qrLabelTitle.Font.Size := 14; // 点为单位,跨 DPI 稳定 qrLabelTitle.Font.Charset := GB2312_CHARSET; // 中文环境显式指定字符集 qrLabelBody.Font.Name := '宋体'; qrLabelBody.Font.Size := 9; qrLabelBody.Font.Charset := GB2312_CHARSET; end;Charset显式设GB2312_CHARSET是老项目里避免中文变问号的保险做法,尤其在打印到某些老式针式打印机时。字号用点不用像素,是因为点是与设备无关的物理单位,96 DPI 和 600 DPI 下同一字号物理大小一致。如果打印出来整体偏大或偏小,先查打印机驱动里的缩放设置,再查TQuickRep.Page的Units属性(有MM、Inch、Pixels等),单位选错会让所有边距和尺寸按错误比例换算。
4.2 多栏输出:Columns 与列宽的分配逻辑
单据类报表常要一页打两栏(比如标签、小票),用TQuickRep.Page.Columns设栏数。设了Columns = 2后,QuickReport 会把可用宽度均分,明细先填满第一栏再填第二栏。这里有个坑:栏间距由Page.ColumnSpace控制,默认值可能让两栏贴在一起,需要手动调。
| 参数 | 含义 | 建议值(A4 纵向两栏) |
|---|---|---|
| Columns | 分栏数 | 2 |
| ColumnSpace | 栏间距(毫米) | 8 |
| Margins.Left/Right | 左右页边距 | 15 |
| Margins.Top/Bottom | 上下页边距 | 12 |
按上表配置,单栏可用宽度 = (210 − 15 − 15 − 8) / 2 ≈ 86 毫米,明细里所有列宽之和不能超过这个值,超了会被裁到栏外。调列宽时我一般先算总宽,再按内容重要性分配,金额列给足、备注列压缩,别指望自动换行救场。
4.3 直接打印与打印对话框
Preview走内置预览,Print直接送打印机,两者都依赖Printer单元。批量打印时用Print并配合Printer.Title设置任务名,方便在打印队列里识别。
// 直接打印,不弹预览 procedure TForm1.PrintDirect; begin QuickRep1.PrinterSetup := False; // 跳过打印机设置对话框 Printer.Title := '销售单打印'; // 打印队列显示名 QuickRep1.Print; // 触发打印 end;PrinterSetup := False表示不弹系统打印设置框,适合后台批量;若要让用户选打印机,设为True或调QuickRep1.PrinterSetup方法。Printer.Title在部分驱动上不生效,属正常现象,不影响打印内容。批量打印多张单据时,每张之间调一次Application.ProcessMessages,避免打印队列堆积导致界面假死。
5. 避坑清单:装包、中文、分页与打印的五个真实翻车记录
这一章全是血泪经验,每条按「现象 → 原因 → 解决」写,遇到对应症状直接对号入座。
现象一:装完设计期包,IDE 启动报「无法加载包」,组件面板空白。原因:设计期包依赖的运行期包未先加载,或机器上残留旧版 QuickReport 的.bpl同名冲突。解决:先在Install Packages里移除所有 QuickReport 相关条目,删掉系统目录和 IDE 的bin下同名.bpl,再按「先运行期、后设计期」顺序重装。装完重启 IDE 验证,别在同一个会话里反复装卸。
现象二:预览里中文正常,打印出来中文变问号或方块。原因:打印链路走了AnsiString版本的 GDI 调用,Unicode 字符在转换时丢失;或字体Charset未指定,打印机驱动回退到不含中文的字体。解决:确认资源包已替换打印相关单元为宽字符版本;给所有TQRLabel、TQRExpr显式设Font.Charset := GB2312_CHARSET;打印机驱动里把「字体替换」关掉。
现象三:明细行数一多,最后一页只剩表头没有明细,或明细被截断。原因:rbDetail.Height设得过大,加上页眉页脚后每页可用高度除不尽,余数不够放一行就被丢弃;或TQuickRep的Page.Margins与打印机物理边距冲突,实际可打印区域比设定的小。解决:把rbDetail.Height调到能整除可用高度的值,或改用TQRBand的ForceNewPage在特定位置强制分页;边距留足打印机不可打印区(一般上下各 5 毫米以上)。
现象四:分组小计算出来是全表总和,不是组内小计。原因:TQRGroup.Expression未设置或分组字段与数据集当前索引不匹配,导致所有记录落在同一组;或SUM表达式放在了rbSummary而非rbGroupFooter。解决:确认TQRGroup.Expression写的是分组字段名,数据集按该字段排序(IndexFieldNames或 SQLORDER BY),小计TQRExpr放在rbGroupFooter内。
现象五:批量打印几十张后程序卡死或内存持续上涨。原因:每张报表的TQuickRep实例未释放,或Preview窗体未关闭累积;数据集未在每张之间Close/Open重置游标。解决:用try...finally包住每张报表的创建与Free,批量场景复用同一个TQuickRep实例只换数据集,每张之间DataSet.First重置游标并Application.ProcessMessages。
注意:避坑的核心是「先隔离变量」。中文问题先分预览与打印,分页问题先算高度除法,分组问题先看组头是否切换。别一上来就改源码,多数问题在参数层。
6. 进阶:用代码动态生成带区,以及一套可复用的自检流程
固定报表用设计器拖拽最快,但遇到「列数由配置决定」的动态报表(比如用户自选导出字段),设计期拖拽就不够了,得用代码在运行时创建 Band 和表达式控件。这段代码我用了很多次,核心是「先建 Band 再建控件、控件 Parent 指向 Band、最后设 Expression」。
// 运行时动态生成明细带区与列 procedure TForm1.BuildDynamicDetail(Fields: TStrings); var band: TQRBand; expr: TQRExpr; i, colWidth: Integer; begin band := TQRBand.Create(Self); band.Parent := QuickRep1; // 挂到报表上 band.BandType := rbDetail; band.Height := 20; // 明细行高,单位与 Page.Units 一致 colWidth := 80; // 每列宽度,按实际可用宽度调整 for i := 0 to Fields.Count - 1 do begin expr := TQRExpr.Create(Self); expr.Parent := band; // 控件必须挂在 Band 上才参与渲染 expr.Left := i * colWidth; expr.Width := colWidth; expr.Height := band.Height; expr.Expression := Fields[i]; // 字段名直接作为表达式 expr.AutoSize := False; expr.Alignment := taLeftJustify; end; end;逻辑说明:band.Parent := QuickRep1让 Band 进入报表的带区链,expr.Parent := band让控件随 Band 一起渲染,这两步的 Parent 关系错了控件就不显示。Left按列序号乘列宽累加,Width固定,AutoSize关掉防止长内容撑破列。Fields传入字段名列表,顺序即列顺序。动态生成后记得在报表销毁时释放这些控件,TQRBand和TQRExpr的 Owner 设为Self时随窗体释放,但若报表被提前Free,要先手动清空 Band 再释放报表,否则访问违例。
自检流程我固定走四步:第一步,空数据集预览,确认带区结构和静态文字正确;第二步,单条记录预览,确认字段绑定和格式化正确;第三步,满页数据预览,确认分页位置和页脚正确;第四步,真实打印机打一张,确认中文、边距、对齐。四步里任何一步跳过,后面排查成本翻倍。从那以后我每次接老报表项目,都强制先跑这四步再动业务逻辑,省下的返工时间远比这十分钟多。希望帮到你。
本文还有配套的精品资源,点击获取