云南临沧武警支队队长:深入QuickReport

来源:百度文库 编辑:偶看新闻 时间:2024/04/29 04:33:42
深入QuickReport(一)
作者:董维春
(本文已在《CSDN开发高手》04年第一、二、三期上发表,应广大网友的要求,经编辑同意,发表在CSDN作者本人文档中,略有修改,但仅即于此,未经CSDN或作者本人同意任何个人与网站不得转载、摘抄,否则任何涉及到版权的行为后果自负)
报表是数据库应用程序的基本组成部分之一,完整的数据库应用程序总要提供报表功能。与传统的数据库应用程序设计工具相比,C++ Builder中提供了QuickReport组件,使C++ Builder的数据库功能得到了极大的丰富。
QuickReport是挪威Qusoft AS公司专门为C++ Builder设计的用于报表的一组控件,在C++ Builder上我们使用的QuickReport与专业版的QuickReport相比,在功能上还有很大的差距,但对于我们来说只要充分利用好她,就可以非常快捷的设计出功能强大、模式多样的数据报表,最重要的是她不需要我们花额外的钱去购买。
其实QuickReport并不难,但介绍的这方面的内容实在太少,并且她的帮助文档写得也不太好,让那些想学习QuickReport的C++  Builder爱好者找不到一个正确的、合适的方法。如果你想了解她、使用她,那好吧,让我来帮你认识这个妖女吧,如果此文对你能有所帮助,那是我最大的快乐!
第一部分:QuickReport基本知识
BCB引进的Qusoft公司的报表组件系列,它包括多个组件。首先有必要先简单的了解一下这二十三个组件的基本位置与功能。
一、组件功能简介
上面的两个图是QuickReport组件页的中所有组件,在BCB6中提供给我们使用的不会少于上面的23个的。
下表是按上图中组件的位置先后,对各控件的使用功能逐一做了简单的介绍:
1)QuickRep
带有坐标,作为其他报表控件(如TQRBand)的容器,通过它的Band属性可以自动添加各种类型的TQRBand
重要属性 (Properties)
使用说明
DataSet
连结数据来源,一般是ADO/BDE连接组件的名字
Page
纸张的设置,可展开
BottomMargin
10.0mm
报表下边界尺寸
Columns
1
报表分栏数,默认为1栏
ColumnSpace
0.0mm
报表栏与栏间隔尺寸,Columns值大于1时有效
LeftMargin
10.0mm
报表左边界尺寸
Length
210.0mm
报表长度尺寸,与PaperSize设置相关
Orientation
poPortrait
poLandscape
报表方向 打印方向,有下面两个值:直印 poPortrait、横印poLandscape
PaperSize
A4
报表打印纸张大小
RightMargin
10.0mm
报表右边界尺寸
Ruler
true
报表标尺显示
TopMargin
10.0mm
报表上边界尺寸
Width
297.0mm
报表宽度尺寸,与PaperSize设置相关
PrinterSetting
Copies
1
报表复制份数
Duplex
false
报表双重打印
FirstPage
0
报表打印起始页
LastPage
0
报表打印终止页
OutPutBin
Auto
报表输出种类
ReportTitle
报表预览打印标题
Units
mm
报表设计阶段显示标尺单位
2)QRSubDetail
建立一个主/明细(master/detail) 报表,连接明细文件的组件
3)QRStringsBand
可建立一读取 TStrings 中项次值的组件。在这个组件里有一个Items属性,若Items一个值(一行空、一回车行)都没有,那他上面的组件将不会起作用。当String List Editor中有几行,该组件上的内容就重复几次。若只有一行内容时,她与一个DetailBand是一样的;而多行时,也是差不多的,即先重复该区段内的可视组件几次(几行就几次),然后下一个再重复。比如三行,那就是第一个数据来三行,然后第二个数据来三行,再然后,直到最后
4)QRBand
用来确定在报表的不同位置应该显示什么内容,它上面可以放控件
5)QRChildBand
在报表中基础子项条列组件。他有一个ParentBand属性,你必须把他与其它band连接起来,只有这样在她里面布置的组件才有效。说直接就是已经有了一个Band而你还需要一个这样的组件,那就用她吧
6)QRGroup
在报表中对资料做逻辑上分组的组件。当你连接了几个datasets到报表时可以通过Groups成组的对dataset进行操作
7)QRLabel
打印静态的文本(即不是根据数据库值来改变的),作用象Tlabel 。 Caption的内容就打印出来的内容;AutoStretch是布尔型变量,当标签标题在设定范围内不能打印时,该属性将起作用,若为true,报表将继续打印没有打印完的文本,其它需要打印的文本将依次向后移动,反之,则超出部分不被打印
8)QRDBText
在报表中具有显示连结资料来源的文字功能的组件
9)QRExpr
在报表中具有显示陈述句或计算表示式值的组件
10)QRSysData
在报表中具有显示系统信息的组件
11)QRMemo
在报表中显示备注文字的组件
12)QRExprMemo
在报表中是TQRExpr 和 TQRMemo 的混合组件
13)QRRichText
在报表中具有显示区域文字功能的组件
14)QRDBRichText
在报表具有显示连结资料来源的区域文字功能的组件
15)QRShape
在报表中处理几何图形的组件,如方形、圆角方形、椭圆
16)QRImage
在报表中显示静态的图片,包括(BMP,WMF,ICON)
17)QRDBImage
在报表中显示从数据库接收的图片
18)QRCompositeReport
在报表中连结有关与无关的报表组件 ,用于创建混合报表的组件,可以把两个或两个以上的报表连接在一起,组成一个报表
19)QRPreview
在执行阶段建立自订预览报表组件
20)QRTextFilter
将报表内容输出于ASCII text 文件
21)QRCSVFilter
将报表内容输出于CSV文件
22)QRHTMLFilter
将报表内容输出于HTML文件
23)QRChart
可以在报表上应用的图表工具
二、组件关系表
下图说明了与QuickReport相关组件之间的连接与关系:
┌ADOTable<=|                    ┌QRChilderBand┐
DataBase<=ADO<=│          |<=====QuickRep<=====├QRBand       │<===可视化组件
└ADOQuery<=|                    └QRStringBand ┘
数据库         ADO引擎         报表容器组件     Band区段(容器)  可视化控件(显示)
注意:可视化组件必须布置在相应的TQRBand区段上,否则将得不到要显示的内容。BDE引擎与这个差不多,他与ADO只是与库联接方式不一样,对QuickReport没有什么影响。
有时有些朋友不小心把可视化组件放在窗体上了,而没有直接放在TQuickRep组件中的各Band区段上,当把这些组件移到想要放置的区段时,发现这些组件不能用,所以你要切记:一定要把组件放在固定的区段上,并且在区段之间也是不能移动的。我们从Object TreeView中可以看到这些组件与TQuickRep是同一层,当然他不可能为TQuickRep工作了,只有他成为TQuickRep中的某一个区段(某个Band)的下一层时,才能正常工作。并且一定要放在一个区段中,不要误认为放在TQuickRep上就行,如果你直接放在TQuickRep上,你的报表中将得不到什么L
三、最大容器TQuickRep
QuickReport中组织层次是特别严格的,TQuickRep是报表组件中必须有的,并且所有Band组件都必须放在她的上面,如果说每个Band区段都是一个小容器的话,那她就是一个最大的容器。想用好QuickReport,TQuickRep绝对不能只看作是一个类似Form的容器,如何用好她对我们用好QuickReport会起到关键性作用。
1)主要属性:
DataSet是TDataSet数据集类型变量,用来设置报表使用的数据集组件对象,在主/明细报表中是主报表的数据集。若我们的报表要用到数据库,她必须指定数据库连接工具的名称,否则你的程序将看不到数据库中的数据。当然,在数据库的连接组件中也一定要把Active设为true,让不你只能在程序运行中动态的把数据库打开,而在程序设计阶段将看不到什么L
RecordCount 是数据集组件对象中记录的数量;
RecordNumber是当前正在打印/显示的记录的索引(第一条记录的索引是0);RecordCount、RecordNumber两个属性只有打开数据集对象之后,它们才可以访问。
若报表组件成功地从数据集组件对象中提取数据,并生成报表,则Available的属性值为true。
State属性是报表状态信息,它是枚举型变量,可以有以下的取值:
qrAvailable:报表资源已准备好,可以预览或打印;
qrPrepare:正在生成报表;
qrPreview:正在预览报表;
qrPrint:正在打印报表;
qrEdit:正在编辑报表。
Description是TStrings类型属性,它可以用来保存程序员对报表的简单描述。主要是起提示作用。
Page属性是TQuickRep组件中最重要属性,它是TQRPage类型变量,用来设置报表的页面属性。报表组件为她提供了特殊的编辑器,使用组件的快捷菜单Report  settings就可以打开Page的属性编辑器窗口。在报表页面属性对话框上可以设置页面的尺寸、边框、分栏、区段等等页面的属性。当然我们也可以在对象查看器中点击 Page前面的“+”展开它的所有属性,并分别设置这些属性。其中PagerSize是设置纸张大小的,他与Width、Length是密切相关的,改变PagerSize的值,后面两个的值也会做出相应的变化,同样,改变后两个属性时,PagerSize的值也会做出相应的改变。我们可以通过改变Orientation的值来改变打印的方向。Margins指的是报表显示内容在PagerSize中的位置。Columns属性我们一定注意,它是分栏设置,与WORD里的分栏设置是一样的,无论出于何种目的,有时我们要把打印内容分成两栏(或多栏)时,这时我们就要用到它,把它的值改为2(或实际栏数),至于栏间的距离我们通过Column space来设定。
Units属性是用来设置纸张的显示单位的,我们通常都选用mm(毫米),当然你要对其它比较了解也可以选用,但我认为最好不要选用其它项。
2)主要方法及事件
TQuickRep提供了大量方法以实现生成、预览和打印报表的功能,但是,它的许多方法是私有成员函数,在类外不允许访问。在实现报表功能时,应用程序经常需要调用Cancel、NewColumn、Preview、PreviewModal、PreviewModeless、Print、PrinterSetup和ResetPageFooterSize几个方面。
Cancel 方法用来撤销当前正在进行的操作,它与把TQuickRep 组件对象的Cancelled属性设置为true是一样的。
NewColumn、Print与PrinterSetup都是与打印有关的方法。其中NewColumn用来强制打印机从新栏开始打印,如果当前栏是页面的最后一栏,则该方法将自动调用方法NewPage ,从新页开始打印。
Preview、PreviewModal和PreviewModeless都是用来预览报表的。
这些方法都不需要使用任何参数。
TQuickRep能够响应的事件主要有:AfterPreview、AfterPrint、BeforePrint、OnEndPage、OnStartPage和OnPreview事件,这些事件的触发与使用方法比较简单,另外在实际应用中运用的并不多,这里就不再多说了。
四、 报表控件摆放的区段
作为报表,它通常有一个固定的模式,最常见的报表主要是由以下六部分组成:
PageHeader:页眉,每页均会出现
|
Title:标题, 只出现在第一页
|
ColumnHeader:所有列的标题,即报表文件的字段区域,每页只会出现一次
|
Detail:记录的内容,一个字段一列,即报表的文本区域(重复区)
|
Summary:摘要(只出现在最后一页)
|
PageFooter:页脚,每页均会出现
在QuickReport中,是通过不同的TQRBand区段来实现的。
TQRBand 是放在TQuickRep组件上面的一个容器,我们在其上面可以放置我们要打印的可视化QuickReport组件。而通过对TQRBand的BandType属性的设置,我们可以改变TQRBand区段的类型,来实现不同区段的功能。
BandType是一个枚举型变量,显然上面常见报表组成表中的那六个部分也一定会出现在BandType的枚举值中,只不过每个前面都加上了前缀rb。我们选择不同的BandType值,就代表不同的区段类型,实现不同的功能,这一点必须注意。
实际上TQRBand的BandType属性值不止上面的六个,她还有以下几个值:
rbGroupHeader:组页眉表格元,用于标志组页中每一页的开始,也用于TQRSubDetail;
rbGroupFooter:组页脚表格元,与rbGroupHeader结合分隔出单页,适用于TQRGroup和TQRSubDetail;
rbSubDetail:字数据表格元,无需手工设置;
rbChilder:字表格元,无需手工设置。
事实上,我们还可以在TQuickRep上来布置区段,我们只要把TQuickRep组件的Band属性前面的“+”点开,就会看到上面那六个值(当然每个前面都加上了Has,可不是rb了),这些值都是布尔型的,我们想要用哪个区段类型的TQRBand,只要把其值设为true,就可以了。从这一点可以看出:前者可以更加灵活的布置Band区段,后者使用起来更加方便、快捷,至于到时候你用哪个,那就看你当时的心情与实际情况了。单纯从结构严谨性上来讲,还是通过TQRBand的BandType来设定区段类型更好一些,并且这样做出的报表出现的问题最少。
五、报表运算组件TQRExpr与系统功能组件TQRSysData
报表运算组件TQRExpr用来提供一些简单的计算功能。Expression属性是她的最重要属性,我们有必要而且也必须掌握好她。
只要单击Expression右端的编辑按钮就可以打开表达式编辑窗口。利用这个编辑器,我们可以比较方便的设计表达式。通过表达式编辑器窗口中的按扭,可以在表达式中插入数据库中的表的字段、函数、数学和逻辑运算符,并可以通过Validate按扭来测试表达式的正确性。
当然,我们也可以直接在编辑器中输入表达式。
在她的函数中IF你一定要用好,因为她会帮我们很多忙,这个函数的对于学过DBASE的程序爱好者来说应该是非常熟悉的,因为在这里她们的使用是一样的;在C中,她与for(;;)语句或“?:”表达式差不多。她的具体使用格式如下:
IF(条件式,为真用这句,为假用这句)
系统功能组件TQRSysData可以为我们提供一些经常用到的系统功能。为了实现这些功能,他提供了两个重要属性Data和Text。
Data是一个枚举类型变量,它有七个取值,如下表:
系统功能类型表(Data取值表)
系统功能类型
功能描述
qrsTime
系统时间
qrsDateTime
系统日期和时间
qrsDetailCount
需要打印的记录数量
qrsDetailNo
正在打印的记录索引
qrsPageNumber
正在打印的页
qrsReportTitle
报表标题
qrsDate
系统日期
Text属性是字符串类型变量,用来构成完整的系统功能字符串。
Data和Text这两个属性通常是一起使用的。如果将Text设置为“打印时间:”,而将Data设置为qrsDataTime,则系统打印结果为“打印时间:2003-9-21 13:08:12”的形式。
许多情况下,我们更想打印如“第1页”这样格式的页码,使用上面的属性是不能实现的,这就要我们编写代码来实现。在打印/显示报表时,应用程序将触发OnPrint事件句柄。这样我们只要在报表运算组件TQRExpr或系统功能组件TQRSysData的OnPrint中写下如下代码就可以实现了:
void __fastcall TForm1::TQRExpr1Print(TObject *sender, AnsiString &Value)
{
Value="第"+Value+"页";
}
//-------------------------------------------------------------------

