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

摘 要: 常用开发工具的报表设计工具操作繁琐,专业性强,难满足用户自己随时定制
报表格式的要求。本文基于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数据,根据不同的格式生成报表。由于篇幅的关系,类中很多私有函数就没有给出。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值