如何快速自动生成并定制报表
西南交通大学电气学院(610031) 童晓阳 |
| 在各种管理信息系统应用中,需要产生大量的报表,通常的做法是由编程人员一个个手工制作,工作效率较低;另外,用户希望能够将在应用程序里查询得到的结果生成报表以便打印。为了解决以上两种问题,本文利用动态生成技术实现了快速自动产生报表,允许用户手工对报表进行修饰,并将实现过程封装成一个类。 |
| 使用过C++ Builder或Delphi的编程人员知道,有一个TDBGrid控件,它能以表格的形式显示和操作用户查询的数据记录;而要制作一个可供打印的报表,则需要使用TQuickRep控件,在它上面增加TQRLabel、TQRDBText、TQRShape等控件,设置它们对应的数据集、数据字段等属性,然后编排它们的位置,以表格或其它格式显示出来供预览和打印,这是一个很繁琐的过程。有时,用户希望能将查询出来的显示在TDBGrid控件的数据打印出来,按照以往的做法,就需要由编程人员按照TDBGrid的显示内容手工设计报表。在这里,本文利用动态生成技术,读出TDBGrid的有关显示信息,在TQuickRep控件里动态生成相应的TQRLabel、TQRDBText、TQRShape等控件,设置各字段的标题和数据以及表格分割条。这是完全可行的,因为在C++
Builder里所有的控件都可以由程序动态生成,不仅仅是在设计阶段才产生的。另外,如果用户对产生的报表表格布局不太满意,本文提供了接口使用户可对报表进行手工调整,调整表格的高度、宽度等布局,实现用户对报表的一定程度的定制。 |
| 利用C++的封装性特点,将自动产生并定制报表的实现封装成一个新类TGridPrint,对外提供编程人员关心的公用接口,屏蔽了内部信息和具体实现,体现了面向对象的设计思想,为编程人员带来方便。编程人员还可以在它基础上进一步扩充功能。这样设计的新类减轻了编程人员的工作量,同时为用户提供了定制报表的接口,提高了报表的质量和用户参与的积极性。 |
| 自动产生并定制报表的实现过程包括自动产生和定制两部分。新类的定义和实现分别在GridPrint.h GridPrint.cpp文件里,另包含3个文件RepRst.h、RepRst.cpp、RepRst.dfm,它们是已产生的一个窗口FrmRepRst,在它里面已增加一个TQuickRep控件,它的属性Bands的各子属性的值全为true。 |
| 先定义一个表示表格某一列信息的结构,在报表里一列有固定标题和显示的数据文本两种信息,为了能画出表格,每一列固定标题栏和数据栏右边分别增加一个分隔条。在类TGridPrint的构造函数里,先根据传入的TQuickRep
*pSrcQuickRep(报表指针),TDBGrid * pSrcDBGrid(数据表格指针),TQRBand *SrcTitleBand1(报表中的总标题栏指针),
TQRBand *SrcColumnHeaderBand1(报表中的字段标题栏指针),TQRBand * SrcDetailBand1(报表中的数据栏指针)参数设置类的私有变量。再动态生成并设置总标题文本、字段标题栏矩形框、数据栏矩形框的属性。然后通过一个循环,读出TDBGrid中各字段的标题和数据信息,动态生成报表中各字段的标题标签控件、数据文本控件以及对应的表格分割竖条控件。在类的析构函数里,删除所有由构造函数动态生成的对象。类的打印预览函数实现报表的打印预览功能。其它的函数说明略。 |
| #include <DBGrids.hpp> //包含的相关头文件 |
| typedef struct tagFieldType{ //表示表格某一列信息的结构 |
| AnsiString sTitle; //字段标题名称 |
| TQRLabel *pLabel; //字段标题控件 |
| TQRDBText *pDBText; //显示的数据控件 |
| TQRShape *pShapeTitle, *pShapeData; //字段标题和数据的表格分隔条 |
| TGridPrint(TQuickRep *pSrcQuickRep,TDBGrid * pSrcDBGrid,TQRBand
*SrcTitleBand1, |
| TQRBand *SrcColumnHeaderBand1,TQRBand * SrcDetailBand1);
//构造函数 |
| void DoPreview(); //报表的打印预览 |
| void SetPrntTitle(AnsiString sTitle); //手工设置表格的总标题 |
| void SetColumnsWidth(int *ColumnsWidth); //手工设置表格各列宽度 |
| void SetHeadRectHeight(int iHeight); //手工设置字段标题行的高度 |
| void SetDetailRectHeight(int iHeight); //手工设置数据行的高度 |
| TDBGrid * pDBGrid; //将要显示的DBGrid |
| TDataSet * pDataSet; //DBGrid对应数据集 |
| TQuickRep * pQuickRep; //报表控件 |
| TQRBand * TitleBand1; //报表的总标题栏 |
| TQRBand * ColumnHeaderBand1; //报表的字段标题栏 |
| TQRBand * DetailBand1; //报表的数据栏 |
| TQRLabel * pTitleLabel; //总标题控件 |
| TQRShape * pHeadRect, * pDetailRect; //整个字段标题栏、数据栏的表格矩形控件 |
| int iHeadRectHeight, iDetailRectHeight; //对应表格矩形框的高度,它们宽度相同 |
| int _iTotalWidth; //整个表格的总宽度 |
| int _iIntClearance; //表格内部数据列到左表格的距离 |
| int _iFieldCount; //将要打印的字段数目 |
| NEWFIELDTYPE _arrayFieldType[40]; //支持到40个字段的打印 |
| void AutoAdjustColumnsWidth(); //程序自动调整各列宽度 |
| 类的主要公用方法的实现(GridPrint.cpp) |
| TGridPrint::TGridPrint(TQuickRep * pSrcQuickRep,TDBGrid
* pSrcDBGrid,TQRBand *SrcTitleBand1, TQRBand *SrcColumnHeaderBand1,TQRBand
* SrcDetailBand1) //构造函数 |
| pQuickRep = pSrcQuickRep; //根据传入参数设置私有变量 |
| pDataSet = pDBGrid->DataSource->DataSet; |
| pQuickRep->DataSet = pDataSet; |
| TitleBand1 = SrcTitleBand1; |
| ColumnHeaderBand1 = SrcColumnHeaderBand1; |
| DetailBand1= SrcDetailBand1; |
| PTitleLabel = new TQRLabel(pQuickRep); //生成并设置总标题标签的属性 |
| pTitleLabel->Parent = TitleBand1; |
| pTitleLabel->Caption = "报表标题"; |
| pTitleLabel->Left= (TitleBand1->Width - pTitleLabel->Width)/2; |
| memset(_arrayFieldType,0,sizeof(NEWFIELDTYPE)*40); |
| _iTotalWidth=0; //计算出表格各列单元宽度和整个表格的总宽度 |
| for(i=0; i< pDBGrid->FieldCount;i++) |
| { _arrayFieldType[i].iWidth= pDBGrid->Columns->Items[i]->Width; |
| _iTotalWidth += _arrayFieldType[i].iWidth; } |
| if(_iTotalWidth > TitleBand1->Width) |
| { //如果原DBGird各列宽度和大于总标题栏宽度,就调整各列宽度 |
| _iTotalWidth= TitleBand1->Width; |
| AutoAdjustColumnsWidth(); } |
| PreLeft= ( TitleBand1->Width - _iTotalWidth)/2; //使整个表格居中 |
| pHeadRect= new TQRShape(pSrcQuickRep); //生成并设置字段标题栏的矩形框 |
| pHeadRect->Parent= ColumnHeaderBand1; |
| pHeadRect->Left = PreLeft; pHeadRect->Top = 0; |
| pHeadRect->Width = _iTotalWidth; pHeadRect->Height=
pHeadRect->Parent->Height; |
| pDetailRect= new TQRShape(pSrcQuickRep); //生成设置数据行的矩形框 |
| pDetailRect->Parent= DetailBand1; |
| pDetailRect->Left = PreLeft; pDetailRect->Top
= -1; |
| pDetailRect->Width = _iTotalWidth; pDetailRect->Height=
pDetailRect->Parent->Height+1 ; |
| _iFieldCount= pDBGrid->FieldCount; //设置表格总列数 |
| for(i=0; i< _iFieldCount;i++) //动态生成各字段 |
| _arrayFieldType[i].pLabel= new TQRLabel(pQuickRep);
//标题控件 |
| _arrayFieldType[i].pLabel->Parent= ColumnHeaderBand1; |
| _arrayFieldType[i].pLabel->Caption= pDBGrid->Columns->Items[i]->Title->Caption;
|
| _arrayFieldType[i].pLabel->Font= pDBGrid->Columns->Items[i]->Title->Font; |
| _arrayFieldType[i].pLabel->Alignment= |
| pDBGrid->Columns->Items[i]->Title->Alignment;
//对齐方式 |
| _arrayFieldType[i].pLabel->Left = PreLeft+_iIntClearance; |
| _arrayFieldType[i].pLabel->Width = _arrayFieldType[i].iWidth-2*_iIntClearance; |
| _arrayFieldType[i].pLabel->Height= _arrayFieldType[i].pLabel->Font->Height; |
| _arrayFieldType[i].pLabel->Top= |
| pHeadRect->Top+(pHeadRect->Height+_arrayFieldType[i].pLabel->Height)/2; |
| _arrayFieldType[i].pShapeTitle= new TQRShape(pQuickRep);
//该字段右边的分隔竖条 |
| _arrayFieldType[i].pShapeTitle->Parent= ColumnHeaderBand1; |
| _arrayFieldType[i].pShapeTitle->Left = PreLeft +
_arrayFieldType[i].iWidth; |
| _arrayFieldType[i].pShapeTitle->Top = 0; |
| _arrayFieldType[i].pShapeTitle->Width= 1; |
| if( i == pDBGrid->FieldCount-1) //最后一列的分隔竖条宽度为0 |
| _arrayFieldType[i].pShapeTitle->Width= 0; |
| _arrayFieldType[i].pShapeTitle->Height=_arrayFieldType[i].pShapeTitle->Parent->Height; |
| _arrayFieldType[i].pDBText= new TQRDBText(pQuickRep);
//该字段对应的文本控件 |
| _arrayFieldType[i].pDBText->Parent = DetailBand1; |
| _arrayFieldType[i].pDBText->DataSet = pDataSet; //数据集 |
| _arrayFieldType[i].pDBText->DataField = pDBGrid->Columns->Items[i]->FieldName;
//字段名 |
| _arrayFieldType[i].pDBText->Font = pDBGrid->Columns->Items[i]->Font;
//字体 |
| _arrayFieldType[i].pDBText->Alignment= pDBGrid->Columns->Items[i]->Alignment;
//对齐方式 |
| _arrayFieldType[i].pDBText->Left = PreLeft+_iIntClearance; |
| _arrayFieldType[i].pDBText->Width = _arrayFieldType[i].iWidth-2*_iIntClearance; |
| _arrayFieldType[i].pDBText->Height= _arrayFieldType[i].pDBText->Font->Height; |
| _arrayFieldType[i].pDBText->Top= |
| pDetailRect->Top+(pDetailRect->Height+_arrayFieldType[i].pDBText->Height)/2; |
| _arrayFieldType[i].pShapeData= new TQRShape(pQuickRep);
//该数据右边的分隔竖条 |
| _arrayFieldType[i].pShapeData->Parent= DetailBand1; |
| _arrayFieldType[i].pShapeData->Left = PreLeft+_arrayFieldType[i].iWidth; |
| _arrayFieldType[i].pShapeData->Top = 0; |
| _arrayFieldType[i].pShapeData->Width= 1; |
| if( i== pDBGrid->FieldCount-1) //最后一列的分隔竖条宽度为0 |
| _arrayFieldType[i].pShapeData->Width= 0; |
| _arrayFieldType[i].pShapeData->Height=_arrayFieldType[i].pShapeData->Parent->Height; |
| PreLeft= _arrayFieldType[i].pShapeTitle->Left; //下列字段的左位置 |
| TGridPrint::~TGridPrint() //析构函数 |
| { delete pTitleLabel; delete pHeadRect; delete pDetailRect; |
| for(int i=0; i< pDBGrid->FieldCount;i++) |
| { delete _arrayFieldType[i].pDBText; delete _arrayFieldType[i].pShapeData; |
| delete _arrayFieldType[i].pLabel; delete _arrayFieldType[i].pShapeTitle; |
| void TGridPrint::DoPreview() //打印预览函数 |
| { pQuickRep->Preview(); } |
| 类的公共接口提供有4个函数SetPrntTitle、SetColumnsWidth、SetHeadRectHeight、SetDetailRectHeight,分别是手工设置表格的总标题、表格各列宽度、字段标题行的高度和数据行的高度。编程人员可自己设计相关界面,供用户来对报表的布局做进一步的修正,满足用户的要求,提高了报表的质量和用户参与的积极性。实际上,这样做将编程人员在报表设计阶段的工作转交给用户来做,避免了原来设计好不能改变的问题。 |
| 完成类TGridPrint的设计后,编程人员可以在需要的地方很方便地调用它。调用前注意包含两个头文件。调用时,需将下面构造函数里的DBGrid1换成程序界面上实际使用的TDBGrid控件名字,其它不用改动。 |
| TFrmRepRst *pFrmRepRst= new TFrmRepRst(NULL); |
| TGridPrint *pGridPrint= new TGridPrint(pFrmRepRst->QuickRep1,
DBGrid1, pFrmRepRst->TitleBand1, pFrmRepRst->ColumnHeaderBand1,
pFrmRepRst->DetailBand1); |
| delete pGridPrint; delete pFrmRepRst; |
| 在前人设计的基础上,根据任务的需要,采用面向对象的设计方法,可设计出实现目标的新类,并能被继承和扩充。本文只针对数据表格TDBGrid控件设计的,起抛砖引玉的作用,读者可在上述思路的基础上,针对其它格式的报表,进行自动生成报表的设计。 |
|