从 QuickReport 迁移到 sgcReports

sgcReports 是 sgcUI 的带区报表引擎:TsgcReport 保存布局,TsgcReportEngine 绑定数据,TsgcReportPreviewVCL 显示页面,PDF 导出会写出带标签的 PDF/UA。本套件把您使用的 QuickReport 带区和控件对应到 sgcReports 中的对应项,展示来自可编译演示的代码,并如实列出无法对应的部分。

使用 QuickReport 构建的 Delphi VCL 报表

窗体上的一个 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 增加了什么

下面每一条 sgcReports 的说法都能在随产品提供的源代码中找到。QuickReport 一侧写“未列出”,表示厂商页面没有提及,而不是“不支持”。

无障碍 PDF/UA

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)生效以来,无障碍文档变得更加重要,该法案涵盖电子书、电子商务和银行服务等产品与服务。

面向最终用户的设计器,支持 VCL 和 FireMonkey

您的用户无需重新构建就能修改报表: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 定义,以及 .NET 对应版本

报表可以保存为 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 中。

QuickReport 类与 sgcReports 对应项

下面每个 sgcReports 名称都在随产品提供的源代码中,演示用到的名称都可以编译。QuickReport 的名称仅供参考;查阅本页时没有安装 QuickReport。

QuickReport sgcReports 版本
TQuickRepTsgcReport(Pages、Styles、Units、Title)和 TsgcReportEngine(Report、AddDataSource、Prepare、Variables、OnGetVariable)全部
TQuickRep.DataSetTsgcReportDataSetSource(SourceName、DataSet)通过 AddDataSource 注册;带区的 DataSourceName 指向它全部
TQuickRep.Page(纸张、页边距)TsgcReportPage.PaperSize (rpsA3, rpsA4, rpsA5, rpsLetter, rpsLegal, rpsCustom), Margins.Left, Top, Right, Bottom全部
TQuickRep.ReportTitleTsgcReport.Title,以及用于 PDF 元数据的 TsgcReportPDFExport.Title全部
TQRBand,BandType 为 rbTitle、rbPageHeader、rbColumnHeaderTsgcReportBand,BandType 为 rbtReportTitle、rbtPageHeader、rbtColumnHeader全部
rbDetailrbtMasterData,DataSourceName 设为主数据源全部
rbGroupHeader, rbGroupFooterrbtGroupHeader, rbtGroupFooter全部
rbSummary, rbPageFooterrbtReportSummary, rbtPageFooter全部
TQRChildBandrbtChild,带 TsgcReportBand.ChildBandName全部
TQRSubDetail一个 rbtDetailData 带区(DataSourceName = 明细数据源),以及明细 TsgcReportDataSetSource 上的 MasterSource、MasterFields、DetailFields全部
TQRGroup.ExpressionrbtGroupHeader,带 GroupExpression全部
TQRGroup.ReprintOnNewPageTsgcReportBand.RepeatOnEveryPage全部
TQRGroup.FooterBand下一个 rbtGroupFooter 带区;第一个页脚关闭最内层的分组全部
TQRBand.ForceNewPageTsgcReportBand.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 qrsHorLinerikLine,带 Height = 0全部
TQRImage, TQRDBImagerikImage,带 Image.FileName 或 Image.Picture,或 Image.DataField;Stretch、KeepAspectRatio全部
TQRRichText, TQRDBRichTextrikRichText,带 RichText.Markup:类 HTML 标记,不是 RTF,并且可以包含 [expressions]全部
运行时设置的 Enabled、BeforePrint 事件带区和条目上的 VisibleExpression;TsgcReportEngine.OnBeforePrintBand全部
Prepare, PreviewTsgcReportEngine.Prepare 返回 TsgcReportPreparedPages;TsgcReportPreviewVCL.Prepare 在窗体上的控件中显示它们全部
Print, PrinterSetupTsgcReportPreviewVCL.Print(aTitle),在标准 TPrintDialog 之后全部
带 PDF 筛选器的 ExportToFilterTsgcReportPDFExport.SaveToFile(aPages, aFileName) 或 SaveToStream全部
PDF/ATsgcReportPDFExport.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 变成报表和引擎