void __fastcall TForm1::TQRSysData1Print(TObject *sender, AnsiString &Value)
{
Value="第"+Value+"页";
}
//-------------------------------------------------------------------
通过这两段代码我们可以知道,原来这两个组件显示或起作用的就是Value,如果我们不修改Value的值,则直接打印Value内容。由于我们在OnPrint事件中修改了她的值,从而改变了打印的内容。
注意:有些朋友把上面代码输入了进去,却得不到正确的内容,原因很简单:TQRSysData的Data属性值设置不对,这个例子中我们要得到页数,看一下Data的值是不是qrsPageNumber,并且Text的值是否为空;对于TQRExpr组件,你是否通过Expression编辑器的Variable按钮进入了下一页,选择了PAGENUMBER值?这是你不能正确实现代码的问题所在。
六、TQRShape组件
TQRShape组件可以在报表上输出一些简单的图形,设置它的Shape属性可以选择不同的图形,但它主要用于绘制表格线,因此与TShape有些不同,下面我们来看一下Shape的取值:
TQRShape组件Shape属性取值表
Shape属性取值
意义
qrsCircle
画圆
qrsHorLine
画水平直线(Height要设为1或其它)
qrsRectangle
画矩形
qrsRightAndLeft
仅在矩形左右绘直线
qrsTopAndBottom
仅在矩形上下绘直线
qrsVertLine
画垂直线(Width要设为1或其它)
作为画图组件她的四个位置属性最为重要,它们是Top、Left、Height和Width。其实对于QuickRep的绝大多数组件来说,这四个属性也是非常重要的。
在Shape属性值为QrsHorLine/ QrsVertLine时,Height/Width的属性值要改为1或其它,这取决于你线条的宽度。
Brush属性用来设置几何图形内部的填充风格,它包括填充颜色Color和填充图案Style。
Pen属性用来设置用于画图的画笔的属性,它包括Color、Style、Mode、Wide等。
TQRShape组件与TShape组件有许多相同之处,但也有一些不同之处,象Shape属性的取值等等,我们一定注意。其实通过对两个类似组件的对比学习,会加深我们对她们的掌握。如果运用得当,漂亮的线条就会出现在我们面前。
对于TQRShape组件的使用来说更多的是熟练,她应该是所有QuickReport组件中最好掌握的一个了,使用率也是最高的一个,并且TQRShape在表格设计中的作用是其它组件所不能取代的,但本文限于篇幅就不在做过多的解释了。
第二部分:深入QuickReprot例程
在应用中学习,在实践中进步,这是我学习QuickReport的最大感受,通过上面的介绍,我想你对QuickReport也有了一定的了解,那么下一步我们将通过例程逐步深入QuickReportJ
例一:TQuickRep、TQRLabel、TQRDBText组件的应用
实践是最好的老师,我们先通过一个例子来看一下QR的快捷与方便。
1) 建立一个新Project 。
2)放一个TTable到Form上,这里我们用BCB中自带的数据库。DatabaseName设为BCDEMOS,TableName指向Customer表,Active设为true。
3) 放一个TQuickRep控件在Form上,DataSet属性为Table1(即要显示Table1所指向的表的内容)。
4)展开TQuickRep的Bands属性,设HasDetail为true,这时自动增加个Detail Band(一个TQRBand控件,故也可以直接放一个TQRBand控件,BandType属性设为rbDetail就行了)。这个区段是重复区段。
5)放一个TQRLabel与TQRDBText控件在Detail Band上面,TQRLabel的Caption属性设置为“公司:”二字;TQRDBText的 DataSet指向Table1,DataField指向Company。
6)选TQuickRep控件(不要指在区段上),按右键,选择“Preview”预览,应该看到表Customer的字段Company中所有字段值。
到这一个简单的例子就OK了。但这个程序你编译后,会得到与你布置时一样的QuickReport组件,看不到你想要的报表内容。程序要实现刚才Preview时的效果只能利用TQRuickRep自带的打印功能了,我们只要在Form中加上一个按钮,在它的OnClick事件中写上:QuickRep1->Preview(); 就可以了。没有人希望那个什么也不显示的TQuickRep组件摆在窗体上,这也告诉我们一点我们的程序中最好有两个Form:一个是放置控制QuickReport显示、打印或实现其它功能的,比如本例中放置控制按扭;一个是提供给QuickReport布置组件的。
当然这不是说在一个Form中实现不了上述功能,其实你只要在窗体的OnCreate事件中把TQuickRep组件隐藏起来就可以了:
void __fastcall TForm1::Form1Create(TObject *Sender)
{
QuickRep1->Hide();
}
要注意QR你不能用Show()调用,那你将得不到你想要的程序,当然用Preview()就没错了。以下是本例的程序源代码:
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------
void __fastcall TForm1::Form1Create(TObject *Sender)
{
QuickRep1->Hide();
}
//---------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
QuickRep1->Preview();
}
//---------------------------------------------------------------
进一步思考:有时我们想实现垂直输出,比如用TQRLabel里的内容我们想垂直显示能否实现?
当然能实现。只要我们按如下设置就可以实现:
AutoSize=false;//默认为true
AutoStrech=true;// 默认为false
WordWrap=true;// 默认为true,这是设置中的重点,这与Label中的设置是一样的
Caption的值要注意,每个字符间一定要有空格;//一般的设置我们不会留空格,因为设计空格除了美观,没什么实际意义,但在这里必须留空格
进一步思考:我们想显示有两位小数的数字,应该如何办呢?
只要把该可视化组件的mask属性设为0.00就可以实现了。
例二:TQRGroup、TQRExpr组件的应用
该报表先列出州名,接着列出该州的所有公司及公司总数,一个州列完后空一行(Group中断),列出新的州名,接着列新州下的所有公司,没有填州名的公司统一列在“不清楚什么地区的”下面。
1)新建一个Project。
2)放一个TQuery在TForm上,其SQL属性为:select * from customer order by State,Company,即根据州、公司排序,DatabaseName为BCDEMOS,Active为true。
3) 放一个TQuickRep控件在Form1上,DataSet为Query1。
4)放一个TQRBand在TQuickRep上面,把其BandType设成rbDetail。
5)放一个TQRGroup控件在TQuickRep上,这时默认为Group Header。(任何时候当Group中断或更高级别的Group中断,这个Header都将打印出来,如果有表达式,根据表达式的值显示内容),接着添加一个Group Footer Band,但我们却不能找到这样的一个组件,那应该如何设计出Group Footer Band区段呢?我们只要放一个QRBand2在报表上,把TQRGroup的FooterBand属性指向QRBand2,看一下,QRBand2是不是变成了Group Footer Band 。(TQRGroup的一个重要特性是表达式,任何时候当表达式的值改变时,Group都将中断,如表达式是按省列出城市名,当前列出辽宁省,当属于辽宁省的城市列完后,表达式值改变,这时Group中断,接着显示其他省的城市名),TQRGroup1的Expression属性设为Query1.State(根据不同的州来中断)。
注意:为什么不直接再放一个TQRGroup,因为无论我们放多少个系统都会默认为 Group Header。
6)放一个TQRExpr控件在Group Header上面,其Expression属性为:IF(Query1.State <> '',Query1.State,'不清楚什么地区的'),即如果公司的州没填,就归入“不清楚什么地区的”,否则归入具体的州。
7)放三个TQRDBText在Detail上,他们的DataSet都指向Query1,DataField分别指向Company、Contact、Phone。
8)再放一个TQRExpr控件在Group Footer  Band上面,Expression为Query1.State+'共有:'+STR(COUNT)+’个公司’,作用是在每个州的公司列完后显示这个州总共有多少个公司。
9)把鼠标放在QuickRep组件上(不能放在Band区段上),按右键选预览,应该看到不同的州名及其公司名称和该州公司总数。
注意:大家发现看到并不是每个州公司的总数,每个州后列出的都是把上一个州的总数也加上的累计值,难道是我们错了,没有,检查一下你的Expression组件(运行COUNT那个)的ResetAfterPrint的属性值是否为true,如果不是把他改为true,现在看一下J
进一步思考:每一组内容都联在一起,有些不爽,要是每组内容之间被什么分隔开就好了?
其实这并不难实现,我们只要把QRBand2(group Footer)的HasChild属性设为true就可以了。当然你直接把一个TQRChildBand组件放在QuickRep1上,把她的ParentBand属性设置为QRBand2,同样可以实现上面的设置。另外我们同样可以在TQRChildBand布置其它可视化组件J
进一步思考:要是每组结束后就换页,那不是更爽吗?
只要我们把QRGroup1的ForceNewPage属性改为true就可以了,同样的组件还有一个ForceNewColumn。但一定要注意,我们改了哪个区段的这两个属性,就从哪个区段开始NewPage/NewColumn。
例三、TQuickRep组件的Page属性应用
PVC胸卡有些朋友一定见过,做这样的卡片并不太难,最近笔者就参与制作了一批胸卡,这里我们要讲的是卡片的设计。
1)新建一个Project。
2)放一个Table组件,把它指向我们的资源数据库,并把Active设为true。
3)放置一个TQuickRep组件在Form1上,并把它DataSet设置为Table1,在她上面放一个TQRBand,把其BandType值设置成rbDetail。在Band区段上面按下图放置好四个TQRLabel组件、三个TQRDBText组件与一个TQRDBImage组件。
4)三个TQRDBText组件的设置基本上一样的,把DataSet指定为Table1,然后在从DataField中选取正确的字段。我们可以同样的设置好TQRDBImage的相关属性。
5)对页面进行设置,这是关键。一张纸上我们不可能只打印一个,打印得越多越多好,左边打完,在右边继续打印,这样才能充分利用原材料。我们这里用到了分栏,把Number of columns属性设为2,也就是分两栏。在TQuickRep中使用组件的快捷菜单Report  settings打开Page的属性编辑器窗口,做如下设置:
其中Pager size选择为自定义,在其后输入纸的大小,其它设置按上图即可。当然我们也可以在对象查看器窗口单击Page属性左端的编辑按钮,展开其属性值来进行正确的设置,如下图:
6)TQRBand的Width设为228,然后重新调整好Band上组件的位置。228实际上是由38×6得到的。
7)把鼠标放在QuickRep组件上(不能放在Band区段上),按右键选预览,看一下效果吧。
难点:为了定位方便、准确, TQuickRep组件提供了坐标(一格一格的,边上带数字的,不会告诉我没看见吧J),从对纸张的设置我们知道这些坐标每整格相距为10mm,但在QR中,很多组件都没有单位,对于这10 mm,QR中对应的单位长度是38,这一点你一定要记住。
通过上面的三个例子,我们已经可以进行了一些简单的报表设计,从例四开始我们将进一步的学习QuickReport,下面例程也只讲关键点,着重分析,省略一些重复性的语言。
例四、主/明细(Master/Detail)报表与TQRSubDetail组件的应用
设计主/明细报表的关键有两点:一是主/明细数据表的连接;二是对TQRSubDetail组件的正确使用。
主从数据表连接示意图1(TTable与TTable)
Master表                 关联             Detail表
组件名                     TTable          TDataSource1       TTable
重要属性                 Name     <=== DataSet              Name
TableName                Name   <====DataSource
DabaseName<===指向同一库名==>DabaseName
(指定索引字段)IndexFieldName                          MasterField(点击右侧“…”调出Field Link Designer对话框,设置好关联字段)
Active<=======同设为true=======>Active
主从数据表连接示意图2(TTable与TQuery)
Master表                 关联             Detail表
组件名                     TTable          TDataSource1       TTQuery
重要属性                 Name     <=== DataSet              Name
TableName                Name   <====DataSource
DabaseName<===指向同一库名==>DabaseName
(指定索引字段)IndexFieldName                                     SQL(点击右侧“…”调出String  list  Editor对话框设置好关联字段)
Active<=======同设为true=======>Active
TQRSubDetail组件的设置:
1)Master的值设为所在TQuickRep组件的名;
2)DataSet的值设为连接明细表的组件名;
3)点击Bands前面的“+”,展开属性,能看到HasFooter与HasHeader 两个属性,把其值设为true,这样我们就得到了一对Group(当然完全我们完全可以用别的方法)。
注意:通过TQRSubDetail属性Band里的HasHeader、HasFooter产生的Group组,在Group Header中没有Expression,她们默认按主/明细表的关联关系分组。其实没有他们,TQRSubDetail也是这样用的。
我们建立如下图的主/明细报表:
1)设置数据集
在窗体上放置TTable、TDataSource、TQuery组件,把Table1、Query1的DatabaseName属性设置为BCDEMOS,把Table1的TableName设为customer.db(主表名),把IndexFieldName设为索引字段名CustNo。把DataSource1的DataSet设为Table1,为做主/明细数据做好准备。
连接明细表的Query1,我们把其DataSource设为DataSource1,从而建立关联关系,并将其SQL属性设成:
SELECT  *        FROM  orders         WHERE       CustNo         =:             CustNo
^                          ^                     ^              ^             ^                    ^
||                          ||                     ||             ||             ||                    ||
选择所有字段   从orders表(从表)    条件   从表的字段   建立主从关系  主表字段
这样我们就建立了明细表与主表的关联,明细表是按上式关系分组的,就是把CustNo一样的放在一起。
2)设置报表结构
首先,在前面设计的窗体上放置一个报表组件TQucikRep,在对象查看器窗口中把Band属性展开,将其HasColumnHeader、HasDetail、HasPageFooter、HasPageHeader、HasSummary和HasTitle属性设为true。
把QuickRep1的DataSet属性设为Table1,为报表主表指定数据源。
然后,把TQRSubDetail组件放到QuickRep1组件上,作为明细表Band区段。把其Master属性设置为QuickRep1,设置Bands的子属性HasHeader和HasFooter为true,并将其DataSet属性设置为Query1,指定明细表数据来源。
3)设置主/明细报表的主体
在ColumnHeaderBand1区段中添加主表中各字段的标题使用的报表标签组件(TQRLable),它们的Caption分别为客户号、公司、电话、传真和所在城市。在DetailBand1区段中添加显示主表字段值的报表组件(TQRDBText),与前面标题对应设置其字段,要注意的是它们的DataSet都为Table1。
在明细报表部分,我们首先在GroupHeaderBand1中放置明细表的表头标签(TQRLable),它们的Caption依次设置为定单号、条款、付款方式、款项总额和未付款额。在QRSubDetail1区段中放置显示明细报表的组件对象(TQRDBText),其DataSet属性全设为Query1,字段名称依次是OrderNo、Terms、PaymentMethod和ItemsTotal。接着添加一个TQRExpr组件,设置其Master属性为QRSubDetail,打开表达编辑器,输入下面的表达式:
INT(Query1.ItemsTotal-Query1.AmounPaid)
在GroupFooterBand1区段中添加统计报表组件QRExpr2,并将其属性设置为QRSubDetail1,打开表达式编辑器,输入如下 表达式:
SUM(INT(Query1.ItemsTotal-Query1.AmounPaid))
即计算未付款项的总额,并设置ResetAfterPrinter属性为true,这样,就可以统计出明细表的总额了。
在SummaryBand1区段中添加一个QRExpr3,打开表达式编辑器,在其中输入如下语句:
SUM(INT(Query1.ItemsTotal-Query1.AmounPaid))
这样这个报表的主体我们就做完了,其它设置看一下上面的图你就应该明白了。
下面我们再添加例一中的那样的程序代码,至此这样的一个复杂报表我们就完成了。
本例是用TTable与TQuery做的主/明细数据关联,同样我们可以用TTable与TTable做主/明细数据关联,实现本例这样的报表。
第三部分:报表中的其他问题
通过前两部分的学习,我想你对QuickRep已经有了一定的掌握,在这部分我们对报表设计中的其他一些问题做一下简单介绍,也许这些你并不常用,但同样这些内容对于我们学习QuickRep还是有很大益处的。
此部分内容都以例程的形式讲解,为了保持文章的完整性,例子的编号接上部分。
例五、报表的连接及保存
通过上面的例子,你也许会动手做了几个报表,有时你一定会想把其中的一些报表连接起来,组成一个综合报表,并作为整体来操作。在BCB中实现这一点并不难,我们这时要用到TQRCompositeReport组件。它提供了一个OnAddReprots事件,在创建报表时将触发这个事件,因此我们只要在这个事件中用Add方法将需要连接在一起的报表添加到该组件的事件中就可以了。下面给出一个示例程序段,这是把两个报表添加到综合报表中的,代码如下:
void __fastcall TForm1::QRCompositeReportAddReports(TObject *Sender)
{
((TQRCompositeReport*)Sender)->Reports->Add(Form2->QuickRep1);//添加第一个报表
((TQRCompositeReport*)Sender)->Reports->Add(Form3->QuickRep1);//添加第二个报表
}
做好的报表我们一定都想保存起来,保存的文件格式有:文本格式文件(TXT),组件TQRTextFilter;超文本格式文件(HTML/HTM),组件TQRHTMLFilder;逗号分隔文件(CSV),组件TQRCSVFilter;以及报表文件。保存前三种格式文件需要调用ExportToFilter方法,而直接保存报表组件,则只需用Save。这个例子中我们放了一个TSaveDialog对话框和QuickReport组件页中的TQRTextFilter、TQRHTMLFilder、TQRCSVFilter三个组件。完整的代码如下:
void __fastcall TForm1:: SaveReportClick(TObject *Sender)
{
AnsiString FileExt;
//  打开保存文件对话框获得文件名
if(SaveDialog1->Execute())
{
//  获得文件后缀
FileExt = AnsiUpperCase(ExtractFileExt(SaveDialog1->FileName));
//  输出Html超文本文件
if((FileExt == ".HTML") || (FileExt == ".HTM"))
QuickRep1->ExportToFilter(new TQRHTMLDocumentFilter(SaveDialog1->FileName));
//  输出txt文本文件
else if(FileExt == ".TXT")
QuickRep1->ExportToFilter(new TQRAsciiExportFilter(SaveDialog1->FileName));
//  输出CSV文件
else if(FileExt == ".CSV")
QuickRep1->ExportToFilter(new TQRCommaSeparatedFilter(SaveDialog1->FileName));
//  输出报表文件
else
{
QuickRep1->QRPrinter->Save(SaveDialog1->FileName);
}
}
}
//-------------------------------------------------------------------
例六、自定义报表预览窗口
QuickReport的报表预览功能总是不能达到令人满意的效果,因此,我们有必要自定义快速报表的预览窗口,以达到完美的设计要求。
1)设置预览窗口
新建工程,在Form1窗体上添加一个ToolBar控件,并在其上添加以下按钮:“调入报表”、“打印”、“打印设置”、 “上一页” 、“下一页”、“放大”、“缩小”和“关闭”。在Form1窗体上添加一个StatusBar,双击该组件,在编辑器中插入三项,在第三个项中显示页面信息。在Form1窗体上添加一个TQRPreview控件,对齐方式设为alClient,Form1窗体的外观如图1所示:
再新建一个窗体,设其Name为Form2, 在该窗体上添加TQuickRep控件,设其Name为QuickRep1,其PrinterSetting中的Units属性设为mm(以毫米为计量单位),然后建立报表。
2)编程实现
(1)在Form2上选择QuickRep1,在其事件中选择OnPreview,输入以下代码:
#include
#pragma hdrstop
#include "Unit2.h"
#include "Unit1.h"//调用Form1的内容
//-------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm2 *Form2;
//-------------------------------------------------------------------
__fastcall TForm2::TForm2(TComponent* Owner)
: TForm(Owner)
{
}
//-------------------------------------------------------------------
void __fastcall TForm2::QuickRepPreview(TObject *Sender)
{
Form1->QRPreview1->QRPrinter=Form2->QuickRep1->QRPrinter;//最为关键的一步
}
//-------------------------------------------------------------------
(2)为Form1中的各功能按钮的OnClick事件添加如下代码:
#include
#pragma hdrstop
#include "Unit1.h"
#include "Unit2.h"//调用Form2的内容
//-------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//-------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//-------------------------------------------------------------------
//调入报表
void __fastcall TForm1::QRLoadClick(TObject *Sender)
{
Form2->QuickRep1->Prepare(); //不加入该句,下一句的共几页将不能正确出现
StatusBar1->Panels->Items[2]->Text="第"+IntToStr(Form1->QRPreview1->PageNumber)+"页"+"共" + IntToStr(Form2->QuickRep1->PageNumber)+"页";
Form2->QuickRep1->Preview();//显示页面的信息
}
//-------------------------------------------------------------------
//打印报表
void __fastcall TForm1::PrintClilck(TObject *Sender)
{
Form2->QuickRep1->Print();
}
//-------------------------------------------------------------------
//打印设置
void __fastcall TForm1::PrintSetupClick(TObject *Sender)
{
Form2->QuickRep1->PrinterSetup();
Form2->QuickRep1->Print();//不加这句可只能设置,不能打印,在我的电脑上测试是这样的
}
//-------------------------------------------------------------------
//上一页
void __fastcall TForm1::PageUpClick(TObject *Sender)
{
if(QRPreview1->PageNumber>1)
QRPreview1->PageNumber--;
StatusBar1->Panels->Items[2]->Text="第"+IntToStr(Form1->QRPreview1->PageNumber)+"页"+"共" +IntToStr(Form2->QuickRep1->PageNumber)+"页";
}
//-------------------------------------------------------------------
//下一页
void __fastcall TForm1::PageDownClick(TObject *Sender)
{
if(QRPreview1->PageNumber < Form2->QuickRep1->PageNumber)
QRPreview1->PageNumber++;
StatusBar1->Panels->Items[2]->Text="第"+IntToStr(Form1->QRPreview1->PageNumber)+"页"+"共" +IntToStr(Form2->QuickRep1->PageNumber)+"页";
}
//-------------------------------------------------------------------
//报表放大
void __fastcall TForm1::ZoomInClick(TObject *Sender)
{
if(QRPreview1->Zoom<200)
QRPreview1->Zoom+=5;
}
//-------------------------------------------------------------------
//报表缩小
void __fastcall TForm1::ZoomOutClick(TObject *Sender)
{
if(QRPreview1->Zoom>5)
QRPreview1->Zoom-=5;
}
//-------------------------------------------------------------------
//程序关闭
void __fastcall TForm1::CloseClick(TObject *Sender)
{
Close();
}
//-------------------------------------------------------
上面只是做了一个简单的设计,你完全可以把它做得功能更强大、外观更漂亮一些。
例七、QR组件的动态设置及探讨
BCB中提供了大量的VCL组件,有时难免要在程序中动态创建组件,VCL是用Object Pascal写的,所以VCL类的对象我们只能在堆中创建。
如创建一个TQRLable对象,我们可以这样来创建:
TQRLable *MyQRLable= new TQRLable(From1);
即写成如下程式:
类名  *对象名=new 类名(…);
注意:()里面可以是你已创建的该类对象的父类名字、工程的名字、NULL或this。但最好是对象的父类名。
例:动态生成TQRLable组件
我们先在窗体(Form1)上,放上一个TQuickRep,并在其上放一个Band。在Form1窗体中按钮Button1的单击事件中写上如下代码:
void __fastcall TForm1::Button1Click(TObject *Sender)
{
TQRLable *MyQRLable= new TQRLable(From1);
MyQRLable ->Parent=Band1;//最为关键的一句,否则你将看不到什么,但编译却是正确的
MyQRLable ->Top=10;
MyQRLable ->Left=38;
MyQRLable ->Height=25;
MyQRLable ->Width=100;
MyQRLable ->Caption="I'm MyQRLable!";
}
通过这个例子我们应该清楚的看出动态创建QR组件的几个重要步骤:
1)要一个空间(内存);// TQRLable *MyQRLable= new TQRLable(From1);
2)指定其父组件,说直接了就是为我们要创建的这个对象指定一个容器;//     MyQRLable ->Parent=Band1;
3)指定组件要出现在父组件的哪个位置;// MyQRLable ->Top=10; MyQRLable ->Left=38; MyQRLable ->Height=25; MyQRLable ->Width=100;
4)其它重要属性。//   MyQRLable ->Caption="I'm MyQRLable!";
注意上面的步骤不能任意安排,否则你的程序会出笑话的。
在动态生成非宝兰VCL原有的组件时要加上对应的头文件。我们要动态生成报表组件时一定要加入:
#include  “Qrctrls.hpp”//若还有问题,你还要加入:
#include “QuickRpt.hpp”
另外由于BCB对内存管理或与系统、硬件的冲突,你的动态创建程序也许一点错误都没有,但就是编译不了;有时也许第一次通过了,第二次一样的程序却通过不了,出现这样那样的提示,最简单的办法就是注销一下系统,再试一下,多数就能解决了。
既然这样的动态产生组件会出现很多不可遇见的问题 ,那我们还有没有更好的办法来实现类似的功能呢?有,答案是一定的。
我们可以在TQuickRep中把所有组件都放置好,各区段组件,可视化组件都放在应该放的位置上,只是一些特定的属性我们先不给出,而通过程序给出,这样就可以仿制动态创建组件的方法来动态产生报表。说简单了就是事先把组件都准备好了,用的时候拿出来,不用的,不给定关键属性,即用属性废掉它,让它根本就不起作用。
例:动态设置QRDBText的属性值
void __fastcall TForm1::Button1Click(TObject *Sender)
{
QRDBText1->DataSet=Table1;
QRDBText1->DataField="AREA";//改成双引号后,一切OKJ
QuickRep1->Preview();
Table1->Close();
}
//---------------------------------------------------------------------------

