下面结合一个具体实例来说明如何其实现的方法和技术。 1、 基本思路 数据来源,我们以DELPHI自带的DBDEMOS中的employee.db表为例,它共有6 个字段。 在F_main主窗体中(如图一),可以自由选择所需要打印的字段。它的主要控件及属性设置如下: ①.Table1:Databasename设置为DEDEMOS,Tablename设置为employee.db ②.Listbox1:显示所连数据库表中的全部字段 ③.Listbox2:用于选择所需报表输出的字段 ④.AddBitBtn:用于把所选择的字段名添加到Listbox2中 ⑤.DeleteBitBtn:用于把Listbox2中的字段名去掉 ⑥.PreviewBitBtn:用于报表的预览 ⑦.PrintBitBtn:用于报表的输出 ⑧.CloseBitBtn:用于关闭应用程序 在F_report窗体中,放置了以下主要控件,并设置属性,以减少程序的篇幅: ①.Table1:Databasename设置为DEDEMOS,Tablename设置为employee.db ②.QuickRep1:papesize属性为A4,dataset属性为Table1,bands属性中的hascolumnheader、hasdetail、hastitle设置为True 图一 显示数据库表中的全部字段 在F_report的Oncreate事件中加入了如下代码: Table1.Open; if Table1.Active then Table1.GetFieldNames(Listbox1.Items);// 获得数据库表中的全部字段名 DeleteBitBtn.Enabled:=False; //在Listbox2中无字段时,DeleteBitBtn变灰2、 从Listbox1中选择字段添加到Listbox2中 为AddBitBtn的Onclick事件加入如下代码: if listbox1.Items.Count=0 then exit; //如Listbox1中无可供选择的字段, 则执行空操作 if listbox1.Selected[listbox1.ItemIndex] then //在Listbox1中选择字段 begin Listbox2.Items.Add(Listbox1.Items[Listbox1.ItemIndex]);//往Listbox2 中增加选中的字段 Listbox1.Items.Delete(Listbox1.ItemIndex);//从Listbox1中删除此字段 if Listbox2.Items.Count>=1 then //在Listbox2中有字段才允许执行删 DeleteBitBtn.Enabled:=True; end;3、 从Listbox2中删除不需要的字段 为DeleteBitBtn的Oncreate事件添加如下代码: if Listbox2.Items.Count=0 then exit; // 如果Listbox2中无字段,则执行空操作 if listbox2.Selected[Listbox2.ItemIndex] then //在Listbox2中选择字段 begin Listbox1.Items.Add(Listbox2.items[Listbox2.itemindex]); //添加到Listbox1中 Listbox2.Items.Delete(Listbox2.itemindex); //从Listbox2中删除此字段 end; if Listbox2.Items.Count=0 then //如果Listbox2中无字段,则DeleteBitBtn变灰 DeleteBitBtn.Enabled:=False;
4、在报表中动态添加一列的步骤: ①. TitleBand1中打印的是报表的名称,这里假设为:动态报表生成示例。可以动态 创建TQrlabel控件,把它的Parent属性置为F_report.TitleBand1,使其成为TQrlabel控件的容器控件。 ②. ColumnHeaderBand1中需要打印的是报表的列名。为了使报表的格式更加整齐,我 们同时动态创建TQrlabel控件和TQRshape控件,把F_report.ColumnHeaderBand1设为它们的容器控件。把Listbox2中选择的字段名,赋给Tqrlabel.caption,从而显示列名。 ③. 在DetailBand1中创建TQRDbText控件与TQRshape控件,把它的Parent属性指向 F_report.DetailBand1,与在ColumnHeaderBand1中创建列名相类似。并使TQRDbText控件的dataset属性指向相应的Ttable或Tquery控件,dataField属性指向对应的字段。 ④. 在预览前根据选择字段,判断报表的总宽度是否超出宽度,以及报表的打印方向是 横向还是纵向。如总宽度超出报表所限的最大值,则提示警告信息,并强制进行调整。 ⑤.另外需要考虑的问题是如何确定列的宽度,以及各列之间的相对位置。通过 Columnswidth过程,确定所选择的字段中最大的字段长度maxwidth。各列的打印宽度可以通过公式(该列宽度=字段最大长度*给定字体下每字节所占的宽度+两边所留空隙)。 widthperbyte:=10; // 每个字节对应的像数 columnswidth; //计算最大列宽与总宽度 disposecontrols; //释放动态创建的控件 if totalwidth*widthperbyte>1123 then //判断总宽度是否超出最大值 begin Application.MessageBox('报表超宽,请调整再输出!','警告',1); exit; //提示警告信息,并强制进行调整 end else if totalwidth*widthperbyte>794 then F_report.QuickRep1.Page.Orientation:=polandscape //横向 else F_report.QuickRep1.Page.Orientation:=poPortrait; //纵向 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-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 //根据所选择字段的数目来动态创建 begin QRShape1:=TQRSHape.Create(self); //创建TQRSHape控件打印列名的格线 QRShape1.parent:=F_report.ColumnHeaderBand1; //设置容器控件 QRShape1.Left:=Leftx; //与列名的格线相对齐 QRShape1.Width:=maxwidth*widthperbyte+4; // TQRSHape控件的宽度 QRShape1.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_report.ColumnHeaderBand1.Height-2; //空2个像数 QRLabel.top:=1; QRLabel.caption:=Listbox2.Items.Strings[i]; //显示选择的字段名称 QRShape2:=TQRSHAPE.Create(self); //创建TQRSHAPE控件 QRShape2.Parent:=F_report.DetailBand1; //设置容器控件 QRShape2.Left:=Leftx; QRShape2.Width:=maxwidth*widthperbyte+4; //确定格线的宽度 QRShape2.Height:=F_report.DetailBand1.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:=F_report.Table1; //连接数据表控件 QRDBText.DataField:=Listbox2.Items.Strings[i]; //连接选择的字段 Leftx:=Leftx+maxwidth*widthperbyte+4; //设置下一列的起始位置 end; F_report.Table1.Active:=true; F_report.QuickRep1.Preview; //报表的预览5.为计算报表的总宽度totalwidth与最大列宽maxwidth,定义了过程columnswidth。 各字段的长度可以用Tfield的DataSize属性得到。字段名的长度根据Length函数来获得。为了整个打印表格整齐划一,通过比较字段名与字段长度来确定最大列宽maxwidth。 maxwidth:=0; for i:=0 to F_main.Listbox2.items.count-1 do begin if F_main.Table1.Fields.Fieldbyname(F_main.Listbox2.items.strings[i]).datasize >maxwidth then maxwidth:=F_main.Table1.Fields.Fieldbyname(F_main.Listbox2.items.strings[i]).datasize; //确定字段中数据的最大长度 if Length(F_main.Listbox2.items.strings[i])>maxwidth then maxwidth:=Length(F_main.Listbox2.items.strings[i]); //确定字段名中最大长度 end; totalwidth:=0; for i:=0 to F_main.Listbox2.items.count-1 do totalwidth:=totalwidth+maxwidth+4; //报表总宽
6.在动态创建完控件后,我们通过过程disposecontrols来释放其所占的资源。由于用 户可能多次的点击预览键,因此我们必须在每次预览事件发生之前,释放上次动态创建的控件。 for i:=0 to F_report.TitleBand1.ControlCount-1 DO //取消系统对控件的控制 F_report.TitleBand1.RemoveControl(F_report.TitleBand1.Controls[0]);for i:=1 to F_report.ColumnHeaderBand1.ControlCount DO //取消系统对控件的控制 F_report.ColumnHeaderBand1.RemoveControl(F_report.ColumnHeaderband1.Controls[0]);for i:=1 to F_report.detailband1.controlcount DO //取消系统对控件的控制 F_report.detailband1.removecontrol(F_report.detailband1.Controls[0]); F_report.Table1.active:=false; 三、注意事项 1. 程序员可以根据用户的实际需求,设制表格线或者取消表格线。也可以不计算最大 宽度,而根据各列实际宽度打印,则无须定义columnswidth过程。各列宽度的计算公式则改为(该列宽度=该字段长度*给定字体下每字节所占的宽度+两边所留空隙)。 2. 在添加Preview的OnClick事件前必须先添加TQRLabel、TQRShape、TQRDBText 控件的系统标准引用单元QRCtrls。 3. 在设置纸的打印方向时,必须引用Printers。 4. 动态生成组件的宽度计算必须放在定义其字体属性完成后进行。 5. 如果要修改QRDBText控件的数据位置,必须先设置其AutoSize属性为false,然 后才能设置其Alignment属性为所需的左对齐、居中或者右对齐。这一点很容易被忽略。 四、结束语 以上程序在DELPHI中调试通过。 上述示例只是介绍了报表动态生成的核心部分,由于在不同的实际情况下,用户对报表输出的格式会有所不同,因此需要根据具体情况,更加灵活的运用报表类控件,对上述示例程序进行修改和添加,以满足不同的要求。比如:可以动态的创建数据库表,再通过数据访问组件来获取所需数据。也可以动态创建TQRExpr控件来实现动态报表的计算功能等等。参考文献: 1、DELPHI4.0数据库与INTERNET开发指南 潘将一 清华大学出版社 1999.9 2、DELPHI3.0编程参考手册 (美)P.Thurrott,G.Brent,R.Bagdazian,S.Tendon著 卢庆龄 蒋全 等译 清华大学出版社 1998.8
1、 基本思路
数据来源,我们以DELPHI自带的DBDEMOS中的employee.db表为例,它共有6 个字段。
在F_main主窗体中(如图一),可以自由选择所需要打印的字段。它的主要控件及属性设置如下:
①.Table1:Databasename设置为DEDEMOS,Tablename设置为employee.db
②.Listbox1:显示所连数据库表中的全部字段
③.Listbox2:用于选择所需报表输出的字段
④.AddBitBtn:用于把所选择的字段名添加到Listbox2中
⑤.DeleteBitBtn:用于把Listbox2中的字段名去掉
⑥.PreviewBitBtn:用于报表的预览
⑦.PrintBitBtn:用于报表的输出
⑧.CloseBitBtn:用于关闭应用程序
在F_report窗体中,放置了以下主要控件,并设置属性,以减少程序的篇幅:
①.Table1:Databasename设置为DEDEMOS,Tablename设置为employee.db
②.QuickRep1:papesize属性为A4,dataset属性为Table1,bands属性中的hascolumnheader、hasdetail、hastitle设置为True 图一 显示数据库表中的全部字段
在F_report的Oncreate事件中加入了如下代码:
Table1.Open;
if Table1.Active then
Table1.GetFieldNames(Listbox1.Items);// 获得数据库表中的全部字段名
DeleteBitBtn.Enabled:=False; //在Listbox2中无字段时,DeleteBitBtn变灰2、 从Listbox1中选择字段添加到Listbox2中
为AddBitBtn的Onclick事件加入如下代码:
if listbox1.Items.Count=0 then exit; //如Listbox1中无可供选择的字段,
则执行空操作
if listbox1.Selected[listbox1.ItemIndex] then //在Listbox1中选择字段
begin
Listbox2.Items.Add(Listbox1.Items[Listbox1.ItemIndex]);//往Listbox2
中增加选中的字段
Listbox1.Items.Delete(Listbox1.ItemIndex);//从Listbox1中删除此字段
if Listbox2.Items.Count>=1 then //在Listbox2中有字段才允许执行删
DeleteBitBtn.Enabled:=True;
end;3、 从Listbox2中删除不需要的字段
为DeleteBitBtn的Oncreate事件添加如下代码:
if Listbox2.Items.Count=0 then exit; // 如果Listbox2中无字段,则执行空操作
if listbox2.Selected[Listbox2.ItemIndex] then //在Listbox2中选择字段
begin
Listbox1.Items.Add(Listbox2.items[Listbox2.itemindex]); //添加到Listbox1中
Listbox2.Items.Delete(Listbox2.itemindex); //从Listbox2中删除此字段
end; if Listbox2.Items.Count=0 then //如果Listbox2中无字段,则DeleteBitBtn变灰
DeleteBitBtn.Enabled:=False;
①. TitleBand1中打印的是报表的名称,这里假设为:动态报表生成示例。可以动态
创建TQrlabel控件,把它的Parent属性置为F_report.TitleBand1,使其成为TQrlabel控件的容器控件。
②. ColumnHeaderBand1中需要打印的是报表的列名。为了使报表的格式更加整齐,我
们同时动态创建TQrlabel控件和TQRshape控件,把F_report.ColumnHeaderBand1设为它们的容器控件。把Listbox2中选择的字段名,赋给Tqrlabel.caption,从而显示列名。
③. 在DetailBand1中创建TQRDbText控件与TQRshape控件,把它的Parent属性指向
F_report.DetailBand1,与在ColumnHeaderBand1中创建列名相类似。并使TQRDbText控件的dataset属性指向相应的Ttable或Tquery控件,dataField属性指向对应的字段。
④. 在预览前根据选择字段,判断报表的总宽度是否超出宽度,以及报表的打印方向是
横向还是纵向。如总宽度超出报表所限的最大值,则提示警告信息,并强制进行调整。
⑤.另外需要考虑的问题是如何确定列的宽度,以及各列之间的相对位置。通过
Columnswidth过程,确定所选择的字段中最大的字段长度maxwidth。各列的打印宽度可以通过公式(该列宽度=字段最大长度*给定字体下每字节所占的宽度+两边所留空隙)。 widthperbyte:=10; // 每个字节对应的像数
columnswidth; //计算最大列宽与总宽度
disposecontrols; //释放动态创建的控件 if totalwidth*widthperbyte>1123 then //判断总宽度是否超出最大值
begin
Application.MessageBox('报表超宽,请调整再输出!','警告',1);
exit; //提示警告信息,并强制进行调整
end
else if totalwidth*widthperbyte>794 then
F_report.QuickRep1.Page.Orientation:=polandscape //横向
else
F_report.QuickRep1.Page.Orientation:=poPortrait; //纵向 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-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 //根据所选择字段的数目来动态创建
begin
QRShape1:=TQRSHape.Create(self); //创建TQRSHape控件打印列名的格线
QRShape1.parent:=F_report.ColumnHeaderBand1; //设置容器控件
QRShape1.Left:=Leftx; //与列名的格线相对齐
QRShape1.Width:=maxwidth*widthperbyte+4; // TQRSHape控件的宽度
QRShape1.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_report.ColumnHeaderBand1.Height-2; //空2个像数
QRLabel.top:=1;
QRLabel.caption:=Listbox2.Items.Strings[i]; //显示选择的字段名称 QRShape2:=TQRSHAPE.Create(self); //创建TQRSHAPE控件
QRShape2.Parent:=F_report.DetailBand1; //设置容器控件
QRShape2.Left:=Leftx;
QRShape2.Width:=maxwidth*widthperbyte+4; //确定格线的宽度
QRShape2.Height:=F_report.DetailBand1.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:=F_report.Table1; //连接数据表控件
QRDBText.DataField:=Listbox2.Items.Strings[i]; //连接选择的字段 Leftx:=Leftx+maxwidth*widthperbyte+4; //设置下一列的起始位置
end; F_report.Table1.Active:=true;
F_report.QuickRep1.Preview; //报表的预览5.为计算报表的总宽度totalwidth与最大列宽maxwidth,定义了过程columnswidth。
各字段的长度可以用Tfield的DataSize属性得到。字段名的长度根据Length函数来获得。为了整个打印表格整齐划一,通过比较字段名与字段长度来确定最大列宽maxwidth。
maxwidth:=0;
for i:=0 to F_main.Listbox2.items.count-1 do
begin
if F_main.Table1.Fields.Fieldbyname(F_main.Listbox2.items.strings[i]).datasize
>maxwidth then
maxwidth:=F_main.Table1.Fields.Fieldbyname(F_main.Listbox2.items.strings[i]).datasize; //确定字段中数据的最大长度
if Length(F_main.Listbox2.items.strings[i])>maxwidth then
maxwidth:=Length(F_main.Listbox2.items.strings[i]); //确定字段名中最大长度
end; totalwidth:=0;
for i:=0 to F_main.Listbox2.items.count-1 do
totalwidth:=totalwidth+maxwidth+4; //报表总宽
户可能多次的点击预览键,因此我们必须在每次预览事件发生之前,释放上次动态创建的控件。
for i:=0 to F_report.TitleBand1.ControlCount-1 DO //取消系统对控件的控制
F_report.TitleBand1.RemoveControl(F_report.TitleBand1.Controls[0]);for i:=1 to F_report.ColumnHeaderBand1.ControlCount DO //取消系统对控件的控制
F_report.ColumnHeaderBand1.RemoveControl(F_report.ColumnHeaderband1.Controls[0]);for i:=1 to F_report.detailband1.controlcount DO //取消系统对控件的控制
F_report.detailband1.removecontrol(F_report.detailband1.Controls[0]); F_report.Table1.active:=false;
三、注意事项
1. 程序员可以根据用户的实际需求,设制表格线或者取消表格线。也可以不计算最大
宽度,而根据各列实际宽度打印,则无须定义columnswidth过程。各列宽度的计算公式则改为(该列宽度=该字段长度*给定字体下每字节所占的宽度+两边所留空隙)。
2. 在添加Preview的OnClick事件前必须先添加TQRLabel、TQRShape、TQRDBText
控件的系统标准引用单元QRCtrls。
3. 在设置纸的打印方向时,必须引用Printers。
4. 动态生成组件的宽度计算必须放在定义其字体属性完成后进行。
5. 如果要修改QRDBText控件的数据位置,必须先设置其AutoSize属性为false,然
后才能设置其Alignment属性为所需的左对齐、居中或者右对齐。这一点很容易被忽略。
四、结束语
以上程序在DELPHI中调试通过。
上述示例只是介绍了报表动态生成的核心部分,由于在不同的实际情况下,用户对报表输出的格式会有所不同,因此需要根据具体情况,更加灵活的运用报表类控件,对上述示例程序进行修改和添加,以满足不同的要求。比如:可以动态的创建数据库表,再通过数据访问组件来获取所需数据。也可以动态创建TQRExpr控件来实现动态报表的计算功能等等。参考文献:
1、DELPHI4.0数据库与INTERNET开发指南 潘将一 清华大学出版社 1999.9
2、DELPHI3.0编程参考手册 (美)P.Thurrott,G.Brent,R.Bagdazian,S.Tendon著
卢庆龄 蒋全 等译 清华大学出版社 1998.8