您的位置:首页 > 编程语言 > C语言/C++

用VC++实现通用的报表控件

2008-09-02 14:09 330 查看
摘 要: 常用开发工具的报表设计工具操作繁琐,专业性强,难满足用户自己随时定制
报表格式的要求。本文基于Word模板,用VC建立一个通用的ActiveX报表控件,用以补充开
发工具中报表处理功能的不足。

关键词:报表控件,OLE自动化,定制报表,ActiveX控件

1 引言
信息管理系统的常用开发工具(如VFP、DELPHI、POWERBULID等)的报表设计工具操作
繁琐,专业性强,当用户对报表的需求有所变化时,需重新修该应用程序。特别对一些突
发性的临时报表的需求,更是无能为力。

为了制作复杂报表及赋予用户定制报表的能力,就需要对常用的开发工具扩充以提供一
种灵活的报表设计工具,当前ActiveX控件可用于大多数开发环境,是扩充开发工具报表
开发能力的首选。本文介绍一种基于WORD模板的报表ActiveX控件,该控件基于报表模板生
成WORD文档形式的报表,以供用户或应用程序打印或预览。用户可通过修改报表模板以改
变报表格式。

本文介绍本控件所实现三类报表中第一类报表(即数据源中每行数据生成一个报表)
的实现。报表的数据源采用EXCEL,当然为了扩充,我把对数据源的访问封装在两个函数
上即取属性名函数及取数据源特定行的函数内,这样能通过ADO方便的转换到其他数据源。

2 报表模板的设计
2.1 根据要求在WORD中设计好报表的格式 。

2.2 在设计好报表中,添加数据项的描述。

数据项的描述定义采用WORD标签,即在报表中需要输出数据源中的数据的位置插入标签。
标签名即数据源的属性名(对EXCEL第一行上各列单元格的值即为属性名)。

3 控件的实现
  在VC++ 6.0下创建Activex控件
