MFC混合C++/CLI实现动态报表

  写在前面:本文算是本次知识学习的一个总结,同时也为了跟我一样需要在MFC中调用.net控件的朋友们少走弯路,因为我在实现的时候,花了很多时间去搜集资料,然后又花了很多时间去摸索.所以,希望这篇文章能帮助朋友们少走一点点弯路.

  最近有一个基于MFC多文档的项目,在其中一个文档模板的视图中,需要根据相应算法生成的数据实时显示一个报表.

  在网上搜索了很多资料,也找了很多现成控件,效果都很不满意.后来,在百度中搜索到微软的ReportViewer,只看了一眼,直觉就告诉我,我要找的东西非它莫属了!一阵觅得良驹的狂喜之后,开始搜索ReportViewer的相关资料.所有资料整理汇总后,冷水随之泼来----MFC不支持ReportViewer控件!所有的资料基本都是C#的资料.不甘心就此失败,于是思索着如何在MFC中调用CLR里的控件.功夫不负有心人,最终我在MSDN中搜索<在 MFC 中使用 Windows 窗体用户控件>一文,原文地址:http://technet.microsoft.com/zh-cn/ahdd1h97(es-es,VS.80).aspx,只可惜这篇文章CLR里的东西是用C#做的,MFC的解决方案里加一个C#的工程感觉怪怪地,而之前从来也没接触C#,难免有一点抵触心理.印象中,似乎存在C++/CLI这门语言,既然都是调用CLR里的组件,那么C++/CLI应该同样也能搞定.虽说之前同样没有接触C++/CLI,但是毕竟跟ISO-C++一脉相承,所以还是能接受在MFC的解决方案里增加一个C++/CLI的工程的.

  OK,说干就干.(参考< MFC 中使用 Windows 窗体用户控件>这篇文章).

.增加C++/CLI的工程

   1. 先在解决方案里添加工程 , 工程类型选择 Visual C++ -- CLR -- Windows  窗体控件库 . 随后 , 在此工程里打开相应的设计器 , 拖一个 ReportViewer 控件到设计器中 , 会发现 ReportViewer 右上角会有一个小三角形按钮 , 单击这个按钮在弹出的菜单中选择 " 在父容器中停靠 ", 这样 ReportViewer 控件就会自动根据父窗口的大小而改变自身的大小 ( 这个功能 , 或者说这个属性我是后来才发现 , 之前为了达到填充父窗口的效果 , 自己在本地 MFC 的视图类中 , 响应 WM_SIZE 消息 , 然后再通过 MoveWindow 来调整 ReportViewer 的大小 . 哈哈 , 这个弯路走得有点冤枉了 ).OK, 拖好控件后 , 直接编译此工程 .

  2.接着,在本地MFC的工程中,设置MFC工程属性 -- 配置属性--常规--公共语言运行时支持,默认的是无,在这里要修改成"公共语言时支持(/clr)",其实就是给编译器增加 /clr参数.设置好后,接着右击该MFC工程名,在弹出的菜单中选"引用",在弹出的对话框中,选择"添加新引用"--项目--选择刚才添加的C++/CLI工程,然后确定.

  3.接着再"添加新引用--".net"--"Microsoft ReportViewer WinForms".(重要步骤,后面会说明)

二.本地调用CLR组件

  因为我的本地MFC是多模板多文档工程,所以我参考MSDN的那篇文章,修改我相关的视图类如下:

  1.stdafx.h

  

#endif // _AFX_NO_AFXCMN_SUPPORT

  这一句的上面添加

#include <afxwinforms.h>

  2.在视图类的头文件中,修改基类为CWinFormsView.

  3.接着在视图类的实现CPP,IMPLEMENT_DYNCREATE宏中,将基类修改为WinFormsView.

再在消息映射宏BEGIN_MESSAGE_MAP中修改基类为WinFormsView.

  OK,重新编译此工程,运行,哈哈视图类已经显示了一个ReportViewer!

三.调用ReportViewer的相关属性及方法.

  1.为视图类增加一个数据成员

private:

gcroot<Microsoft::Reporting::WinForms::ReportViewer^ > m_pReportCtrl;

   对于跟我一样之前从没接触过C++/CLI的朋友们,这句代码乍一眼看上去,一定觉得很奇怪对不对?哈哈,在这里我简单的把我查了资料后的个人理解简单说一下,因为我也是现学现卖,不要拍砖呵呵.

  首先说"^"这个关键字.这个"^"正规叫法叫做"跟踪句柄"(tracking handle),对于这个新名词,我的理解是其实就是对应了ISO-C++里的"*"关键字,我们知道在本地C++"*"可以声明一个指针,比如CMyClass* pMyClass; 指针操作的都是本地内存,CLR中的所有资源都是托管的,所以指针这个概念到了CLR里就叫跟踪句柄.声明一个跟踪句柄 CMyClass^ pMyClass.呵呵,其实我个人觉得追踪句柄应该可以叫做托管指针吧.

  那么gcroot是啥意思呢,因为我们声明的这句代码是在本地代码定义的,在本地代码需要声明一个托管对象就得使用gcroot.其实,如果不加gcroot,编译器也会出现错误提示会提示你在本地不能声明托管对象.

  好了,更多的C++/CLI的相关知识,我也是个刚入门的初学者,朋友们可以自己去参考相关资料.在我以后的随笔中,我也会逐步增加学习C++/CLI的心得笔记.

