从 QuickReport 迁移到 sgcReports
sgcReports 是 sgcUI 的带区报表引擎:TsgcReport 保存布局,TsgcReportEngine 绑定数据,TsgcReportPreviewVCL 显示页面,PDF 导出会写出带标签的 PDF/UA。本套件把您使用的 QuickReport 带区和控件对应到 sgcReports 中的对应项,展示来自可编译演示的代码,并如实列出无法对应的部分。
sgcReports 是 sgcUI 的带区报表引擎:TsgcReport 保存布局,TsgcReportEngine 绑定数据,TsgcReportPreviewVCL 显示页面,PDF 导出会写出带标签的 PDF/UA。本套件把您使用的 QuickReport 带区和控件对应到 sgcReports 中的对应项,展示来自可编译演示的代码,并如实列出无法对应的部分。
窗体上的一个 TQuickRep、带区、带区上的 QR 控件,以及应用中的预览、打印或 PDF 按钮。
QuickReport 又重新有人维护了。在“QuickReport is back”(Embarcadero 博客,Ian Barker,2024 年 11 月 14 日)中,Embarcadero 说明该产品在首席开发者去世后陷入危机,后来与 Embarcadero 合作伙伴 Barnsten 一起重新推出,现在支持 RAD Studio 的所有版本,Delphi 和 C++Builder 从 Delphi 7 起。Barnsten QuickReport 页面(2026-10-20 查阅)列出了嵌入 TrueType 字体的 PDF 筛选器、PDF/A 支持,以及 HTML、XML/XSLT、XLS 和 XLSX 导出筛选器,并标出 QuickReport Pro 5 开发者版价格为 2.155,00 欧元(不含增值税)。因此本页讲的不是一个已死的产品,也不是缺少 PDF 按钮。它面向的是想要该功能列表中没有提到的内容的团队。
下面每一条 sgcReports 的说法都能在随产品提供的源代码中找到。QuickReport 一侧写“未列出”,表示厂商页面没有提及,而不是“不支持”。
TsgcReportPDFExport 默认写出带标签的 PDF/UA(PDFUA 为 True,PDFUAPart 选择第 1 部分或第 2 部分),包含阅读顺序、来自条目 TagRole 的标题角色、来自 AltText 的替代文本,以及文档的 Language 和 Title。PDFA 可为归档添加 PDF/A-3b、PDF/A-3a 或 PDF/A-4。它在每个版本中都有,包括社区版。Barnsten 的功能页面没有列出 PDF/UA 和带标签的 PDF。自从《欧洲无障碍法案》(指令 (EU) 2019/882)生效以来,无障碍文档变得更加重要,该法案涵盖电子书、电子商务和银行服务等产品与服务。
您的用户无需重新构建就能修改报表:sgcShowReportDesigner(Engine) 打开 VCL 设计器,也可以嵌入 TsgcVCLReportDesigner 面板;sgcShowReportDesignerFMX(Engine) 在 FireMonkey 中实现同样的功能(Delphi XE6 及更高版本,桌面)。VCL 设计器会跟随窗体的 DPI 变化(其检查器、图标和设计画面会在 ChangeScale 中重新缩放)。仅限注册版。Barnsten 的功能页面没有列出面向最终用户的设计器;过去那是一个单独的第三方产品 QRDesign。关于高 DPI,2022 年 4 月 4 日的一条 Delphi-PRAXiS 回复称 QuickReport 6 可以用 Delphi 11.1 构建,但不支持高 DPI;请向厂商确认当前版本的情况。
交叉表、条形码、子报表和富文本都是同一个带区引擎的条目类型。借助桥接单元,报表还可以把 sgcCharts 图表和 sgcPivotGrid 透视表作为条目打印,并在 PDF 中以矢量内容绘制(注册版)。导出:每个版本都支持 PDF、XLSX、CSV、HTML 和 PNG,注册版还支持 DOCX。
报表可以保存为 JSON 并从 JSON 加载(TsgcReport.SaveToFile、LoadFromFile),因此布局可以存放在数据库或版本控制中。同一份定义可以在 .NET 版 sgcReports 中加载(NuGet esegece.sgcReports,.NET Standard 2.0、.NET Framework 4.7.2 和 .NET 6 及更高版本),因此 Delphi 报表可以被 .NET 服务复用。
报表是重建的,不是导入的。 sgcReports 无法打开 QuickReport 的 .dfm:没有 QuickReport 导入器。每个报表都要重新创建,要么像下面的演示那样用代码,要么在 sgcReports 设计器中(注册版)。本页的对应表和步骤让这项工作可以逐带区机械地完成。sgcReports 按开发者授权,价格从 €199 起,附带完整源代码和一年更新,同时也包含在 sgcUI 中。
下面每个 sgcReports 名称都在随产品提供的源代码中,演示用到的名称都可以编译。QuickReport 的名称仅供参考;查阅本页时没有安装 QuickReport。
| QuickReport | sgcReports | 版本 |
|---|---|---|
TQuickRep | TsgcReport(Pages、Styles、Units、Title)和 TsgcReportEngine(Report、AddDataSource、Prepare、Variables、OnGetVariable) | 全部 |
TQuickRep.DataSet | TsgcReportDataSetSource(SourceName、DataSet)通过 AddDataSource 注册;带区的 DataSourceName 指向它 | 全部 |
TQuickRep.Page(纸张、页边距) | TsgcReportPage.PaperSize (rpsA3, rpsA4, rpsA5, rpsLetter, rpsLegal, rpsCustom), Margins.Left, Top, Right, Bottom | 全部 |
TQuickRep.ReportTitle | TsgcReport.Title,以及用于 PDF 元数据的 TsgcReportPDFExport.Title | 全部 |
TQRBand,BandType 为 rbTitle、rbPageHeader、rbColumnHeader | TsgcReportBand,BandType 为 rbtReportTitle、rbtPageHeader、rbtColumnHeader | 全部 |
rbDetail | rbtMasterData,DataSourceName 设为主数据源 | 全部 |
rbGroupHeader, rbGroupFooter | rbtGroupHeader, rbtGroupFooter | 全部 |
rbSummary, rbPageFooter | rbtReportSummary, rbtPageFooter | 全部 |
TQRChildBand | rbtChild,带 TsgcReportBand.ChildBandName | 全部 |
TQRSubDetail | 一个 rbtDetailData 带区(DataSourceName = 明细数据源),以及明细 TsgcReportDataSetSource 上的 MasterSource、MasterFields、DetailFields | 全部 |
TQRGroup.Expression | rbtGroupHeader,带 GroupExpression | 全部 |
TQRGroup.ReprintOnNewPage | TsgcReportBand.RepeatOnEveryPage | 全部 |
TQRGroup.FooterBand | 下一个 rbtGroupFooter 带区;第一个页脚关闭最内层的分组 | 全部 |
TQRBand.ForceNewPage | TsgcReportBand.StartNewPage | 全部 |
TQRLabel (Caption) | Kind = rikText 为 Text.Text 的条目 | 全部 |
TQRDBText (DataField, Mask) | rikText,Text.Text = '[Source.Field]';Mask 变成 Text.DisplayFormat | 全部 |
TQRExpr (Expression) | rikText,带 '[expression]',例如 '[Lines.Qty * Lines.Price]';函数如 IIF、FormatFloat、FormatDateTime、Round、Upper | 全部 |
TQRExpr,带 SUM、COUNT、AVERAGE、MIN、MAX | [Sum(x)]、[Count()]、[Avg(x)]、[Min(x)]、[Max(x)];在分组页脚中按分组重置,在页脚中按页范围 | 全部 |
TQRSysData qrsPageNumber | [Page],以及用于“第 X 页,共 Y 页”的 [TotalPages] | 全部 |
TQRSysData qrsDate, qrsTime, qrsDetailNo | [Date]、[Time],配合 Text.DisplayFormat;[Line] 或 [RecNo] | 全部 |
TQRShape (qrsRectangle, qrsCircle, qrsRoundRect) | rikShape,Shape.ShapeKind 为 rskRectangle、rskEllipse、rskRoundRect;Shape.PenColor、FillColor、PenWidth | 全部 |
TQRShape qrsHorLine | rikLine,带 Height = 0 | 全部 |
TQRImage, TQRDBImage | rikImage,带 Image.FileName 或 Image.Picture,或 Image.DataField;Stretch、KeepAspectRatio | 全部 |
TQRRichText, TQRDBRichText | rikRichText,带 RichText.Markup:类 HTML 标记,不是 RTF,并且可以包含 [expressions] | 全部 |
运行时设置的 Enabled、BeforePrint 事件 | 带区和条目上的 VisibleExpression;TsgcReportEngine.OnBeforePrintBand | 全部 |
Prepare, Preview | TsgcReportEngine.Prepare 返回 TsgcReportPreparedPages;TsgcReportPreviewVCL.Prepare 在窗体上的控件中显示它们 | 全部 |
Print, PrinterSetup | TsgcReportPreviewVCL.Print(aTitle),在标准 TPrintDialog 之后 | 全部 |
带 PDF 筛选器的 ExportToFilter | TsgcReportPDFExport.SaveToFile(aPages, aFileName) 或 SaveToStream | 全部 |
| PDF/A | TsgcReportPDFExport.PDFA = rpaA3b、rpaA3a 或 rpaA4 | 全部 |
| 未列出 | PDF/UA:TsgcReportPDFExport.PDFUA(默认 True)、PDFUAPart、Language、Title;条目 TagRole 和 AltText | 全部 |
| 未列出(QRDesign 是第三方附加产品) | 面向最终用户的设计器:sgcShowReportDesigner、TsgcVCLReportDesigner;FireMonkey 中为 sgcShowReportDesignerFMX | 注册版 |
每个片段都复制自 MigrateFromQuickReport 演示,该演示可用 Delphi 7 和 Delphi 13(Win32 和 Win64)编译,在社区版中也可编译。注释中写明了它所替代的 QuickReport 代码。
迁移前:窗体上的一个 TQuickRep,报表及其数据都在一个组件中。迁移后:布局、引擎和预览是各自独立的组件。
object sgcPreview: TsgcReportPreviewVCL
Left = 0
Top = 44
Width = 900
Height = 676
Align = alClient
TabOrder = 1
Engine = sgcEngine
end
object sgcReport: TsgcReport
Left = 40
Top = 80
end
object sgcEngine: TsgcReportEngine
Report = sgcReport
Left = 120
Top = 80
end迁移前:用于发票的 TQuickRep.DataSet,以及带有自己的 DataSet 用于明细行的 TQRSubDetail。迁移后:每个数据集一个数据源,明细通过字段链接到主表。FormCreate 用 sgcEngine.AddDataSource 注册两者。
object srcInvoices: TsgcReportDataSetSource
SourceName = 'Invoices'
DataSet = cdsInvoices
Left = 40
Top = 208
end
object srcLines: TsgcReportDataSetSource
SourceName = 'Lines'
MasterSource = srcInvoices
MasterFields = 'Number'
DetailFields = 'Invoice'
DataSet = cdsLines
Left = 120
Top = 208
end演示通过窗体上的这个小方法添加每一段文本,因此下面的片段保持简短。
function TformMain.AddText(aBand: TsgcReportBand; const aText: string;
aLeft, aTop, aWidth: Double; const aStyle: string): TsgcReportItem;
var
oStyle: TsgcReportStyle;
begin
// one item replaces TQRLabel (literal text), TQRDBText ([Source.Field])
// and TQRExpr ([expression]); literal text and [expressions] can be mixed
Result := aBand.Items.Add;
Result.Kind := rikText;
Result.Name := aBand.Name + IntToStr(aBand.Items.Count);
Result.Text.Text := aText;
Result.Left := aLeft;
Result.Top := aTop;
Result.Width := aWidth;
Result.Height := 14;
Result.StyleName := aStyle;
oStyle := sgcReport.Styles.FindStyle(aStyle);
if Assigned(oStyle) and (oStyle.Font.Size * 1.25 > Result.Height) then
Result.Height := oStyle.Font.Size * 1.25;
end;迁移前:TQuickRep.Page、一个标题带区和一个显示日期的 TQRSysData。迁移后:TagRole 在 PDF/UA 标签树中把标题标记为标题。
// QuickReport: TQuickRep.Page (PaperSize, LeftMargin, TopMargin, ...)
oPage := sgcReport.Pages.Add;
oPage.Name := 'Invoices';
oPage.PaperSize := rpsA4;
oPage.Margins.Left := 40;
oPage.Margins.Top := 40;
oPage.Margins.Right := 40;
oPage.Margins.Bottom := 40;
// TQRBand BandType = rbTitle: printed once, on the first page
oBand := AddBand(oPage, rbtReportTitle, 'Title', 40);
AddText(oBand, 'Invoices by customer', 0, 0, 350, 'Title').TagRole :=
rtrHeading1;
AddText(oBand, 'Printed on', 315, 6, 120, 'Right');
// TQRSysData Data = qrsDate
oItem := AddText(oBand, '[Date]', 440, 6, 75, 'Right');
oItem.Text.DisplayFormat := 'yyyy-mm-dd';迁移前:客户上的一个 TQRGroup、由 TQRDBText 控件组成的明细带区,以及带有用于行金额的 TQRExpr 的 TQRSubDetail。迁移后:
// TQRGroup Expression = 'Invoices.Customer', ReprintOnNewPage = True.
// The column titles are TQRLabel components in QuickReport
oBand := AddBand(oPage, rbtGroupHeader, 'CustomerHeader', 42);
oBand.GroupExpression := 'Invoices.Customer';
oBand.RepeatOnEveryPage := True;
AddText(oBand, '[Invoices.Customer]', 0, 4, 515, 'Group').TagRole :=
rtrHeading2;
AddText(oBand, 'Invoice / product', 0, 24, 200, 'Bold');
AddText(oBand, 'Date / qty', 210, 24, 120, 'Bold');
AddText(oBand, 'Price', 345, 24, 70, 'BoldRight');
AddText(oBand, 'Amount', 425, 24, 90, 'BoldRight');
AddLine(oBand, 40);
// TQRBand BandType = rbDetail: one band per record of TQuickRep.DataSet.
// Each [Source.Field] text item replaces a TQRDBText with DataField set
oBand := AddBand(oPage, rbtMasterData, 'Invoice', 18);
oBand.DataSourceName := 'Invoices';
AddText(oBand, '[Invoices.Number]', 0, 3, 200, 'Bold');
oItem := AddText(oBand, '[Invoices.InvoiceDate]', 210, 3, 120, 'Bold');
oItem.Text.DisplayFormat := 'yyyy-mm-dd';
oItem := AddText(oBand, '[Invoices.Amount]', 425, 3, 90, 'BoldRight');
// TQRDBText.Mask
oItem.Text.DisplayFormat := '#,##0.00';
// TQRSubDetail DataSet = Lines, linked to the invoice: the link is
// MasterSource / MasterFields / DetailFields of srcLines (see the form)
oBand := AddBand(oPage, rbtDetailData, 'Line', 14);
oBand.DataSourceName := 'Lines';
AddText(oBand, '[Lines.Product]', 15, 1, 195);
AddText(oBand, '[Lines.Qty]', 210, 1, 40, 'Right');
oItem := AddText(oBand, '[Lines.Price]', 345, 1, 70, 'Right');
oItem.Text.DisplayFormat := '#,##0.00';
// TQRExpr Expression = 'Lines.Qty * Lines.Price'
oItem := AddText(oBand, '[Lines.Qty * Lines.Price]', 425, 1, 90, 'Right');
oItem.Text.DisplayFormat := '#,##0.00';迁移前:分组页脚带区中带 COUNT 和 SUM 的 TQRExpr 组件。迁移后:
// TQRBand BandType = rbGroupFooter (TQRGroup.FooterBand). Aggregates are
// reset for every group, like TQRExpr with ResetAfterPrint = True
oBand := AddBand(oPage, rbtGroupFooter, 'CustomerFooter', 30);
AddLine(oBand, 2);
// TQRExpr Expression = 'COUNT' and 'SUM(Lines.Qty)'
AddText(oBand, '[Count()] invoices, [Sum(Lines.Qty)] items', 0, 8,
250, 'Bold');
AddText(oBand, 'Customer total', 260, 8, 155, 'BoldRight');
// TQRExpr Expression = 'SUM(Invoices.Amount)'
oItem := AddText(oBand, '[Sum(Invoices.Amount)]', 425, 8, 90, 'BoldRight');
oItem.Text.DisplayFormat := '#,##0.00';迁移前:带有 TQRShape、总计和 TQRRichText 的汇总带区。迁移后:富文本是类 HTML 标记。
// TQRBand BandType = rbSummary: printed once, after the last record
oBand := AddBand(oPage, rbtReportSummary, 'Summary', 90);
oBand.KeepTogether := True;
// TQRShape Shape = qrsRectangle, Brush.Color and Pen.Color
oItem := oBand.Items.Add;
oItem.Kind := rikShape;
oItem.Name := 'SummaryBox';
oItem.Top := 10;
oItem.Width := 515;
oItem.Height := 30;
oItem.Shape.ShapeKind := rskRectangle;
oItem.Shape.PenColor := $00A0A0A0;
oItem.Shape.FillColor := $00EEF3FA;
// TQRExpr Expression = 'COUNT' and 'SUM(Invoices.Amount)' in the summary
AddText(oBand, 'Grand total, [Count()] invoices', 10, 17, 300, 'Group');
oItem := AddText(oBand, '[Sum(Invoices.Amount)]', 315, 17, 190,
'GroupRight');
oItem.Text.DisplayFormat := '#,##0.00';
// TQRRichText: the text is HTML-like markup instead of RTF
oItem := oBand.Items.Add;
oItem.Kind := rikRichText;
oItem.Name := 'SummaryNote';
oItem.Top := 50;
oItem.Width := 515;
oItem.Height := 36;
oItem.StyleName := 'Base';
oItem.RichText.Markup := '<b>Migrated from QuickReport.</b> Each band of ' +
'this report replaces a <i>TQRBand</i>, each text item a <i>TQRLabel</i>, '
+ '<i>TQRDBText</i> or <i>TQRExpr</i>.';// TQRBand BandType = rbPageFooter
oBand := AddBand(oPage, rbtPageFooter, 'PageFooter', 20);
AddLine(oBand, 2);
// TQRLabel with a literal caption
AddText(oBand, 'Invoices by customer', 0, 6, 300);
// TQRSysData Data = qrsPageNumber; [TotalPages] is resolved after the
// last page is laid out
AddText(oBand, 'Page [Page] of [TotalPages]', 315, 6, 200, 'Right');迁移前:QuickRep1.Preview、PrinterSetup 和 Print。迁移后:
procedure TformMain.btnPreviewClick(Sender: TObject);
begin
// QuickReport: QuickRep1.Preview (or Prepare + a TQRPreview). sgcReports:
// the preview control on the form runs the engine and shows the pages
sgcPreview.Prepare;
lblStatus.Caption := Format('%d pages prepared', [sgcPreview.PageCount]);
end;
procedure TformMain.btnPrintClick(Sender: TObject);
begin
// QuickReport: QuickRep1.PrinterSetup + QuickRep1.Print. The print dialog
// selects the printer, Print sends the prepared pages to it
if dlgPrint.Execute then
sgcPreview.Print(sgcReport.Title);
end;迁移前:带 PDF 筛选器的 ExportToFilter。迁移后:一个开关即可写出普通 PDF 或带标签的 PDF/UA 文件。
procedure TformMain.SavePDF(aPDFUA: Boolean; const aFileName: string);
var
oPages: TsgcReportPreparedPages;
vFileName: string;
begin
// QuickReport: QuickRep1.ExportToFilter(TQRPDFDocumentFilter.Create(...))
vFileName := ExtractFilePath(ParamStr(0)) + aFileName;
// PDF/UA: a tagged PDF (headings, reading order, alternate texts); Title
// and Language are set on pdfExport in the form
pdfExport.PDFUA := aPDFUA;
oPages := sgcEngine.Prepare;
try
pdfExport.SaveToFile(oPages, vFileName);
finally
oPages.Free;
end;
lblStatus.Caption := Format('Saved %s (%d warnings)',
[vFileName, pdfExport.Warnings.Count]);
end;数据集保持不变,报表按带区逐个重建。
在普通窗体上放置一个 TsgcReport、一个 TsgcReportEngine(Report = 该报表)和一个 TsgcReportPreviewVCL(Engine = 该引擎)。
为每个数据集添加一个 TsgcReportDataSetSource(SourceName、DataSet)并调用 AddDataSource。对每个 TQRSubDetail,设置 MasterSource、MasterFields 和 DetailFields。
添加一个 TsgcReportPage,其纸张大小和页边距取自 TQuickRep.Page,并设置 TsgcReport.Units(演示中为 ruPoints),使坐标可以预测。
每个 TQRBand 对应一个 TsgcReportBand,按报表顺序排列,带区类型使用匹配的 rbt;主表和明细带区设置 DataSourceName,分组头设置 GroupExpression。
TQRLabel、TQRDBText 和 TQRExpr 变成混合字面文本和 [expressions] 的 rikText 条目。Mask 变成 Text.DisplayFormat,字体变成命名样式。
聚合变成 [Sum(x)]、[Count()]、[Avg(x)]、[Min(x)] 和 [Max(x)];TQRSysData 变成 [Page]、[TotalPages]、[Date] 和 [Time]。
Preview 变成 TsgcReportPreviewVCL.Prepare,Print 变成 Print,PDF 筛选器变成 TsgcReportPDFExport.SaveToFile。为得到无障碍 PDF,请设置 PDFUA、Title 和 Language。
可选,仅注册版:用 TsgcReport.SaveToFile 把重建的报表保存为 JSON,并让用户用 sgcShowReportDesigner 编辑它。
规划迁移之前请先阅读这些内容。
.dfm 报表无法加载。没有导入器,因此每个报表都要重建,可以用代码,也可以在设计器中。TQRRichText 使用 RTF;rikRichText 使用类 HTML 标记(b、i、u、s、sup、sub、br、p、span、font、ul、ol、li、a)。RTF 内容必须转换。TQRSubDetail 有自己的页眉和页脚带区;sgcReports 的明细带区没有。请在主键上使用分组头和页脚,或使用子带区(rbtChild、ChildBandName)。[Source.Field] 引用字段,而不是裸字段名,并使用 Pascal 风格的运算符,例如 Line mod 2 = 0。BeforePrint 事件代码不会被复用:它变成 VisibleExpression,或者引擎的 OnBeforePrintBand 或 OnGetVariable 中的代码。TsgcReportPreviewVCL.Print 打印到当前打印机。若要获得 PrinterSetup 的行为,请先显示一个 TPrintDialog,演示就是这样做的。一个典型的 QuickReport 主从发票清单,按客户分组:4 个客户的 18 张发票及其明细行,来自用代码填充的两个 TClientDataSet。
sgcUI 安装程序会把它放在 Demos\Reports\Delphi\MigrateFromQuickReport:sgcReports_Demo_MigrateFromQuickReport.dpr、frmMain.pas、frmMain.dfm 和一个 readme.html。社区版中也有。
在安装了 sgcReports 和 sgcPDF 的 Delphi 7 到 13 的任意版本中打开 .dpr,构建 Win32 或 Win64 并运行。报表在启动时生成,共三页,带“第 X 页,共 Y 页”。
Print... 显示打印对话框并打印。Save PDF 写出 invoices.pdf,Save PDF/UA 在可执行文件旁边写出带标签的 invoices_pdfua.pdf,可直接用 PDF/UA 验证器检查。
公平地说:QuickReport 又有人维护了,支持 Delphi 7 及以上版本,其报表在 Delphi 窗体设计器中设计。它的功能页面列出了 sgcReports 没有的导出筛选器,例如 XML/XSLT,而继续使用它意味着无需重建任何报表。如果您需要无障碍 PDF/UA 输出、面向用户的设计器、与 sgcCharts、sgcPivotGrid 或 .NET 服务共用的报表,并且接受把每个报表重建一次,那么 sgcReports 是合适的选择。完整功能列表请参阅 sgcReports 产品页面。