void __fastcall TForm1::Button1Click(TObject *Sender)
{
QRDBText1->DataSet=Table1;
QRDBText1->DataField=Table1->Fields->Fields[3]->FieldName;
QuickRep1->Preview();
}
//---------------------------------------------
注意:下面的程序是错误的
void __fastcall TForm1::Button1Click(TObject *Sender)
{
QRDBText1->DataSet=Table1;
QRDBText1->DataField=Table1->FieldValues["AREA"];//注意这将得不到你想得到的内容,因为他不是把字段名给了DataField
QuickRep1->Preview();
}
单就一个QuickReport动态生成报表,我想不是三言两语就能说完的,单独成文也不为过,好在我也把动态生成报表的一些常见问题都说出来了,解决之道上面也提了,并且还给出了几个例子,通过这些介绍,你要是有精力的话一定会做出一个不出问题的动态生成报表系统。
以上程序均在XP系统、BCB6、EPSON C43UX下编译通过。
结束语:写到这里我相信多数的读者对QuickReport应该有了一个比较全面、深入的了解了。但当我仔细读了一遍这篇文章后,发现还有很多内容没有写出来,让我意识到想在一篇文章里把QuickReport全都写出来,还真有些困难,这样也好,留点以后再写,让不闲下来做什么呢J
其实说简单了,QuickReport并不难学,只是有时我们把她孤立起来看,而没有想到, 其实QuickReport就是那么回事,她在BCB上出现也不是一天两天了,现在的她可以说早已完全融入到VCL组件中去了。所以说,想真正学通QuickReport,关键还是取决于我们对VCL的掌握程度。
深入QuickReport(二)
作者:董维春
(本文已在《CSDN开发高手》04年第一、二、三期上发表,应广大网友的要求,经编辑同意,发表在CSDN作者本人文档中,略有修改,但仅即于此,未经CSDN或作者本人同意任何个人与网站不得转载、摘抄,否则任何涉及到版权的行为后果自负)
第二部分:深入QuickReprot例程
在应用中学习,在实践中进步,这是我学习QuickReport的最大感受,通过上面的介绍,我想你对QuickReport也有了一定的了解,那么下一步我们将通过例程逐步深入QuickReportJ
例一:TQuickRep、TQRLabel、TQRDBText组件的应用
实践是最好的老师,我们先通过一个例子来看一下QR的快捷与方便。
1) 建立一个新Project 。
2)放一个TTable到Form上,这里我们用BCB中自带的数据库。DatabaseName设为BCDEMOS,TableName指向Customer表,Active设为true。
3) 放一个TQuickRep控件在Form上,DataSet属性为Table1(即要显示Table1所指向的表的内容)。
4)展开TQuickRep的Bands属性,设HasDetail为true,这时自动增加个Detail Band(一个TQRBand控件,故也可以直接放一个TQRBand控件,BandType属性设为rbDetail就行了)。这个区段是重复区段。
5)放一个TQRLabel与TQRDBText控件在Detail Band上面,TQRLabel的Caption属性设置为“公司:”二字;TQRDBText的 DataSet指向Table1,DataField指向Company。
6)选TQuickRep控件(不要指在区段上),按右键,选择“Preview”预览,应该看到表Customer的字段Company中所有字段值。
到这一个简单的例子就OK了。但这个程序你编译后,会得到与你布置时一样的QuickReport组件,看不到你想要的报表内容。程序要实现刚才Preview时的效果只能利用TQRuickRep自带的打印功能了,我们只要在Form中加上一个按钮,在它的OnClick事件中写上:QuickRep1->Preview(); 就可以了。没有人希望那个什么也不显示的TQuickRep组件摆在窗体上,这也告诉我们一点我们的程序中最好有两个Form:一个是放置控制QuickReport显示、打印或实现其它功能的,比如本例中放置控制按扭;一个是提供给QuickReport布置组件的。
当然这不是说在一个Form中实现不了上述功能,其实你只要在窗体的OnCreate事件中把TQuickRep组件隐藏起来就可以了:
void __fastcall TForm1::Form1Create(TObject *Sender)
{
QuickRep1->Hide();
}
要注意QR你不能用Show()调用,那你将得不到你想要的程序,当然用Preview()就没错了。以下是本例的程序源代码:
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------
void __fastcall TForm1::Form1Create(TObject *Sender)
{
QuickRep1->Hide();
}
//---------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
QuickRep1->Preview();
}
//---------------------------------------------------------------
进一步思考:有时我们想实现垂直输出,比如用TQRLabel里的内容我们想垂直显示能否实现?
当然能实现。只要我们按如下设置就可以实现:
AutoSize=false;//默认为true
AutoStrech=true;// 默认为false
WordWrap=true;// 默认为true,这是设置中的重点,这与Label中的设置是一样的
Caption的值要注意,每个字符间一定要有空格;//一般的设置我们不会留空格,因为设计空格除了美观,没什么实际意义,但在这里必须留空格
进一步思考:我们想显示有两位小数的数字,应该如何办呢?
只要把该可视化组件的mask属性设为0.00就可以实现了。
例二:TQRGroup、TQRExpr组件的应用
该报表先列出州名,接着列出该州的所有公司及公司总数,一个州列完后空一行(Group中断),列出新的州名,接着列新州下的所有公司,没有填州名的公司统一列在“不清楚什么地区的”下面。
1)新建一个Project。
2)放一个TQuery在TForm上,其SQL属性为:select * from customer order by State,Company,即根据州、公司排序,DatabaseName为BCDEMOS,Active为true。
3) 放一个TQuickRep控件在Form1上,DataSet为Query1。
4)放一个TQRBand在TQuickRep上面,把其BandType设成rbDetail。
5)放一个TQRGroup控件在TQuickRep上,这时默认为Group Header。(任何时候当Group中断或更高级别的Group中断,这个Header都将打印出来,如果有表达式,根据表达式的值显示内容),接着添加一个Group Footer Band,但我们却不能找到这样的一个组件,那应该如何设计出Group Footer Band区段呢?我们只要放一个QRBand2在报表上,把TQRGroup的FooterBand属性指向QRBand2,看一下,QRBand2是不是变成了Group Footer Band 。(TQRGroup的一个重要特性是表达式,任何时候当表达式的值改变时,Group都将中断,如表达式是按省列出城市名,当前列出辽宁省,当属于辽宁省的城市列完后,表达式值改变,这时Group中断,接着显示其他省的城市名),TQRGroup1的Expression属性设为Query1.State(根据不同的州来中断)。
注意:为什么不直接再放一个TQRGroup,因为无论我们放多少个系统都会默认为 Group Header。
6)放一个TQRExpr控件在Group Header上面,其Expression属性为:IF(Query1.State <> '',Query1.State,'不清楚什么地区的'),即如果公司的州没填,就归入“不清楚什么地区的”,否则归入具体的州。
7)放三个TQRDBText在Detail上,他们的DataSet都指向Query1,DataField分别指向Company、Contact、Phone。
8)再放一个TQRExpr控件在Group Footer  Band上面,Expression为Query1.State+'共有:'+STR(COUNT)+’个公司’,作用是在每个州的公司列完后显示这个州总共有多少个公司。
9)把鼠标放在QuickRep组件上(不能放在Band区段上),按右键选预览,应该看到不同的州名及其公司名称和该州公司总数。
注意:大家发现看到并不是每个州公司的总数,每个州后列出的都是把上一个州的总数也加上的累计值,难道是我们错了,没有,检查一下你的Expression组件(运行COUNT那个)的ResetAfterPrint的属性值是否为true,如果不是把他改为true,现在看一下J
进一步思考:每一组内容都联在一起,有些不爽,要是每组内容之间被什么分隔开就好了?
其实这并不难实现,我们只要把QRBand2(group Footer)的HasChild属性设为true就可以了。当然你直接把一个TQRChildBand组件放在QuickRep1上,把她的ParentBand属性设置为QRBand2,同样可以实现上面的设置。另外我们同样可以在TQRChildBand布置其它可视化组件J
进一步思考:要是每组结束后就换页,那不是更爽吗?
只要我们把QRGroup1的ForceNewPage属性改为true就可以了,同样的组件还有一个ForceNewColumn。但一定要注意,我们改了哪个区段的这两个属性,就从哪个区段开始NewPage/NewColumn。
例三、TQuickRep组件的Page属性应用
PVC胸卡有些朋友一定见过,做这样的卡片并不太难,最近笔者就参与制作了一批胸卡,这里我们要讲的是卡片的设计。
1)新建一个Project。
2)放一个Table组件,把它指向我们的资源数据库,并把Active设为true。
3)放置一个TQuickRep组件在Form1上,并把它DataSet设置为Table1,在她上面放一个TQRBand,把其BandType值设置成rbDetail。在Band区段上面按下图放置好四个TQRLabel组件、三个TQRDBText组件与一个TQRDBImage组件。
4)三个TQRDBText组件的设置基本上一样的,把DataSet指定为Table1,然后在从DataField中选取正确的字段。我们可以同样的设置好TQRDBImage的相关属性。
5)对页面进行设置,这是关键。一张纸上我们不可能只打印一个,打印得越多越多好,左边打完,在右边继续打印,这样才能充分利用原材料。我们这里用到了分栏,把Number of columns属性设为2,也就是分两栏。在TQuickRep中使用组件的快捷菜单Report  settings打开Page的属性编辑器窗口,做如下设置:
其中Pager size选择为自定义,在其后输入纸的大小,其它设置按上图即可。当然我们也可以在对象查看器窗口单击Page属性左端的编辑按钮,展开其属性值来进行正确的设置,如下图:
6)TQRBand的Width设为228,然后重新调整好Band上组件的位置。228实际上是由38×6得到的。
7)把鼠标放在QuickRep组件上(不能放在Band区段上),按右键选预览,看一下效果吧。
难点:为了定位方便、准确, TQuickRep组件提供了坐标(一格一格的,边上带数字的,不会告诉我没看见吧J),从对纸张的设置我们知道这些坐标每整格相距为10mm,但在QR中,很多组件都没有单位,对于这10 mm,QR中对应的单位长度是38,这一点你一定要记住。
通过上面的三个例子,我们已经可以进行了一些简单的报表设计,从例四开始我们将进一步的学习QuickReport,下面例程也只讲关键点,着重分析,省略一些重复性的语言。
例四、主/明细(Master/Detail)报表与TQRSubDetail组件的应用
设计主/明细报表的关键有两点:一是主/明细数据表的连接;二是对TQRSubDetail组件的正确使用。
主从数据表连接示意图1(TTable与TTable)
Master表                 关联             Detail表
组件名                     TTable          TDataSource1       TTable
重要属性                 Name     <=== DataSet              Name
TableName                Name   <====DataSource
DabaseName<===指向同一库名==>DabaseName
(指定索引字段)IndexFieldName                          MasterField(点击右侧“…”调出Field Link Designer对话框,设置好关联字段)
Active<=======同设为true=======>Active
主从数据表连接示意图2(TTable与TQuery)
Master表                 关联             Detail表
组件名                     TTable          TDataSource1       TTQuery
重要属性                 Name     <=== DataSet              Name
TableName                Name   <====DataSource
DabaseName<===指向同一库名==>DabaseName
(指定索引字段)IndexFieldName                                     SQL(点击右侧“…”调出String  list  Editor对话框设置好关联字段)
Active<=======同设为true=======>Active
TQRSubDetail组件的设置:
1)Master的值设为所在TQuickRep组件的名;
2)DataSet的值设为连接明细表的组件名;
3)点击Bands前面的“+”,展开属性,能看到HasFooter与HasHeader 两个属性,把其值设为true,这样我们就得到了一对Group(当然完全我们完全可以用别的方法)。
注意:通过TQRSubDetail属性Band里的HasHeader、HasFooter产生的Group组,在Group Header中没有Expression,她们默认按主/明细表的关联关系分组。其实没有他们,TQRSubDetail也是这样用的。
我们建立如下图的主/明细报表:
1)设置数据集
在窗体上放置TTable、TDataSource、TQuery组件,把Table1、Query1的DatabaseName属性设置为BCDEMOS,把Table1的TableName设为customer.db(主表名),把IndexFieldName设为索引字段名CustNo。把DataSource1的DataSet设为Table1,为做主/明细数据做好准备。
连接明细表的Query1,我们把其DataSource设为DataSource1,从而建立关联关系,并将其SQL属性设成:
SELECT  *        FROM  orders         WHERE       CustNo         =:             CustNo
^                          ^                     ^              ^             ^                    ^
||                          ||                     ||             ||             ||                    ||
选择所有字段   从orders表(从表)    条件   从表的字段   建立主从关系  主表字段
这样我们就建立了明细表与主表的关联,明细表是按上式关系分组的,就是把CustNo一样的放在一起。
2)设置报表结构
首先,在前面设计的窗体上放置一个报表组件TQucikRep,在对象查看器窗口中把Band属性展开,将其HasColumnHeader、HasDetail、HasPageFooter、HasPageHeader、HasSummary和HasTitle属性设为true。
把QuickRep1的DataSet属性设为Table1,为报表主表指定数据源。
然后,把TQRSubDetail组件放到QuickRep1组件上,作为明细表Band区段。把其Master属性设置为QuickRep1,设置Bands的子属性HasHeader和HasFooter为true,并将其DataSet属性设置为Query1,指定明细表数据来源。
3)设置主/明细报表的主体
在ColumnHeaderBand1区段中添加主表中各字段的标题使用的报表标签组件(TQRLable),它们的Caption分别为客户号、公司、电话、传真和所在城市。在DetailBand1区段中添加显示主表字段值的报表组件(TQRDBText),与前面标题对应设置其字段,要注意的是它们的DataSet都为Table1。
在明细报表部分,我们首先在GroupHeaderBand1中放置明细表的表头标签(TQRLable),它们的Caption依次设置为定单号、条款、付款方式、款项总额和未付款额。在QRSubDetail1区段中放置显示明细报表的组件对象(TQRDBText),其DataSet属性全设为Query1,字段名称依次是OrderNo、Terms、PaymentMethod和ItemsTotal。接着添加一个TQRExpr组件,设置其Master属性为QRSubDetail,打开表达编辑器,输入下面的表达式:
INT(Query1.ItemsTotal-Query1.AmounPaid)
在GroupFooterBand1区段中添加统计报表组件QRExpr2,并将其属性设置为QRSubDetail1,打开表达式编辑器,输入如下表达式:
SUM(INT(Query1.ItemsTotal-Query1.AmounPaid))
即计算未付款项的总额,并设置ResetAfterPrinter属性为true,这样,就可以统计出明细表的总额了。
在SummaryBand1区段中添加一个QRExpr3,打开表达式编辑器,在其中输入如下语句:
SUM(INT(Query1.ItemsTotal-Query1.AmounPaid))
这样这个报表的主体我们就做完了,其它设置看一下上面的图你就应该明白了。
下面我们再添加例一中的那样的程序代码,至此这样的一个复杂报表我们就完成了。
本例是用TTable与TQuery做的主/明细数据关联,同样我们可以用TTable与TTable做主/明细数据关联,实现本例这样的报表。
深入QuickReport(三)
作者:董维春
(本文已在《CSDN开发高手》04年第一、二、三期上发表,应广大网友的要求,经编辑同意,发表在CSDN作者本人文档中,略有修改,但仅即于此,未经CSDN或作者本人同意任何个人与网站不得转载、摘抄,否则任何涉及到版权的行为后果自负)
第三部分:报表中的其他问题
通过前两部分的学习,我想你对QuickRep已经有了一定的掌握,在这部分我们对报表设计中的其他一些问题做一下简单介绍,也许这些你并不常用,但同样这些内容对于我们学习QuickRep还是有很大益处的。
此部分内容都以例程的形式讲解,为了保持文章的完整性,例子的编号接上部分。
例五、报表的连接及保存
通过上面的例子,你也许会动手做了几个报表,有时你一定会想把其中的一些报表连接起来,组成一个综合报表,并作为整体来操作。在BCB中实现这一点并不难,我们这时要用到TQRCompositeReport组件。它提供了一个OnAddReprots事件,在创建报表时将触发这个事件,因此我们只要在这个事件中用Add方法将需要连接在一起的报表添加到该组件的事件中就可以了。下面给出一个示例程序段,这是把两个报表添加到综合报表中的,代码如下:
void __fastcall TForm1::QRCompositeReportAddReports(TObject *Sender)
{
((TQRCompositeReport*)Sender)->Reports->Add(Form2->QuickRep1);//添加第一个报表
((TQRCompositeReport*)Sender)->Reports->Add(Form3->QuickRep1);//添加第二个报表
}
做好的报表我们一定都想保存起来,保存的文件格式有:文本格式文件(TXT),组件TQRTextFilter;超文本格式文件(HTML/HTM),组件TQRHTMLFilder;逗号分隔文件(CSV),组件TQRCSVFilter;以及报表文件。保存前三种格式文件需要调用ExportToFilter方法,而直接保存报表组件,则只需用Save。这个例子中我们放了一个TSaveDialog对话框和QuickReport组件页中的TQRTextFilter、TQRHTMLFilder、TQRCSVFilter三个组件。完整的代码如下:
void __fastcall TForm1:: SaveReportClick(TObject *Sender)
{
AnsiString FileExt;
//  打开保存文件对话框获得文件名
if(SaveDialog1->Execute())
{
//  获得文件后缀
FileExt = AnsiUpperCase(ExtractFileExt(SaveDialog1->FileName));
//  输出Html超文本文件
if((FileExt == ".HTML") || (FileExt == ".HTM"))
QuickRep1->ExportToFilter(new TQRHTMLDocumentFilter(SaveDialog1->FileName));
//  输出txt文本文件
else if(FileExt == ".TXT")
QuickRep1->ExportToFilter(new TQRAsciiExportFilter(SaveDialog1->FileName));
//  输出CSV文件
else if(FileExt == ".CSV")
QuickRep1->ExportToFilter(new TQRCommaSeparatedFilter(SaveDialog1->FileName));
//  输出报表文件
else
{
QuickRep1->QRPrinter->Save(SaveDialog1->FileName);
}
}
}
//-------------------------------------------------------------------
例六、自定义报表预览窗口
QuickReport的报表预览功能总是不能达到令人满意的效果,因此,我们有必要自定义快速报表的预览窗口,以达到完美的设计要求。
1)设置预览窗口
新建工程,在Form1窗体上添加一个ToolBar控件,并在其上添加以下按钮:“调入报表”、“打印”、“打印设置”、 “上一页” 、“下一页”、“放大”、“缩小”和“关闭”。在Form1窗体上添加一个StatusBar,双击该组件,在编辑器中插入三项,在第三个项中显示页面信息。在Form1窗体上添加一个TQRPreview控件,对齐方式设为alClient,Form1窗体的外观如图1所示:
再新建一个窗体,设其Name为Form2, 在该窗体上添加TQuickRep控件,设其Name为QuickRep1,其PrinterSetting中的Units属性设为mm(以毫米为计量单位),然后建立报表。
2)编程实现
(1)在Form2上选择QuickRep1,在其事件中选择OnPreview,输入以下代码:
#include
#pragma hdrstop
#include "Unit2.h"
#include "Unit1.h"//调用Form1的内容
//-------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm2 *Form2;
//-------------------------------------------------------------------
__fastcall TForm2::TForm2(TComponent* Owner)
: TForm(Owner)
{
}
//-------------------------------------------------------------------
void __fastcall TForm2::QuickRepPreview(TObject *Sender)
{
Form1->QRPreview1->QRPrinter=Form2->QuickRep1->QRPrinter;//最为关键的一步
}
//-------------------------------------------------------------------
(2)为Form1中的各功能按钮的OnClick事件添加如下代码:
#include
#pragma hdrstop
#include "Unit1.h"
#include "Unit2.h"//调用Form2的内容
//-------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//-------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//-------------------------------------------------------------------
//调入报表
void __fastcall TForm1::QRLoadClick(TObject *Sender)
{
Form2->QuickRep1->Prepare(); //不加入该句,下一句的共几页将不能正确出现
StatusBar1->Panels->Items[2]->Text="第"+IntToStr(Form1->QRPreview1->PageNumber)+"页"+"共" + IntToStr(Form2->QuickRep1->PageNumber)+"页";
Form2->QuickRep1->Preview();//显示页面的信息
}
//-------------------------------------------------------------------
//打印报表
void __fastcall TForm1::PrintClilck(TObject *Sender)
{
Form2->QuickRep1->Print();
}
//-------------------------------------------------------------------
//打印设置
void __fastcall TForm1::PrintSetupClick(TObject *Sender)
{
Form2->QuickRep1->PrinterSetup();
Form2->QuickRep1->Print();//不加这句可只能设置,不能打印,在我的电脑上测试是这样的
}
//-------------------------------------------------------------------
//上一页
void __fastcall TForm1::PageUpClick(TObject *Sender)
{
if(QRPreview1->PageNumber>1)
QRPreview1->PageNumber--;
StatusBar1->Panels->Items[2]->Text="第"+IntToStr(Form1->QRPreview1->PageNumber)+"页"+"共" +IntToStr(Form2->QuickRep1->PageNumber)+"页";
}
//-------------------------------------------------------------------
//下一页
void __fastcall TForm1::PageDownClick(TObject *Sender)
{
if(QRPreview1->PageNumber < Form2->QuickRep1->PageNumber)
QRPreview1->PageNumber++;
StatusBar1->Panels->Items[2]->Text="第"+IntToStr(Form1->QRPreview1->PageNumber)+"页"+"共" +IntToStr(Form2->QuickRep1->PageNumber)+"页";
}
//-------------------------------------------------------------------
//报表放大
void __fastcall TForm1::ZoomInClick(TObject *Sender)
{
if(QRPreview1->Zoom<200)
QRPreview1->Zoom+=5;
}
//-------------------------------------------------------------------
//报表缩小
void __fastcall TForm1::ZoomOutClick(TObject *Sender)
{
if(QRPreview1->Zoom>5)
QRPreview1->Zoom-=5;
}
//-------------------------------------------------------------------
//程序关闭
void __fastcall TForm1::CloseClick(TObject *Sender)
{
Close();
}
//-------------------------------------------------------
上面只是做了一个简单的设计,你完全可以把它做得功能更强大、外观更漂亮一些。
例七、QR组件的动态设置及探讨
BCB中提供了大量的VCL组件,有时难免要在程序中动态创建组件,VCL是用Object Pascal写的,所以VCL类的对象我们只能在堆中创建。
如创建一个TQRLable对象,我们可以这样来创建:
TQRLable *MyQRLable= new TQRLable(From1);
即写成如下程式:
类名  *对象名=new 类名(…);
注意:()里面可以是你已创建的该类对象的父类名字、工程的名字、NULL或this。但最好是对象的父类名。
例:动态生成TQRLable组件
我们先在窗体(Form1)上,放上一个TQuickRep,并在其上放一个Band。在Form1窗体中按钮Button1的单击事件中写上如下代码:
void __fastcall TForm1::Button1Click(TObject *Sender)
{
TQRLable *MyQRLable= new TQRLable(From1);
MyQRLable ->Parent=Band1;//最为关键的一句,否则你将看不到什么,但编译却是正确的
MyQRLable ->Top=10;
MyQRLable ->Left=38;
MyQRLable ->Height=25;
MyQRLable ->Width=100;
MyQRLable ->Caption="I'm MyQRLable!";
}
通过这个例子我们应该清楚的看出动态创建QR组件的几个重要步骤:
1)要一个空间(内存);// TQRLable *MyQRLable= new TQRLable(From1);
2)指定其父组件,说直接了就是为我们要创建的这个对象指定一个容器;//     MyQRLable ->Parent=Band1;
3)指定组件要出现在父组件的哪个位置;// MyQRLable ->Top=10; MyQRLable ->Left=38; MyQRLable ->Height=25; MyQRLable ->Width=100;
4)其它重要属性。//   MyQRLable ->Caption="I'm MyQRLable!";
注意上面的步骤不能任意安排,否则你的程序会出笑话的。
在动态生成非宝兰VCL原有的组件时要加上对应的头文件。我们要动态生成报表组件时一定要加入:
#include  “Qrctrls.hpp”//若还有问题,你还要加入:
#include “QuickRpt.hpp”
另外由于BCB对内存管理或与系统、硬件的冲突,你的动态创建程序也许一点错误都没有,但就是编译不了;有时也许第一次通过了,第二次一样的程序却通过不了,出现这样那样的提示,最简单的办法就是注销一下系统,再试一下,多数就能解决了。
既然这样的动态产生组件会出现很多不可遇见的问题,那我们还有没有更好的办法来实现类似的功能呢?有,答案是一定的。
我们可以在TQuickRep中把所有组件都放置好,各区段组件,可视化组件都放在应该放的位置上,只是一些特定的属性我们先不给出,而通过程序给出,这样就可以仿制动态创建组件的方法来动态产生报表。说简单了就是事先把组件都准备好了,用的时候拿出来,不用的,不给定关键属性,即用属性废掉它,让它根本就不起作用。
例:动态设置QRDBText的属性值
void __fastcall TForm1::Button1Click(TObject *Sender)
{
QRDBText1->DataSet=Table1;
QRDBText1->DataField="AREA";//改成双引号后,一切OKJ
QuickRep1->Preview();
Table1->Close();
}
//---------------------------------------------------------------------------