迁移前:窗体上的一个 TQuickRep,报表及其数据都在一个组件中。迁移后:布局、引擎和预览是各自独立的组件。

frmMain.dfm
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 和 TQRSubDetail 变成数据源

迁移前:用于发票的 TQuickRep.DataSet,以及带有自己的 DataSet 用于明细行的 TQRSubDetail。迁移后:每个数据集一个数据源,明细通过字段链接到主表。FormCreate 用 sgcEngine.AddDataSource 注册两者。

frmMain.dfm
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

替代 TQRLabel、TQRDBText 和 TQRExpr 的辅助方法

演示通过窗体上的这个小方法添加每一段文本,因此下面的片段保持简短。

frmMain.pas
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;

页面设置、标题带区和 TQRSysData

迁移前:TQuickRep.Page、一个标题带区和一个显示日期的 TQRSysData。迁移后:TagRole 在 PDF/UA 标签树中把标题标记为标题。

frmMain.pas
// 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、明细带区和 TQRSubDetail

迁移前:客户上的一个 TQRGroup、由 TQRDBText 控件组成的明细带区,以及带有用于行金额的 TQRExpr 的 TQRSubDetail。迁移后:

frmMain.pas
// 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 组件。迁移后:

frmMain.pas
// 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

迁移前:带有 TQRShape、总计和 TQRRichText 的汇总带区。迁移后:富文本是类 HTML 标记。

frmMain.pas
// 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>.';

带“第 X 页,共 Y 页”的页脚

frmMain.pas
// 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。迁移后:

frmMain.pas
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;

ExportToFilter 变成 TsgcReportPDFExport

迁移前:带 PDF 筛选器的 ExportToFilter。迁移后:一个开关即可写出普通 PDF 或带标签的 PDF/UA 文件。

frmMain.pas
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;

每个报表八步

数据集保持不变,报表按带区逐个重建。

1

替换 TQuickRep

在普通窗体上放置一个 TsgcReport、一个 TsgcReportEngine(Report = 该报表)和一个 TsgcReportPreviewVCL(Engine = 该引擎)。

2

注册数据

为每个数据集添加一个 TsgcReportDataSetSource(SourceName、DataSet)并调用 AddDataSource。对每个 TQRSubDetail,设置 MasterSource、MasterFields 和 DetailFields。

3

设置页面

添加一个 TsgcReportPage,其纸张大小和页边距取自 TQuickRep.Page,并设置 TsgcReport.Units(演示中为 ruPoints),使坐标可以预测。

4

重建带区

每个 TQRBand 对应一个 TsgcReportBand,按报表顺序排列,带区类型使用匹配的 rbt;主表和明细带区设置 DataSourceName,分组头设置 GroupExpression。

5

重建控件

TQRLabel、TQRDBText 和 TQRExpr 变成混合字面文本和 [expressions] 的 rikText 条目。Mask 变成 Text.DisplayFormat,字体变成命名样式。

6

转换表达式

聚合变成 [Sum(x)]、[Count()]、[Avg(x)]、[Min(x)] 和 [Max(x)];TQRSysData 变成 [Page]、[TotalPages]、[Date] 和 [Time]。

7

预览、打印、导出

Preview 变成 TsgcReportPreviewVCL.Prepare,Print 变成 Print,PDF 筛选器变成 TsgcReportPDFExport.SaveToFile。为得到无障碍 PDF,请设置 PDFUA、Title 和 Language。

8

交给您的用户

可选,仅注册版:用 TsgcReport.SaveToFile 把重建的报表保存为 JSON,并让用户用 sgcShowReportDesigner 编辑它。

哪些内容无法一一对应

规划迁移之前请先阅读这些内容。

MigrateFromQuickReport

一个典型的 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 产品页面。

超值之选:All-AccesseSeGeCe 全部产品,含高级支持,每年 €1,059 起。
查看 All-Access 价格

今天就重建您的第一个报表

下载包含 sgcReports 和 MigrateFromQuickReport 演示的 sgcUI 试用版,迁移您的第一个报表。