我们回到正题.

  2.获得ReportViewer控件的跟踪句柄.

  我们重写视图类的OnInitialUpdate添加以下代码:

m_pReportCtrl= (safe_cast<DxReportView::DxReportViewControl^>(this->GetControl()))->GetReportCtrl();

  解释一下,GetControl()方法是视图类从WinFormsView继承下来的,该方法的返回类型是 System.Windows.Forms..::.Control 类型的追踪句柄,所以需要使用safe_cast强制转换成DxReportView::DxReportViewControl类型的追踪句柄.(这就有点像我们在视图类使用GetDocment()方法一样,返回的是CDocment指针,我们要转换成我们自己的派生类的指针类型.)其中DxReportView就是我建立的CLR工程里的命名空间,DxReportViewControl就是我CLR工程里的类名.大家根据实际情况修改成自己的命名空间跟类名.

  最后调用GetReportCtrl,这个是我自定义的方法.因为,CLR工程中ReportViewer控件是私有的,该方法就是返回该控件的跟踪句柄.

Microsoft::Reporting::WinForms::ReportViewer^

GetReportCtrl(void)

{

return reportViewer1;// 这个就是私有的ReportViewer控件了

}

  这样,以后我们要在视图类中调用ReportViewer就直接使用视图类的数据成员m_pReportCtrl就好了.

  3.ReportViewer的相关属性设定.

  我们在OnInitialUpdate里接着上面的代码,可以这样:

 

m_pReportCtrl->ShowToolBar = FALSE;

 

  这样我们就可以隐藏ReportViewer的工具栏了.因为我原来的MFC程序界面本身就带有一个工具栏,在视图类里再显示一个ReportViewer控件自带的工具栏感觉不伦不类的.(隐藏ReportViewer控件的工具栏,是为了界面统一看着好看些,但是也折磨了我好一阵,这是后话,在下一篇随笔中,打算唠叨一下这段小插曲).

  还记得在本文第一部分,第三小节所说的添加引用"Microsoft ReportViewer WinForms"?这里该说明需要这个步骤的的原因了.假如,没有这个引用,那么编译器在这里就无法识别ShowToolBar字段.这里就有点像我们包含头文件一样.

  关于ReportViewer的其他属性,可以参看MSDN,这里就不过多介绍了.

四.显示报表

  1.制作报表文件(.rdlc)

  程序现在跑起来,ReportViewer只是一个空壳子而已,因为我们没有指定它要显示哪个报表,所以我们得先做一个报表.

  在CLR工程中,打开界面设计器,单击ReportViewer右上角的小三角按钮,选择"设计新报表",这个时候工程会自动创建一个默认名为Report1.rdlc的报表文件,同时自动打开了报表设计器,我们在这个报表设计器里就可以设计自己的报表了.相关报表设计请参考MSDN<创建客户端报表定义 (.rdlc) 文件>

  链接:http://technet.microsoft.com/zh-cn/ms252067(v=vs.80).

  2.设计业务对象以充当数据源

  设计好报表的相关布局后,需要指定数据源.数据源可以是SQL,XML,业务对象等.因为我的需要是实时动态的显示报表,所以使用业务对象无疑是最好的选择.

  首先,我们在CLR工程里使用向导新增一个空的C++.在新增类的头文件中我们需要修改一下.向导生成的代码类似如下:

ref class CReportData

{

public:

    CReportData(void);

}

  这里我们修改成下面的代码,(这是我新建类的一部分代码)

namespace DxReportView
{
    public ref class CReportData
    {
    public:
        CReportData(void);
    public:
        property double T;
        property double Th;
        property double Tice;
        property double Vp;
    };
}

  有三点要说一下.

  先是 命名空间 DxReportView 这个不是必要的,只是为了保持C++/CLI的风格统一而已,(用命名空间管理类)

  接着我们发现在ref class 前面加了一个public这个是一定要加的,否则我们创建的数据源的时候就无法使用我们自定义的类充当业务对象.(这个一定要得加public也是我摸索了一个多小时才整出来的撒,惭愧...C++/CLI的语法什么的都没学过,只能摸着石头慢慢过河了).

  最后,添加了四个数据成员,其中property关键字是一定要的,切记!这个也是我摸索很久才搞定的.再次惭愧!

  新建好这个类后,我们的业务对象就设计完成了.我们编译此CLR工程.

  3.为报表指定数据源

  确保在解决方案视图,选中CLR工程,接着在菜单栏--数据--添加新数据源.在弹出的对话框中,选择"对象",下一步,接着选择我们刚才新建的那个类,确定.

  这个时候我们发现我们新建的业务对象已经在数据源面板显示出来了,如果你的导航栏没有"数据源"一项,则菜单--数据--显示数据源.如果在上一个小节中我们创建数据成员的时候没有使用property关键字,那么在数据源里我们是看不到该字段的.

  接下来,我们我们打开我们刚才新建的报表文件(.rdlc),直接从数据源面板中,把相应的字段拖到报表里相应的数据单元格中.

  再接着,我们来到CLR工程的界面设计器中,点击ReportViewer右上角的三角按钮,在选择报表的Combox下拉框中,选择我们刚才的.rdlc文件.

  最后,最重要的一步,我们打开CLR工程的属性设置--配置属性--链接器--输入--嵌入托管资源文件,输入报表文件名Report1.rdlc.为什么要嵌入这个报表文件当做资源呢,这是因为在向导生成的代码中,我们可以看到这一句:

