1、DELPHI报表的动态生成(Create Delphi Dynamic Report)摘要:本文主要介绍如何在 DELPHI开发环境下动态生成报表,给出公用模块,并结合具体示例分析了实现的主要技术问题,给出解决这些问题的技术细节。关键词:DELPHI 报表、动态创建一、引言报表是数据库应用程序的重要部分,可是报表的生成也是数据库开发中最麻烦的一项工作。报表格式复杂多样,一直是使程序员头疼的事。DELPHI 在其 30 以后版本中加入了 QUICKREPORT,使这种情形有所改变。它的全部可视化编程以及设计和运行过程中都可以进行预览等特性给程序开发带来了很大的方便。我们可以通过在应用程序设计阶段
2、往窗体中添加报表控件,再与数据库表组件 Ttable,查询组件 Tquery等数据访问组件相关联,从而实现报表的预览与报表的输出。但在许多应用程序中,程序员往往期望从静态或者动态生成的数据库表中动态提取所需数据,进而生成报表输出。本文介绍的就是如何动态选择所需数据,动态生成报表的公用模块。这种动态方式生成的报表更加灵活,也更加容易做到报表的格式统一。动态报表主要是在程序运行阶段利用 Creat方法动态创建控件,设置其 Parent属性来设置其容器控件。然后,在程序中根据所选择的数据库表中的数据字段的长度和数目,通过修改控件的大小和位置属性,来控制控件的大小和外观,如果该控件有事件,可直接把函数
3、或过程名赋给它的相应事件名。二、动态报表的实现下面结合一个具体实例来说明如何其实现的方法和技术。1、基本思路数据来源,我们以 DELPHI自带的 DBDEMOS中的 employee.db表为例,它共有 6个字段。在 F_main主窗体中(如图一) ,可以自由选择所需要打印的字段。它的主要控件及属性设置如下:Table1:Databasename 设置为 DEDEMOS,Tablename设置为employee.dbListbox1:显示所连数据库表中的全部字段Listbox2:用于选择所需报表输出的字段AddBitBtn:用于把所选择的字段名添加到 Listbox2中DeleteBitBtn
4、:用于把 Listbox2中的字段名去掉PreviewBitBtn:用于报表的预览PrintBitBtn:用于报表的输出CloseBitBtn:用于关闭应用程序在 F_report窗体中,放置了以下主要控件,并设置属性,以减少程序的篇幅:Table1:Databasename 设置为 DEDEMOS,Tablename设置为employee.dbQuickRep1:papesize 属性为 A4,dataset 属性为Table1,bands 属性中的 hascolumnheader、hasdetail、hastitle设置为 True显示数据库表中的全部字段在 F_report的 Oncre
5、ate事件中加入了如下代码:Table1.Open;if Table1.Active thenTable1.GetFieldNames(Listbox1.Items);/ 获得数据库表中的全部字段名DeleteBitBtn.Enabled:=False; /在 Listbox2中无字段时,DeleteBitBtn变灰2、从 Listbox1中选择字段添加到 Listbox2中为 AddBitBtn的 Onclick事件加入如下代码:if listbox1.Items.Count=0 then exit; /如 Listbox1中无可供选择的字段, 则执行空操作if listbox1.Selec
6、tedlistbox1.ItemIndex then /在 Listbox1中选择字段beginListbox2.Items.Add(Listbox1.ItemsListbox1.ItemIndex);/往 Listbox2中增加选中的字段Listbox1.Items.Delete(Listbox1.ItemIndex);/从 Listbox1中删除此字段if Listbox2.Items.Count=1 then /在 Listbox2中有字段才允许执行删DeleteBitBtn.Enabled:=True;end;3、从 Listbox2中删除不需要的字段为 DeleteBitBtn的 O
7、ncreate事件添加如下代码:if Listbox2.Items.Count=0 then exit; / 如果 Listbox2中无字段,则执行空操作if listbox2.SelectedListbox2.ItemIndex then /在Listbox2中选择字段beginListbox1.Items.Add(Listbox2.itemsListbox2.itemindex); /添加到 Listbox1中Listbox2.Items.Delete(Listbox2.itemindex); /从Listbox2中删除此字段end;if Listbox2.Items.Count=0 th
8、en /如果 Listbox2中无字段,则 DeleteBitBtn变灰DeleteBitBtn.Enabled:=False;4、在报表中动态添加一列的步骤:.TitleBand1 中打印的是报表的名称,这里假设为:动态报表生成示例。可以动态创建 TQrlabel控件,把它的 Parent属性置为F_report.TitleBand1,使其成为 TQrlabel控件的容器控件。.ColumnHeaderBand1 中需要打印的是报表的列名。为了使报表的格式更加整齐,我们同时动态创建 TQrlabel控件和 TQRshape控件,把F_report.ColumnHeaderBand1设为它们的
9、容器控件。把 Listbox2中选择的字段名,赋给 Tqrlabel.caption,从而显示列名。.在 DetailBand1中创建 TQRDbText控件与 TQRshape控件,把它的 Parent属性指向F_report.DetailBand1,与在 ColumnHeaderBand1中创建列名相类似。并使 TQRDbText控件的 dataset属性指向相应的 Ttable或Tquery控件,dataField 属性指向对应的字段。.在预览前根据选择字段,判断报表的总宽度是否超出宽度,以及报表的打印方向是横向还是纵向。如总宽度超出报表所限的最大值,则提示警告信息,并强制进行调整。另外
10、需要考虑的问题是如何确定列的宽度,以及各列之间的相对位置。通过Columnswidth过程,确定所选择的字段中最大的字段长度maxwidth。各列的打印宽度可以通过公式(该列宽度=字段最大长度*给定字体下每字节所占的宽度+两边所留空隙)。widthperbyte:=10; / 每个字节对应的像数columnswidth; /计算最大列宽与总宽度disposecontrols; /释放动态创建的控件if totalwidth*widthperbyte1123 then /判断总宽度是否超出最大值beginApplication.MessageBox(报表超宽,请调整再输出!,警告,1);exit
11、; /提示警告信息,并强制进行调整endelse if totalwidth*widthperbyte794 thenF_report.QuickRep1.Page.Orientation:=polandscape /横向elseF_report.QuickRep1.Page.Orientation:=poPortrait; /纵向Heading:=TQRlabel.Create(self); /创建 TQRlabel控件Heading.parent:=F_report.TitleBand1; /设置其容器控件Heading.Caption:=动态报表生成示例; /报表标题Heading.Fo
12、nt.Size:=16;Heading.Font.Style:=fsbold; /粗体字Heading.Alignment:=tacenter;Heading.Width:=Length(动态报表生成示例)*(widthperbyte+4);/标题的宽度Heading.Left:=(F_report.QuickRep1.Width-Heading.width)div 2;Heading.Height:=F_report.TitleBand1.Height-1;Heading.Top:=0;Leftx:=(F_report.quickrep1.width-totalwidth*widthperb
13、yte)div 2;F_report.QuickRep1.Font.Size:=12;for i:=0 to Listbox2.items.count-1 do /根据所选择字段的数目来动态创建beginQRShape1:=TQRSHape.Create(self); /创建 TQRSHape控件打印列名的格线QRShape1.parent:=F_report.ColumnHeaderBand1; /设置容器控件QRShape1.Left:=Leftx; /与列名的格线相对齐QRShape1.Width:=maxwidth*widthperbyte+4; / TQRSHape控件的宽度QRSh
14、ape1.Height:=F_report.ColumnHeaderBand1.Height;QRShape1.top:=0;QRLabel:=TQRLabel.Create(self); /创建 TQRLabel控件QRLabel.parent:=F_report.ColumnHeaderBand1; /设置容器控件QRLabel.Font.Style:=fsbold; /设置列名的字体为粗体字QRLabel.Left:=Leftx+2; /左边空 2个像数QRLabel.width:=maxwidth*widthperbyte; /列名的打印宽度QRLabel.height:=F_repo
15、rt.ColumnHeaderBand1.Height-2; /空 2个像数QRLabel.top:=1;QRLabel.caption:=Listbox2.Items.Stringsi; /显示选择的字段名称QRShape2:=TQRSHAPE.Create(self); /创建 TQRSHAPE控件QRShape2.Parent:=F_report.DetailBand1; /设置容器控件QRShape2.Left:=Leftx; QRShape2.Width:=maxwidth*widthperbyte+4; /确定格线的宽度QRShape2.Height:=F_report.Detai
16、lBand1.Height;QRShape2.top:=0;QRDBText:=TQRDBText.Create(self); /创建TQRDBText控件QRDBText.parent:=F_report.DetailBand1; /设置容器控件QRDBText.Left:=Leftx+2; /左边空 2个像数QRDBText.Width:=maxwidth*widthperbyte; /设置 QRDBText的宽度QRDBText.Height:=F_report.DetailBand1.Height-2; /空 2个像数QRDBText.Top:=1;QRDBText.DataSet:=
17、F_report.Table1; /连接数据表控件QRDBText.DataField:=Listbox2.Items.Stringsi; /连接选择的字段Leftx:=Leftx+maxwidth*widthperbyte+4; /设置下一列的起始位置end;F_report.Table1.Active:=true;F_report.QuickRep1.Preview; /报表的预览5为计算报表的总宽度 totalwidth与最大列宽 maxwidth,定义了过程 columnswidth。各字段的长度可以用 Tfield的 DataSize属性得到。字段名的长度根据 Length函数来获得
18、。为了整个打印表格整齐划一,通过比较字段名与字段长度来确定最大列宽 maxwidth。maxwidth:=0;for i:=0 to F_main.Listbox2.items.count-1 dobeginif F_main.Table1.Fields.Fieldbyname(F_main.Listbox2.items.stringsi).datasizemaxwidth thenmaxwidth:=F_main.Table1.Fields.Fieldbyname(F_main.Listbox2.items.stringsi).datasize; /确定字段中数据的最大长度if Length
19、(F_main.Listbox2.items.stringsi)maxwidth thenmaxwidth:=Length(F_main.Listbox2.items.stringsi); /确定字段名中最大长度end;totalwidth:=0; for i:=0 to F_main.Listbox2.items.count-1 dototalwidth:=totalwidth+maxwidth+4; /报表总宽6在动态创建完控件后,我们通过过程 disposecontrols来释放其所占的资源。由于用户可能多次的点击预览键,因此我们必须在每次预览事件发生之前,释放上次动态创建的控件。for
20、 i:=0 to F_report.TitleBand1.ControlCount-1 DO /取消系统对控件的控制F_report.TitleBand1.RemoveControl(F_report.TitleBand1.Controls0);for i:=1 to F_report.ColumnHeaderBand1.ControlCount DO /取消系统对控件的控制F_report.ColumnHeaderBand1.RemoveControl(F_report.ColumnHeaderband1.Controls0);for i:=1 to F_report.detailband1
21、.controlcount DO /取消系统对控件的控制F_report.detailband1.removecontrol(F_report.detailband1.Controls0);F_report.Table1.active:=false;三、注意事项1程序员可以根据用户的实际需求,设制表格线或者取消表格线。也可以不计算最大宽度,而根据各列实际宽度打印,则无须定义 columnswidth过程。各列宽度的计算公式则改为(该列宽度=该字段长度*给定字体下每字节所占的宽度+两边所留空隙)。2在添加 Preview的 OnClick事件前必须先添加TQRLabel、TQRShape、TQR
22、DBText 控件的系统标准引用单元 QRCtrls。3在设置纸的打印方向时,必须引用 Printers。4动态生成组件的宽度计算必须放在定义其字体属性完成后进行。5如果要修改 QRDBText控件的数据位置,必须先设置其 AutoSize属性为 false,然后才能设置其 Alignment属性为所需的左对齐、居中或者右对齐。这一点很容易被忽略。四、结束语以上程序在 DELPHI中调试通过。上述示例只是介绍了报表动态生成的核心部分,由于在不同的实际情况下,用户对报表输出的格式会有所不同,因此需要根据具体情况,更加灵活的运用报表类控件,对上述示例程序进行修改和添加,以满足不同的要求。比如:可以
23、动态的创建数据库表,再通过数据访问组件来获取所需数据。也可以动态创建 TQRExpr 控件来实现动态报表的计算功能等等。参考文献:1、DELPHI4.0 数据库与 INTERNET开发指南 潘将一 清华大学出版社 1999.92、DELPHI3.0 编程参考手册 (美)P.Thurrott,G.Brent,R.Bagdazian,S.Tendon著卢庆龄 蒋全 等译 清华大学出版社 1998.8 附件:U_main 的程序代码unit U_main;interfaceusesWindows, Messages, SysUtils, Classes, Graphics, Controls, Fo
24、rms, Dialogs,ExtCtrls, StdCtrls, Buttons, Db, DBTables,QRCtrls,printers;typeTF_main = class(TForm)ListBox1: TListBox;ListBox2: TListBox;addBitBtn: TBitBtn;deleteBitBtn: TBitBtn;Panel1: TPanel;Panel2: TPanel;previewBitBtn: TBitBtn;printBitBtn: TBitBtn;closeBitBtn: TBitBtn;Table1: TTable;procedure For
25、mCreate(Sender: TObject);procedure addBitBtnClick(Sender: TObject);procedure deleteBitBtnClick(Sender: TObject);procedure previewBitBtnClick(Sender: TObject);procedure printBitBtnClick(Sender: TObject);procedure closeBitBtnClick(Sender: TObject);private Private declarations public Public declaration
26、s end;varF_main: TF_main;maxwidth,totalwidth:integer;procedure columnswidth;procedure disposecontrols;implementationuses U_report;$R *.DFMprocedure TF_main.FormCreate(Sender: TObject);beginTable1.Open;if Table1.Active thenTable1.GetFieldNames(Listbox1.Items);/ 获得数据库表中的全部字段名DeleteBitBtn.Enabled:=Fals
27、e; /在 Listbox2中无字段时,DeleteBitBtn变灰end;procedure TF_main.addBitBtnClick(Sender: TObject);beginif listbox1.Items.Count=0 then exit; /如 Listbox1中无可供选择的字段,则执行空操作if listbox1.Selectedlistbox1.ItemIndex then /在Listbox1中选择字段beginListbox2.Items.Add(Listbox1.ItemsListbox1.ItemIndex);Listbox1.Items.Delete(List
28、box1.ItemIndex);if Listbox2.Items.Count=1 then /在 Listbox2中有字段才允许执行删DeleteBitBtn.Enabled:=True;end;end;procedure TF_main.deleteBitBtnClick(Sender: TObject);beginif Listbox2.Items.Count=0 then exit; / 如果 Listbox2中无字段,则执行空操作if listbox2.SelectedListbox2.ItemIndex then /在Listbox2中选择字段beginListbox1.Items
29、.Add(Listbox2.itemsListbox2.itemindex);Listbox2.Items.Delete(Listbox2.itemindex);end;if Listbox2.Items.Count=0 then /如果 Listbox2中无字段,则 DeleteBitBtn变灰DeleteBitBtn.Enabled:=False;end;procedure columnswidth;var i:integer;beginmaxwidth:=0;for i:=0 to F_main.Listbox2.items.count-1 dobegin/确定字段中数据的最大长度if
30、F_main.Table1.Fields.Fieldbyname(F_main.Listbox2.items.stringsi).datasizemaxwidth thenmaxwidth:=F_main.Table1.Fields.Fieldbyname(F_main.Listbox2.items.stringsi).datasize;/确定字段名中最大长度if Length(F_main.Listbox2.items.stringsi)maxwidth thenmaxwidth:=Length(F_main.Listbox2.items.stringsi);end;totalwidth:=
31、0;/报表总宽for i:=0 to F_main.Listbox2.items.count-1 dototalwidth:=totalwidth+maxwidth+4;end;procedure TF_main.previewBitBtnClick(Sender: TObject);vari:integer;leftx:integer;widthperbyte:integer;Heading:TQRlabel;QRLabel: TQRlabel;QRshape1: TQRshape;QRshape2: TQRshape;QRdbtext: TQRDBtext;beginwidthperbyt
32、e:=10; / 每个字节对应的像数columnswidth; /计算最大列宽与总宽度disposecontrols; /释放动态创建的控件if totalwidth*widthperbyte1123 thenbeginApplication.MessageBox(报表超宽,请调整再输出!,警告,1);/输出对话框exit;endelse if totalwidth*widthperbyte794 thenF_report.QuickRep1.Page.Orientation:=polandscape /横向elseF_report.QuickRep1.Page.Orientation:=po
33、Portrait;/纵向Heading:=TQRlabel.Create(self); /创建 TQRlabel控件Heading.parent:=F_report.TitleBand1; /设置其容器控件Heading.Caption:=动态报表生成示例; /报表标题Heading.Font.Size:=16;Heading.Font.Style:=fsbold;Heading.Alignment:=tacenter;Heading.Width:=Length(动态报表生成示例)*(widthperbyte+4);Heading.Left:=(F_report.QuickRep1.Width
34、-Heading.width)div 2;Heading.Height:=F_report.TitleBand1.Height-1;Heading.Top:=0;Leftx:=(F_report.quickrep1.width-totalwidth*widthperbyte)div 2;F_report.QuickRep1.Font.Size:=12;for i:=0 to Listbox2.items.count-1 do /根据所选择字段的数目来动态创建beginQRShape1:=TQRSHape.Create(self);QRShape1.parent:=F_report.Column
35、HeaderBand1;QRShape1.Left:=Leftx;QRShape1.Width:=maxwidth*widthperbyte+4;QRShape1.Height:=F_report.ColumnHeaderBand1.Height;QRShape1.top:=0;QRLabel:=TQRLabel.Create(self);QRLabel.parent:=F_report.ColumnHeaderBand1;QRLabel.Font.Style:=fsbold;QRLabel.Left:=Leftx+2;QRLabel.width:=maxwidth*widthperbyte;
36、QRLabel.height:=F_report.ColumnHeaderBand1.Height-2;QRLabel.top:=1;QRLabel.caption:=Listbox2.Items.Stringsi;QRShape2:=TQRSHAPE.Create(self);QRShape2.Parent:=F_report.DetailBand1;QRShape2.Left:=Leftx;QRShape2.Width:=maxwidth*widthperbyte+4;QRShape2.Height:=F_report.DetailBand1.Height;QRShape2.top:=0;
37、QRDBText:=TQRDBText.Create(self);QRDBText.parent:=F_report.DetailBand1;QRDBText.Left:=Leftx+2;QRDBText.Width:=maxwidth*widthperbyte;QRDBText.Height:=F_report.DetailBand1.Height-2;QRDBText.Top:=1;QRDBText.DataSet:=F_report.Table1;QRDBText.DataField:=Listbox2.Items.Stringsi;Leftx:=Leftx+maxwidth*width
38、perbyte+4;end;F_report.Table1.Active:=true;F_report.QuickRep1.Preview;end;procedure disposecontrols;var i:integer;beginfor i:=0 to F_report.TitleBand1.ControlCount-1 do /取消系统对控件的控制F_report.TitleBand1.RemoveControl(F_report.TitleBand1.Controls0);for i:=1 to F_report.ColumnHeaderBand1.ControlCount DOF
39、_report.ColumnHeaderBand1.RemoveControl(F_report.ColumnHeaderband1.Controls0);for i:=1 to F_report.detailband1.controlcount doF_report.detailband1.removecontrol(F_report.detailband1.Controls0);F_report.Table1.active:=false;end;procedure TF_main.printBitBtnClick(Sender: TObject);beginF_report.QuickRe
40、p1.Print;end;procedure TF_main.closeBitBtnClick(Sender: TObject);begindisposecontrols;close;end;end.*Delphi程序执行时实时生成报表当前,在软件开发工具中,Delphi 以其控件多、面向对象编程功能强、代码执行速度快和简单易用等特点,结合可视化开发环境和当前最快的编译器技术,已成为全球公认的快速应用开发工具,正被愈来愈多的编程人员所采用。使用 Delphi可以编写各种 Windows应用程序,尤其是开发数据库信息管理系统有其独特的优势。在数据库信息管理系统的开发的过程中,我们经常需要打印输出
41、很多报表,用 Delphi设计复杂报表是一件比较烦锁的事件,它没有 Visual FoxPro中那样简便。但由于 Delphi中设计报表采用的也是控件,因此,我们可以在程序执行时直接建立所需的报表控件来实时生成报表,而且,生成的报表样工可以由程序控制来决定。例如,我们在数据库信息查询时,查询出来的结果信息结构一般是不固定的,假如我们要将查询结果打印出来,只设计一种报表格式是不行的,为所有可能的结果信息都设计一种报表格式也不是一种很好的解决办法。为了解决这样一个问题,我们可以采用实时生成报表技术。本文的目的就是通过一个实例向大家详细介绍怎样实时生成报表。本例所将设计一个打印对话框,该对话框包括
42、TQickRep控件和一些报表样式控制控件,其它窗体外观如下图所示:1、 控件功能说明QuickRep:TQuickRep 它包括列标头(HB:TQRBand)、细节(DB:TQRBand)、页脚(FB:TQRBand)、总结(SB:TQRBand)带区,并且细节、页脚、总结中没有包括一个 TQRLabel、TQRExpr 或 TDBText控件,主要是在程序执行时建立,列标头带区中包括 Title (TQRLabel)用于报表标题;QRSQL: TQRLabel用于查询条件,这两个控件的 Caption属性在程序执行时可任意更改。为了能够让QuickRep不显示出来,将其置于 Panel1
43、(Tpanel)的后面,并将Panel1扩展到整个窗体;Query:TQuery SQL语句控件,程序将根据 Query返回的结果来生成报表。因此,在建立这个窗体时,一定要将 Query.SQL属性指定一条 SQL语句;在以上窗体中“纸张”和“页面设置”两栏所包括的控件是对QuickRep.Page属性的控制,程序执行时更改它们会直接改变QuickRep控件相应的属性值,这可以通过 OnChange或 OnExit事件代码完成;“打印内容设置”栏中的标题是指定报表的标题(TT:TEdit) ,其值与 QuickRep.ReportTitle和 Title.Caption一致,可以任意更改;“打
44、印查询条件”复选框指定是否打印查询条件,该复选框的选取否直接控制 QRSQL.Caption是否为空;“表列对齐方式”由一组选项按钮组成,它主要用于报表生成时细节内容的对齐方式,它的更改控制变量 RD1(Byte)的值(0 自动对齐,1 中间对齐,2 左边对齐);“表列打印宽度”由一组选项按钮组成,主要用于在生成报表格式时列值的宽度,它的更改控制变量 RD2(Byte)的值(0 自动宽度,1相同宽度,2 限制最大宽度),当选中 1相同宽度,2 限制最大宽度时要求输入宽度,单位为像素;“统计方式”指出报表是否包含页脚(FB:TQRBAND)和总和(SB:TQRBAND)带区。2、 程序说明程序定
45、义了如下类型:TQRLabelName=array of TQRLabel;TQRDBTextName=array of TQRDBText;TQRShapeName=array of TQRShape;TQRExpName=array of TQRExpr;上述类型为动态数组类型,数据的每个元素为一个类。在实时建立报表控件时,要建立的控件个数是不确定的且控制名称也不能确定,用动态数组是一个比较好的解决办法,即可以任意指定数据的维数,又不用自己管理内存分配问题,还有利于报表包含控件的释放与处理。程序还声明了上述类型的变量如下:CHBName:TQRLabelName;DBName:TQRDBT
46、extName;CHBShape,DBShape,FBShape,SumShape:TQRShapeName;FBName,SumName:TQRExpName;这些数组变量将在窗体建立时根据 Query返回的字段结果分配内存,每一个字段对就数组的一个元素。程序执行过程:窗体在建立并显示时,就对本窗体建立初始化操作。在 OnCreate事件中将 QuickRep.Page属性的相应值显示出来,在 OnShow事件中执行 Query.Open操作,并根据返回结果分配控件数组变量空间。窗体建立后,单击“生成”按钮生成报表(忽略备注字段和相片字段),然后可单击“打印”和“预览”进行打印或者预览报表。
47、当产生报表后又更改了设置,必须重新生成报表。如果Query返回的结果集字段太多,生成报表时有可能纸张矿小不能将生成全部报表,可调整报表纸张大小,再生成报表。当关闭窗体时,将释放建立的控件。3、 源程序清单及注释unit PrintDlg;interfaceusesWindows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,StdCtrls, Buttons, ExtCtrls, Spin, QuickRpt,QRPrntr,printers, Qrctrls,Db, DBTables, ComCtrls,SysIni;typeTQRLabelName=array of TQRLabel;/列标头带区中列标题控制件类动态数组TQRDBTextName=array of TQRDBText; /细节带区中列标题控制件类动态数组TQRShapeName=array of TQRShape; /线条控制件类动态数组TQRExpName=array of TQRExpr; /统计控制件类动态数组TPrintForm = class(TForm)GroupBox1: TGroupBox