void __fastcall TForm1::Button1Click(TObject *Sender)
{
QRDBText1->DataSet=Table1;
QRDBText1->DataField=Table1->Fields->Fields[3]->FieldName;
QuickRep1->Preview();
}
//---------------------------------------------
注意:下面的程序是错误的
void __fastcall TForm1::Button1Click(TObject *Sender)
{
QRDBText1->DataSet=Table1;
QRDBText1->DataField=Table1->FieldValues["AREA"];//注意这将得不到你想得到的内容,因为他不是把字段名给了DataField
QuickRep1->Preview();
}
单就一个QuickReport动态生成报表,我想不是三言两语就能说完的,单独成文也不为过,好在我也把动态生成报表的一些常见问题都说出来了,解决之道上面也提了,并且还给出了几个例子,通过这些介绍,你要是有精力的话一定会做出一个不出问题的动态生成报表系统。
以上程序均在XP系统、BCB6、EPSON C43UX下编译通过。
结束语:写到这里我相信多数的读者对QuickReport应该有了一个比较全面、深入的了解了。但当我仔细读了一遍这篇文章后,发现还有很多内容没有写出来,让我意识到想在一篇文章里把QuickReport全都写出来,还真有些困难,这样也好,留点以后再写,让不闲下来做什么呢J
其实说简单了,QuickReport并不难学,只是有时我们把她孤立起来看,而没有想到,其实QuickReport就是那么回事,她在BCB上出现也不是一天两天了,现在的她可以说早已完全融入到VCL组件中去了。所以说,想真正学通QuickReport,关键还是取决于我们对VCL的掌握程度。
致谢:这里首先要感谢CSDN的韩磊,是他给了我写这篇文章的动力,让我感觉到的确有些朋友需要这样的文章,并感谢他对本文提出的宝贵意见。当然还有CSDN的熊建国、欧阳璟,尤其是欧阳老弟,他对文章做了细致如微的修改,本文的最终成形离不开他的辛勤工作与中肯建议。最后还要感谢我的女朋友王岩,她帮我完成了全文的文字校对。