this->reportViewer1->LocalReport->ReportEmbeddedResource = L"Report1.rdlc";

  ReportEmbeddedResource这个属性值的作用就是指定ReportViewer显示内嵌的报表文件.

  如果我们把这句代码修改成下面这样,

 

this->reportViewer1->LocalReport->ReportPath= L"Report1.rdlc";

 

  那么我们就不用吧Report1.rdlc当做资源嵌入进来了,而要保证程序运行时,在程序目录存在Report1.rdlc文件.

  4.实例化数据源

  我们为报表指定了我们的业务对象充当数据源,但是我们还没有实例化业务对象,所以需要实例化我们的业务对象.

  首先,右键点击界面设计器--查看代码.在此头文件开头包含头文件

#include "ReportData.h" // 该头文件就是刚才我们新建类的头文件了

  接着,为界面类添加数据成员

private: CReportData^ reportData;

   然后,我们返回到界面设计器,因为我们的ReportViewer设定了在父容器中停靠,所以ReportViewer充满了整个窗口,无法选定它的父容器,所以我们右键--"选择DxReportViewControl",这样就可以选择它的父容器了.再接着,右键--属性--事件.在行为分组中找到Load事件,双击编辑框,这样就自动添加了Load事件,在该事件代码中我们添加如下代码:

private: System::Void OnLoad(System::Object^  sender, System::EventArgs^  e)
 {
     reportData = gcnew CReportData;
     this->CReportDataBindingSource->DataSource = reportData;
 }

  其中,CReportDataBindingSource是设计器自动声明的变量,它的声明代码我也贴出来吧,

this->CReportDataBindingSource = (gcnew System::Windows::Forms::BindingSource(this->components));

  这是向导自动生成的,所以在OnLoad,朋友们要把CReportDataBindingSource替换成大家的向导生成的变量名字.

  5.动态修改业务对象的字段值以达到动态显示报表的效果

  我们回到MFC的本地工程,在视图类添加成员变量:

gcroot<DxReportView::CReportData ^> m_pReportData;

  然后在视图类OnInitialUpdate中添加代码:

m_pReportData = (safe_cast<DxReportView::DxReportViewControl^>(this->GetControl()))->GetReportData();

  GetReportData()方法,是我在CLR工程里自定义的一个方法,代码如下.

public:
CReportData^ GetReportData(void)
{
    return reportData;
}

  reportData,就是我们在上一小节实例化业务对象的时候,声明的数据成员,因为是私有的,所以我们提供个方法,获得该成员.

这样,以后就可以直接使用视图类的成员m_pReportData来操作报表绑定的数据源了.

   我们来到视图类的OnUpdata,以下是我的代码片段

void CDxReportView::OnUpdate(CView* /*pSender*/, LPARAM /*lHint*/, CObject* /*pHint*/)
{
    CDxReportDoc *pDoc = (CDxReportDoc*)GetDocument();
    ASSERT_VALID(pDoc);
    m_pReportData->T = pDoc->Input.T;
    m_pReportData->Th = pDoc->Input.Th;
    m_pReportData->Tice = pDoc->Input.Tice;
    m_pReportData->Vp = pDoc->Input.Vp;
    ......
    ......
    m_pReportCtrl->RefreshReport();
}

  我们修改了业务对象的值后,最后通过调用ReportViewerRefreshReport方法,来刷新报表,这个时候我们就可以看到,报表可以实时的根据需要显示动态的数据了!

  后记:初次接触C++/CLI,感觉世界一下变得无限宽广了,有了这次经验,相信在以后的项目中要融入.net的东东会顺手很多.回头看这次初体验,基本都是一个一个属性的试,一个一个试验调用方法,一句一句代码的调试.一点一点的啃有限的帮助文档.这个过程有很多曲折艰辛,但更多的是收获的喜悦.

  C++/CLI确实很强大,准备开始全面学习C++/CLI,呵呵到时候就不用像现在这样完全是摸着石头过河,完全是一半靠猜,一半靠试验了.

  关于,这个项目中的动态报表的模块,在手头上的这个项目完成后,时间充裕的话我就会把这部分模块代码提取出来,到时候再发完整代码.

                                                          2012-05-28 14:12:36

转载于:https://www.cnblogs.com/lijianglidedaxia/archive/2012/05/28/2521278.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值