ExcelHome技术论坛

 找回密码
 免费注册

QQ登录

只需一步,快速开始

EH搜索     
EH云课堂-专业的职场技能充电站 Excel转在线管理系统,怎么做看这里 Excel服务器-会Excel,做管理系统 Excel Home精品图文教程库
Excel不给力? 何不试试FoxTable! Excel 2016函数公式学习大典 高效办公必会的Office实战技巧 免费下载Excel行业应用视频
300集Office 2010微视频教程 Tableau-数据可视化工具 精品推荐-800套精选PPT模板,点击获取 ExcelHome出品 - VBA代码宝免费下载
你的Excel 2010实战技巧学习锦囊 欲罢不能, 过目难忘的 Office 新界面 Excel VBA经典代码实践指南
查看: 6619|回复: 18

[分享] 将Exce嵌入你的.Net程序

[复制链接]

TA的精华主题

TA的得分主题

发表于 2011-6-21 18:43 | 显示全部楼层 |阅读模式
本帖已被收录到知识树中,索引项:其他专业开发
这是一篇技术总结,主要说明在.Net情况下,将Excel嵌入我们自己的程序,并进行控制。
    如果没有.Net版的OLE Container Control(2005版本叫ActiveDocumentControl),我们几乎没有太多的办法来完成这个任务(可以利用Visual C++来完成工作,不过该实现对技术的要求太高,大部分人不具备这个能力,最好在Visual C++基础上开发一个控件,在别的环境下调用),这样只能够使用重量级的解决方案,就是利用WebBrowser控件和dsoframer.OCX来完成这个工作。
        在Visual Studio 2005中,为我们提供了一个WebBrowser的封装控件,这个控件隐藏了底层的axWebBrowser控件,我们就利用WebBrowser控件来完成我们的Excel等ActiveDocument的嵌入和操作。在此之前,你需要将Office的.Net库安装到你的机器上。
1.在你的工具栏上选择WebBrowser控件,将其放到你的Form上
2.在Form上增加一个控件来使其加载一个Excel文件,如下:
Button的事件处理程序:
  private void button1_Click_1(object sender, EventArgs e)
  {
            string strFileName = @"d:\a.xls";
            Object refmissing = System.Reflection.Missing.Value;
            this.webBrowser1 .Navigate(strFileName);
            object axWebBrowser = this.webBrowser1.ActiveXInstance;
   }
<下一节>

评分

参与人数 1鲜花 +5 收起 理由
xmyjk + 5 优秀作品

查看全部评分

TA的精华主题

TA的得分主题

 楼主| 发表于 2011-6-21 18:46 | 显示全部楼层

之二

3.增加WebBrowser的DocumentComplete事件处理程序
private void webBrowser1_DocumentCompleted_1(object sender, WebBrowserDocumentCompletedEventArgs e)
{
            Object refmissing = System.Reflection.Missing.Value;
            object[] args = new object[4];
            args[0] = SHDocVw.OLECMDID.OLECMDID_HIDETOOLBARS;
            args[1] = SHDocVw.OLECMDEXECOPT.OLECMDEXECOPT_DONTPROMPTUSER;
            args[2] = refmissing;
            args[3] = refmissing;

            object axWebBrowser = this.webBrowser1.ActiveXInstance;
            axWebBrowser.GetType().InvokeMember("ExecWB", BindingFlags.InvokeMethod, null, axWebBrowser, args);
            object oApplication = axWebBrowser.GetType().InvokeMember("Document", BindingFlags.GetProperty, null, axWebBrowser, null);
   
            Excel.Workbook wbb = (Excel.Workbook)oApplication;
            Excel.ApplicationClass excel = wbb.Application as Excel.ApplicationClass ;
            Excel.Workbook wb = excel.Workbooks[1] ;
            Excel.Worksheet ws = wb.Worksheets[1] as Excel.Worksheet;
            ws.Cells.Font.Name = "Verdana";
            ws.Cells.Font.Size = 14;
            ws.Cells.Font.Bold = true;
            Excel.Range range = ws.Cells;

            Excel.Range oCell = range[10, 10] as Excel.Range ;
            oCell.Value2 = "你好";
            
  }
        这里要注意,WebBrowser加载Excel文档后,并不显示工具栏,为此我们需要使用高级一点的技术,通过反射调用底层的方法来实现,首先我们需要找到WebBrowser封装的AxWebBrowser控件。
      object axWebBrowser = this.webBrowser1.ActiveXInstance;