3.1建立工程
MFC Activex ControlWizard新建一个名为report的工程,在向导过程中选取
invisible at runtime 的特征。打开ClassWizard ---Add Class---From a type library
选择本机的Word9.olb(本机装Word2000,Word98中为Word8.olb 具体根据本机word的
版本)。选择_Application (类名改为_Applicationword),_Document,Documents ,
Bookmark,Bookmarks,Cell,Cells,Column,Columns ,Range(类名改成Rangeword),
Row,Rows,Selection,Table,Tables,Window,Characters,Paragraph,Paragraph,
View。加入新类。用同样方法选择本机的EXCEL9.OLB,选择_Application,_Workbook,
Workbooks ,_Worksheet,Sheets,Range加入新类。
在ReportCtl.h中加入
#include "excel9.h"
#include<comdef.h>
#include "msword9.h"
3.2增加ActiveX控件属性:
1.文档模板文件名:ReportTemplateFileName类型Cstring 内部名 m_reportTemplateFileName
2.数据源名: DateSourceName 类型 BSTR 内部名 m_DateSourceName;
3. 添加报表种类属性
(1)定义枚举类型:在CreportCtrl类的定义中加入
enum ReportType {OneRecordOneReport=1,OneGroupRecordOneReport, OneTableOneReport};
//每条记录一张报表,每组记录一张报表,所有记录一张报表
(2)添加报表种类属性 ReportType 类型 short 内部名 m_ReportType
4.添加报表文件名属性ReportFileName 类型 Cstring 内部名称 m_reportFileName
5.添加报表特征属性
(1)定义枚举类型enum ReportCharacter
{EveryReportPageAlone=0x0001,VerticalAjacentCellUnite=0x0002,Group=0x0004,
EveryPageHasHeadTail=0x0008 ,EveryPageHasTailNoHead=0x0010 };
//对第一类报表仅第一个用到,即一页能不能包含多张报表
(2)定义报表特征属性 ReportCharacter 类型 short 内部名称 m_ReportCharact
6.添加文件路径属性 FilePath 类型 Cstring 内部名m_filePath
7.添加数据源主属性属性 DataSourceKey 类 Cstring 内部名m_DateSourceName
多个主属性以,分开。
3.3 添加报表控件的报表制作方法及相关的私有函数及数据
3.3.1报表制作方法MakeReport
short CReportCtrl::MakeReport()
{
/*检查 数据源文件、报表文件、报表模板文件文件名的合法性及数据源文件、报表
模板文件及文件路径是否存在------从略*/
int pronum=GetDataSourceProperty(DataSourceProperty); /*启动excel服务,
读数据源的属性名到数组DataSourceProperty 返回 –1表示 启动EXCEL服务失败*/
if(pronum==-1){m_ErrorinformationCode=7; EndExcel();return 7;}
/* m_ErrorinformationCode是类的私有成员,保存制表后的错误代码 错误信息
在类的一个常量字符串数组中-----从略*/
if(!DataSourceKeyIsExist()){m_ErrorinformationCode=6; return 6;}
//检查关键字属性存在否
switch(m_ReportType)//报表类型
{ case OneRecordOneReport:if(OneRecordOneReportMakeReport()==-1)
{m_ErrorinformationCode=11; return 11; };
break; }
//对DataSourceProperty 等字符串数组删除数组内的所有串
/对pDataSourcePropertyKeySN等指针变量释放空间-----从略
EndExcel();//结束Excel服务
return 0;
}
3.3.2添加私有数据
_Application excel;Workbooks books;_Workbook book;Sheets sheets;
_Worksheet worksheet;
CstringArray DataSourceProperty ,DataSourcePropertyKey ;
/*存放数据源的属性名,数据源的关键字 */
int * pDataSourcePropertyKeySN;//关键字属性对应的excel表的列数从一开始
3.3.3 添加的CreportCtr类的部分私有函数
1.从串中分解出子串(以,为分界)同时去除空格,返回子串数
int ExtractSubtring(Cstring &str,CstringArray &strarr)//实现从略
2.取excel指定行 返回false表示全空 n 表示第几行 SpaceEndOrAppointCol=0
表示该行从左到右查找,如碰到属性为空则表示该行结束 否则SpaceEndOrAppointCol
表示该行的列数*/
bool CReportCtrl::GetExcelRowToArray(int n, CStringArray &prostrarr,
int SpaceEndOrAppointCol)
{if(SpaceEndOrAppointCol<0) return false;
Range range,cell;range=worksheet.GetRows ();
range=range.GetItem (_variant_t((long)n),vtMissing).pdispVal ;
// //取当前行
range=range.GetCells ();//取当前行所有的单元格
int i=1;
while(1)
{ cell=range.GetItem (_variant_t((long)i),vtMissing).pdispVal ;
//取出该行的第i列的单元格
_variant_t f= cell.GetValue ();
f.ChangeType( VT_BSTR ,NULL); //把取出数据如short转换成BSTR类型
BSTR bstr=f.bstrVal ;CString text(bstr);//把BSTR转换成CString
ReMoveChar (text,' ');//删除text串中的空格
if(SpaceEndOrAppointCol==0)
{if(!text.GetLength ()) break;} else if(i>SpaceEndOrAppointCol) break;
prostrarr.Add (text); i++;}
if(range.m_lpDispatch !=NULL)range.ReleaseDispatch ();
int j; i=prostrarr.GetSize ();if(i==0) return false;
for(j=0;j<i;j++)
if(!prostrarr[j].IsEmpty ()) return true;
return false;
}
3.取数据源的属性,返回属性个数 如返回-1则是启动excel失败或其他excel问题
int GetDataSourceProperty(CstringArray &prostrarr)
int CReportCtrl::GetDataSourceProperty(CStringArray &prostrarr)
{ if(excel.m_lpDispatch ==NULL)
{ if(!excel.CreateDispatch ("Excel.Application",NULL)) return -1;
books=excel.GetWorkbooks (); if(books.m_lpDispatch ==NULL) return -1;
books.Open (m_filePath?m_filePath+'//'+m_DateSourceName:m_DateSourceName,vtMissing,
vtMissing,vtMissing,vtMissing,vtMissing,vtMissing,vtMissing,
vtMissing,vtMissing,vtMissing,vtMissing,vtMissing);
book=books.GetItem (_variant_t((long)1)); if(book.m_lpDispatch ==NULL) return-1;
sheets=book.GetSheets ();if(sheets.m_lpDispatch ==NULL) return-1;
worksheet=sheets.GetItem (_variant_t((long)1));
if(worksheet.m_lpDispatch ==NULL) return-1;}
GetExcelRowToArray(1,prostrarr);
return prostrarr.GetSize ();}

