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

用VC++实现通用的报表控件相关推荐

  1. 只用最适合的! 全面对比主流 .NET 报表控件:水晶报表、FastReport、ActiveReports 和 Stimulsoft...

    原文:只用最适合的! 全面对比主流 .NET 报表控件:水晶报表.FastReport.ActiveReports 和 Stimulsoft 前言 随着 .NET 平台的出现,报表相关的开发控件随之出 ...

  2. ActiveReports 报表控件V12新特性 -- 新增JSON和CSV导出

    ActiveReports 报表控件V12新特性 -- 新增JSON和CSV导出 ActiveReports 是一款专注于 .NET 平台的报表控件,全面满足 HTML5 / WinForms / A ...

  3. 转发:只用最适合的! 全面对比主流 .NET 报表控件:水晶报表、FastReport、ActiveReports 和 Stimulsoft

    前言 随着 .NET 平台的出现,报表相关的开发控件随之出现,目前已经有若干成熟的产品可供开发人员使用,本文旨在通过从不同维度对比目前最流行的4款 .NET报表控件,给所有报表开发人员在做产品选型时一 ...

  4. 只用最适合的 | 主流 .NET 报表控件全面对比

    随着 .NET 平台的出现,报表相关的开发控件随着而来,已经有若干成熟的产品可供开发人员使用,本文旨在通过从不同维度对比目前最流行的3款 .NET报表控件:FastReport.Stimulsoft. ...

  5. ATGrid WEB报表打印控件/MIS报表控件

    ATGrid WEB报表打印控件/MIS报表控件 ATGrid报表控件/WEB插件[专业版],对EtCell进行了进行了全面的改革, 将会彻彻底底的解决你的报表问题,让你开发达到从未有过的轻松和喜悦- ...

  6. ATGrid报表控件/WEB插件[专业版]

    ATGrid报表控件/WEB插件[专业版] Delphi / Windows SDK/API http://www.delphi2007.net/DelphiAPI/html/delphi_20061 ...

  7. access字段属性设置下拉列表_可嵌入您系统的.NET 报表控件ActiveReports:带状列表组件...

    葡萄城报表控件ActiveReports V14.0 全面支持 .NET Core平台.同时 ActiveReports的桌面报表设计器UI也全面增强,报表预览方式得以全面优化,报表设计能力得以大幅提 ...

  8. 免费资源 | ActiveReports 报表控件发布多平台 Demo 代码集合

    近期,ActiveReports 产品开发组的小伙伴针对大家比较关注的报表功能.常见问题.经典实现,特意准备了一个Demo代码集合,涉及WinFormss \ ASP.NET \ MVC 多个技术平台 ...

  9. android中的标题栏是什么意思,Android通用标题栏组合控件

    原标题:Android通用标题栏组合控件 快,点击蓝色"字体"关注这个公众号,一起涨姿势 由于项目中经常用到此种组合控件,就封装了下,具体效果看下图,老司机可以绕道哈! 一.主要功 ...

最新文章

  1. 对象特性-----拷贝构造函数的调用
  2. 技术总监到底要不要写代码?
  3. 1920+1080+android三星手机,三星Galaxy Note3能拍摄1080p视频吗?支持1080p播放吗?
  4. 一个小技巧,让您的ABAP OPEN SQL具有自描述效果
  5. php代码的健壮性,代码健壮性的几点思考 - 逍遥客 - 51Testing软件测试网 51Testing软件测试网-软件测试人的精神家园...
  6. 基于JAVA+SpringMVC+Mybatis+MYSQL的民宿客栈系统
  7. 【经验心得】关于RPGmaker的工程解码
  8. QT每日一练day15:QColorDialog颜色对话框
  9. Jenkins执行.bat 提示不是内部或外部命令
  10. 一文读懂电子罗盘的原理、校准和应用
  11. Lauterbach trace32与 jlink
  12. 新基建深度报告:七大领域十大龙头分析
  13. 维度诅咒_维度的诅咒减去行话的诅咒
  14. 超文本传输协议HTTP
  15. 裴蜀定理(详细定义+应用+模板)
  16. idea html设置字体大小,intellij idea设置(字体大小、背景)
  17. 仿掘金社区全栈项目开发(二)-前端工程化
  18. 微信小程序中页面引入js文件
  19. C++实验题8 数组使用(bushi)
  20. 百余署名AI论文被爆抄袭 智源现已致歉

热门文章

  1. Spring学习10之动态代理
  2. java rest 序列化_http请求/restful/序列化反序列化/JSON
  3. comsol积分函数_怎样在COMSOL中实现时间和空间积分
  4. python链表的创建_python数据结构之链表的实例讲解
  5. [Google Guava] 1.2-前置条件
  6. Vue.js 组件 处理边界情况
  7. 说说 JAVA 代理模式
  8. git rebase -i 汇合提交
  9. Redis 键(key) 命令
  10. Java 理论与实践: 垃圾收集简史