然后,我们调用反射来显示工具
      Object refmissing = System.Reflection.Missing.Value;
            object[] args = new object[4];
            args[0] = SHDocVw.OLECMDID.OLECMDID_HIDETOOLBARS;
            args[1] = SHDocVw.OLECMDEXECOPT.OLECMDEXECOPT_DONTPROMPTUSER;
            args[2] = refmissing;
            args[3] = refmissing;

         axWebBrowser.GetType().InvokeMember("ExecWB", BindingFlags.InvokeMethod, null, axWebBrowser, args);
         注:如果你直接使用AxWebBrowser控件,那你就可以直接调用ExecWB方法来显示工具栏。如下:
     axWebBrowser1.ExecWB(SHDocVw.OLECMDID.OLECMDID_HIDETOOLBARS, SHDocVw.OLECMDEXECOPT.OLECMDEXECOPT_DONTPROMPTUSER, refmissing, refmissing)

TA的精华主题

TA的得分主题

 楼主| 发表于 2011-6-21 18:49 | 显示全部楼层

之三

好了,现在你需要获得你的Excel对象来操作Excel,此时需要使用Office的.Net库来完成。代码如下:               
      object oApplication = axWebBrowser.GetType().InvokeMember("Document",  BindingFlags.GetProperty, null, axWebBrowser, null);
            Excel.ApplicationClass excel = wbb.Application as Excel.ApplicationClass ;
            Excel.Workbook wb = excel.Workbooks[1] ;
            Excel.Worksheet ws = wb.Worksheets[1] as Excel.Worksheet;
            ws.Cells.Font.Name = "Verdana";
            ws.Cells.Font.Size = 14;
            ws.Cells.Font.Bold = true;
            Excel.Range range = ws.Cells;
            Excel.Range oCell = range[10, 10] as Excel.Range ;
            oCell.Value2 = "你好";
说明:通过使用WebBrowser控件,是不能显示菜单栏的。

TA的精华主题

TA的得分主题

 楼主| 发表于 2011-6-21 18:53 | 显示全部楼层

之四

3.如果使用AxWebBrowser控件,那就需要处理NavigateComplete2事件,事件处理程序如下
    private void axWebBrowser_NavigateComplete2(object sender, AxSHDocVw.DWebBrowserEvents2_NavigateComplete2Event e)
        {
            Object refmissing = System.Reflection.Missing.Value;
            this.webBrowser1.ExecWB(SHDocVw.OLECMDID.OLECMDID_HIDETOOLBARS, SHDocVw.OLECMDEXECOPT.OLECMDEXECOPT_DONTPROMPTUSER, ref refmissing, ref refmissing);
                   Object o = e.pDisp;
                   Object oDocument = o.GetType().InvokeMember("Document",BindingFlags.GetProperty, null, o, null);
            Object oApplication = o.GetType().InvokeMember("Application", BindingFlags.GetProperty, null, oDocument, null);
                ////由于打开的是excel文件,所以这里的oApplication 其实就是Excel.Application
            Excel.Application excel = (Excel.Application)oApplication;
               //这样就可以象上文中所述来操作Excel了。

      Excel.Workbook wb = excel.Workbooks[1];
            Excel.Worksheet ws = wb.Worksheets[1] as Excel.Worksheet;
            ws.Cells.Font.Name = "Verdana";
            ws.Cells.Font.Size = 14;
            ws.Cells.Font.Bold = true;
            Excel.Range range = ws.Cells;

            Excel.Range oCell = range[10, 10] as Excel.Range;
            oCell.Value2 = "你好";
        }
        这里操作更简单一点,缺点就是你必须直接使用AxWebBrowser控件,该控件比起已经封装好的WebBrowser可能有些我们所不知道的问题。微软推荐大家通过WebBrowser来访问。使用AxWebBrowser,有一个意外的收获,如下:
   Object o = e.pDisp;
       IOleCommandTarget cmdTarget = o as IOleCommandTarget;
      Guid guidCMDGroup = new Guid("00020820-0000-0000-C000-000000000046");
      cmdTarget.Exec(ref guidCMDGroup , 1, 0, null, null);

        通过IoleCommandTarget可以模拟如菜单命令,这样你可以自己定制菜单,然后将菜单命令发送到Excel来处理。呵呵,模拟菜单。
  public interface IOleCommandTarget
    {
        [PreserveSig()]
        int QueryStatus([In, MarshalAs(UnmanagedType.Struct)] ref Guid
           pguidCmdGroup, [MarshalAs(UnmanagedType.U4)] int cCmds,
           [In, Out] IntPtr prgCmds, [In, Out] IntPtr pCmdText);

        [PreserveSig()]
        int Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdExecOpt,
            object[] pvaIn, [In, Out, MarshalAs(UnmanagedType.LPArray)]
            object[] pvaOut);
    }
<待续>

TA的精华主题

TA的得分主题

 楼主| 发表于 2011-6-21 18:58 | 显示全部楼层

之五

4.如果我们使用Visual C++我们可以获得一个完全的,嵌入式Excel,包括菜单,看起来就像我们自己的一样:
    可惜我们对Visual C++都不太知道如何控制。
    使用AxWebBrowser或者WebBrowser的方法将Office嵌入我们的.Net系统问题有几个,1是WebBrowser控件是一个比较重的控件,2是通过WebBrowser去控制Office如果出现问题没有办法进行调试与判断,也无法修改,3是Office对应的菜单没有办法控制。为此我们决定需求新的解决方案,使用微软提供的dsoFramer控件例子,这个例子使用VC++编写,本身是可以使用的,可以参见:
   http://support.microsoft.com/default.aspx?scid=kb;en-us;311765
      在这里下载dsoFramer的源码
   http://www.microsoft.com/downloa ... &displaylang=en

TA的精华主题

TA的得分主题

 楼主| 发表于 2011-6-21 19:02 | 显示全部楼层

之六:艰辛的改造历程

下面进行艰辛的改造过程
    1.使用VisualStudio2005打开该VC工程,按系统提示进行转换
    2.转换完成后,发现该控件缺少OLB文件,这个文件是控件的对象库,在引用该控件的时候要用到,编译该对象库需要mktyplib.exe但该工具在VS2005中已经不再提供,使用MIDL代替,但MIDL又不支持这种转换过来的工程,可从VS2003中找到旧的mktyplib.exe,拷入VS2005\common\tools\bin下面,呵呵
    3.编译好ocx,通过Regsv***** 注册到系统中
    4.创建一个C#工程,引入刚才的ocx
        5.发现该控件,自带了菜单,工具栏,并且我们基本不能控制该控件,晕倒
    6.决定自己将它的菜单栏放到我们的.Net  Form上,发现不能将 C++的菜单直接放过来,C++的有太多的控制了,如果要重写这些控制,就要吐血了,并且发现新的MenuStrip控件根本就不是一个菜单,传入C++后,不认识。费了好大的劲,将Form的MainMenu给传了进去,呵呵,这下行了,看看运行的效果:
    怎么样,感觉还不错吧,现在这个Excel就可以好好的控制了,最主要的是, 如果你想增加什么新的东西,出现什么问题就可以自己调试解决了。
    如果你要在OCX中增加接口,你必须同时修改三个地方,一个是ODL,OCX的接口定义文件,并且按照规范,一个是你的控件实现的对应的.H文件,一个实现的.CPP文件,这些都简单了。

TA的精华主题

TA的得分主题

 楼主| 发表于 2011-6-21 19:07 | 显示全部楼层

之七:传递控制菜单

6.菜单出来了,发现不能控制,又再次晕倒,不知所措了
    解决办法,是通过OVerride窗体的WndProc方法,将菜单的事件,除了你自己的菜单以外的,都给传到OCX中。
    在OCX中增加一个接口方法:ExecuteMenuMessage,它直接调用OnMenuMessage,呵呵,这样就行了,代码如下:
        protected override void WndProc(ref Message m)
        {
            IntPtr  lWParam=IntPtr.Zero  ;
            switch (m.Msg)
            {
                case WindowsMessages.WM_INITMENU :
                case WindowsMessages.WM_ENTERIDLE:
                case WindowsMessages.WM_MENUSELECT:
                case WindowsMessages.WM_INITMENUPOPUP:
                case WindowsMessages.WM_COMMAND :                  
                    ExecuteMenuMessage(m.Msg, m.WParam, m.LParam);
                    //Console.Write(m.Msg.ToString("X"));
                    //Console.Write("    ");
                    //Console.Write(lWParam.ToString("X"));
                    //Console.Write("    ");
                    //Console.WriteLine(m.LParam.ToString("X"));
                    break;               
            }
        
             base.WndProc(ref m);      
        }
   现在菜单信息也传入了,哦,创建菜单的程序如下,酷吧:
   private void Form1_Load(object sender, EventArgs e)
        {
            //if (this.Menu == null)
            {
                this.Menu = new MainMenu();
                CreateFileMenu();
                IntPtr hMainMenu = this.Menu.Handle;
                this.axFramerControl1.MainFormMenu = hMainMenu;
                this.axFramerControl1.MainForm = this.Handle;
            }
        }
        

        private void CreateFileMenu()
        {
            IntPtr pFileMenu = MenuHelper.CreatePopupMenu();
            MenuHelper.AppendMenu(pFileMenu, MenuFlags.MF_STRING, FrameMenuMessage.MNU_NEW, "新建(&N)...\tCtrl+N");
            MenuHelper.AppendMenu(pFileMenu, MenuFlags.MF_STRING, FrameMenuMessage.MNU_OPEN, "打开(&O)...\tCtrl+O");
            MenuHelper.AppendMenu(pFileMenu, MenuFlags.MF_STRING, FrameMenuMessage.MNU_CLOSE, "关闭(&C)");
            MenuHelper.AppendMenu(pFileMenu, MenuFlags.MF_SEPARATOR, 0, string.Empty);
            MenuHelper.AppendMenu(pFileMenu, MenuFlags.MF_STRING, FrameMenuMessage.MNU_SAVE, "保存(&S)\tCtrl+S");
            MenuHelper.AppendMenu(pFileMenu, MenuFlags.MF_STRING, FrameMenuMessage.MNU_SAVEAS, "另存为(&A)...");
            MenuHelper.AppendMenu(pFileMenu, MenuFlags.MF_SEPARATOR, 0, string.Empty);
            MenuHelper.AppendMenu(pFileMenu, MenuFlags.MF_STRING, FrameMenuMessage.MNU_PGSETUP, "页面设置(&U)...");
            MenuHelper.AppendMenu(pFileMenu, MenuFlags.MF_STRING, FrameMenuMessage.MNU_PRINTPV, "打印预览(&V)");
            MenuHelper.AppendMenu(pFileMenu, MenuFlags.MF_STRING, FrameMenuMessage.MNU_PRINT, "打印(&P)...");
            MenuHelper.AppendMenu(pFileMenu, MenuFlags.MF_SEPARATOR, 0, string.Empty);
            MenuHelper.AppendMenu(pFileMenu, MenuFlags.MF_STRING, FrameMenuMessage.MNU_PROPS, "属性(&I)");
            MenuHelper.AppendMenu(this.Menu.Handle, MenuFlags.MF_BYPOSITION | MenuFlags.MF_POPUP, (uint)pFileMenu.ToInt32() , "文件(&F)");
            MenuHelper.DrawMenuBar(this.Handle);
}

TA的精华主题

TA的得分主题

 楼主| 发表于 2011-6-21 19:10 | 显示全部楼层

之八 收尾之作

7.当关闭Excel后会怎么样,靠,菜单还在,并且每个都报错,OLE已经卸载了,只好跑去修改OCX控件中docObject的ClearMergeMenu,让它清除掉Excel的菜单。
// CDsoDocObject::ClearMergedMenu (protected)
//  Frees the merged menu set by host.
STDMETHODIMP_(void) CDsoDocObject::ClearMergedMenu()
{
if (m_hMenuMerged)
{
        int cbMenuCnt = GetMenuItemCount(m_hMenuMerged);
  int iMainMenuCnt=0;
  if(this->m_hMenuMainForm)
   iMainMenuCnt=GetMenuItemCount(m_hMenuMainForm);
  for (int i = cbMenuCnt-1; i >0; --i)
  {
   if(this->m_hMenuMainForm)
   {
    RemoveMenu(m_hMenuMainForm,--iMainMenuCnt,MF_BYPOSITION);
   }
   RemoveMenu(m_hMenuMerged, i, MF_BYPOSITION);
  }
  if(m_hwndMainForm)
   DrawMenuBar(m_hwndMainForm);
  DestroyMenu(m_hMenuMerged);
  m_hMenuMerged = NULL;
}
}
      还有一些问题:
   1.菜单合并时候,可以指定位置,这样有利于我们控制,详细代码实现未付诸实践。
   2.文件菜单中,当Excel卸载后,文件菜单的部分不能自动置为灰色的,还没搞清楚具体原因

TA的精华主题

TA的得分主题

发表于 2011-6-21 19:27 | 显示全部楼层

TA的精华主题

TA的得分主题

发表于 2011-6-21 20:38 | 显示全部楼层
您需要登录后才可以回帖 登录 | 免费注册

本版积分规则

关注官方微信,每天学会一个新技能

手机版|关于我们|联系我们|ExcelHome

GMT+8, 2019-8-22 08:15 , Processed in 0.098871 second(s), 17 queries , Gzip On, MemCache On.

Powered by Discuz! X3.4

© 1999-2020 Wooffice Inc.

   

沪公网安备 31011702000001号 沪ICP备11019229号

本论坛言论纯属发表者个人意见,任何违反国家相关法律的言论,本站将协助国家相关部门追究发言者责任!     本站特聘法律顾问:徐怀玉律师 李志群律师

快速回复 返回顶部 返回列表