4.判断一个串集合是否属于另一个串集合 如one为空返回false 用p指向的数组返回one串中的每个元素在another串的位置
bool CReportCtrl::IsOneStrBelongAnotherStr(CStringArray &one, CStringArray &another,int *p )//实现从略

5.判断数据源关键属性是否存在
bool CReportCtrl::DataSourceKeyIsExist()
{ bool result;
if(!m_dataSourceKey.GetLength ()) return false;
int grouppropertynum=ExtractSubtring(m_dataSourceKey,DataSourcePropertyKey);
pDataSourcePropertyKeySN=new int[ DataSourcePropertyKey.GetSize ()];
result=IsOneStrBelongAnotherStr(DataSourcePropertyKey,
DataSourceProperty,
pDataSourcePropertyKeySN);
if(result==false) { delete pDataSourcePropertyKeySN;
pDataSourcePropertyKeySN=NULL;}
return result;
}
6.在字符数组中查找字符串
int CReportCtrl:: FindStringInStringArray (CStringArray &x, CString &str)//实现从略
7. 判断数组在制定位置是否为空,位置由共n个,由p指针所指
bool IsStringArrSpeciPositionNoEmpty( CStringArray &array , int *p,int n)//实现从略
8.结束word服务
void CReportCtrl::EndWord()
{ word.Quit(&vtMissing,&vtMissing,&vtMissing) ;
word.ReleaseDispatch ();
//对CreportCtrl类中所有word类的对象调用ReleaseDispatch () --从略
}
9.结束excel服务 类同结束word服务
void CReportCtrl::EndExcel()
10.制第一类表 返回-1表示制表错
short CReportCtrl::OneRecordOneReportMakeReport()
{
Rangeword range; int exceldatarownum=0; CStringArray excelrow;
long count,i,j,colnum;//count 标签数量 colnum excel当前行
long priorposition_end=0;//粘贴前的文件结尾
_variant_t end; long pagenum,priorpagenum; //粘贴前后的页数
short *point;//标签所在的列
int validbookname=0;
if(word.m_lpDispatch !=NULL) return -1;
//*******************启动word服务
if(!word.CreateDispatch ("Word.Application",NULL)) return -1;
if(worddoc_ReportFile.m_lpDispatch ==NULL)
{
word.SetVisible (false); word.SetWindowState (1);
worddocs=word.GetDocuments ();
worddoc_ReportFile=worddocs.Add(&vtMissing,&vtMissing,&vtMissing,&vtMissing);
_variant_t file=m_filePath+'//'+ m_reportTemplateFileName;
worddoc_TemplateFile=worddocs.Open (&file,&vtMissing,&vtMissing,
&vtMissing,&vtMissing, &vtMissing,&vtMissing,
&vtMissing,&vtMissing,&vtMissing,&vtMissing,&vtMissing);
//*************启动word服务
//*************取出模板文件中的标签并确定该标签对应数据源的第几列
bookmarks=worddoc_TemplateFile.GetBookmarks ();
count=bookmarks.GetCount ();
point =new short[count+1];
for(i=1;i<=count;i++)//确定标签对应excel表的列数
{
bookmark=bookmarks.Item (&_variant_t((long)i));
CString str=bookmark.GetName ();//word标签是不含空格的
point[i]= FindStringInStringArray (DataSourceProperty,str);
if(point[i]>0) validbookname++; }
//***********取出数据源中的每一行,生成一个报表
colnum=2;//第二列开始放数据,第一列为属性名
while(GetExcelRowToArray(colnum,excelrow,DataSourceProperty.GetSize ())&&
IsStringArrSpeciPositionNoEmpty(excelrow,
pDataSourcePropertyKeySN, DataSourcePropertyKey.GetSize ())
)//取出第colnum列,如关键属性非空即如该行有效
{ exceldatarownum++; i=1;
while(i<=count)//count为标签的个数
{ bookmark=bookmarks.Item (&_variant_t((long)i));
range=bookmark.GetRange ();
int k=point[i];
if(k>0) //如该标签有效即有对应的数据源列
range.SetText (excelrow[k]);//用colnum行的对应列的数据插入到标签位置
i++;
}
//************把生成的第colnum-1张报表拷贝到报表文件的文件尾
range= worddoc_TemplateFile.GetContent ();
range.Copy ();
/*如报表特性不为一张报表单独成页,则判断把报表拷贝到报表文件的文件尾后,是否会造成该报表在报表文件中跨页,跨页则该页从另一页开始*/
range=worddoc_ReportFile.GetContent(); end=(long) (range.GetEnd ()-1);
priorposition_end=end; priorpagenum=(range.GetInformation (4)).lVal ;
range=worddoc_ReportFile.Range (&end,&end);range.Paste ();
switch(m_ReportCharacter&0x0001?1:2)
{
case 1://每张报表单独成页
range=worddoc_ReportFile.GetContent();
end=(long) (range.GetEnd ()-1);
range=worddoc_ReportFile.Range (&end,&end);
range.InsertBreak (&_variant_t((long)7));//插入分页符
break;
case 2:// 每张报表不单独成页,但如跨页则当前报表从另一页开始
range=worddoc_ReportFile.GetContent();
end=(long) (range.GetEnd ()-1);
pagenum=range.GetInformation (4).lVal;
if(pagenum!=priorpagenum)
{range=worddoc_ReportFile.Range (&_variant_t((long)(priorposition_end)),&_variant_t((long) (priorposition_end)));
range.InsertBreak (&_variant_t((long)7)); }//如跨页则分页
break;
}//switch
colnum++; excelrow.RemoveAll ();
//取消对worddoc2的修改
worddoc_TemplateFile.Undo (&_variant_t((long)validbookname)); }//while
//如模板文件刚好满页则结果多一个空页(一个仅包含回车的段),所以计算页数,多的减一
switch(m_ReportCharacter&0x0001?1:2)
{ case 2:
range= worddoc_ReportFile.GetContent ();
j=range.GetInformation (4).lVal;
if(j>exceldatarownum) //每张报表不单独成页 则最多一页每条记录
{
characters=worddoc_ReportFile.GetCharacters ();
range=characters.Item (1);
range.Delete (&_variant_t(long(1)),&_variant_t(long(1)));
}
case 1:
range= worddoc_TemplateFile.GetContent ();
i=range.GetInformation (4).lVal; //取得页数
range= worddoc_ReportFile.GetContent ();
j=range.GetInformation (4).lVal;
if(j>=i* exceldatarownum+1)
{
paragraphs=worddoc_ReportFile.GetParagraphs ();
count=paragraphs.GetCount ();
if(count>0)paragraph=paragraphs.Item (count);
range=paragraph.GetRange ();
range.Delete (&_variant_t(long(1)),&_variant_t(long(1)));
}
break;
}
window=word.GetActiveWindow ();view=window.GetView ();
view.SetShowBookmarks (FALSE); _variant_t filename= m_filePath+'//'+m_reportFileName;
worddoc_ReportFile.SaveAs(&filename,&vtMissing,&vtMissing,&vtMissing,&vtMissing,
&vtMissing,&vtMissing,&vtMissing,&vtMissing,&vtMissing,&vtMissing);
worddoc_TemplateFile.Close (&_variant_t((long)0),&vtMissing,&vtMissing);
worddoc_ReportFile.Close (&_variant_t((long)0),&vtMissing,&vtMissing);
EndWord(); }
return 0;
}
 

4 结束语

本控件是在遇到用户要求一些突发的临时报表的时,为解决此类问题开发的。用户所要求的临时报表是对上下级部门之间交换的EXCEL数据,根据不同的格式生成报表。由于篇幅的关系,类中很多私有函数就没有给出。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: