zoukankan      html  css  js  c++  java
  • 【转】【学习笔记】《北塔教你做插件 从RibbonX开始》第三讲:再建Ribbon——ATL的实现方法

    原文链接:https://blog.csdn.net/northtower/java/article/details/41316687


    本文将介绍如何使用ATL标准模板库实现Office 插件的开发工作,重点为Addin插件连接以及Ribbon菜单载入。
    PS:本章示例的实现环境为VS2010。


    目录:
    实现MSOffice_Addin插件的基础
    用ATL创建WordAddin插件
    WordAddin插件定义Ribbon菜单


    第一节、实现MSOffice_Addin插件的基础


    做Office插件,你可曾想过:是什么来维护插件的生命周期?又是什么控制了插件的创建、卸载与更新行为?答案就是IDTExtensibility2 接口!
    该接口中定义了五个方法,具体如下:


    从实例化插件到被卸载,整个生命流程如图所示。

    我们有了实现MSOffice_Addin插件的理论基础,加之上一章中介绍的IRibbonExtensibility接口,自己动手开发插件已水到渠成。

    备注:
    MSDN中对该接口的定义如下:
    http://msdn.microsoft.com/en-us/library/extensibility.idtextensibility2.aspx
    IDTExtensibility2是个接口,控制插件连接到宿主程序的生命周期,而宿主程序并不一定为MSOffice!


    第二节、用ATL创建WordAddin插件

     2.1、创建ATL项目
      启动VS,新建ATL工程"ATLAddin",所有设置均保持默认选项,唯一需要注意的就是"Application type"选择DLL。

         通过添加类(Add Class),加入ATL简单对象(ATL Simple Object)。[Short Name]键入:“MSConnect”,ProgID定义为"ATLAddin.MSConnect"。
         此对象将用于维护所有与MSOffice相关的连接工作。

     


      2.2、引入IDTExtensbility2接口    
         根据第一节中的理论基础,在ATL对象已经创建完备的前提下,下一步工作就是实现与MSOffice的互连。     
         在类视图中选择"CMSConnect"节点,右键添加实现接口(Implement Interface)。

     

       在接口向导对话框中,类型库选择引入"Microsoft Add-In Designer <1.0>"的支持,在接口列表框中引入对“_IDTExtensbility2"接口的支持。


        对_IDTExtensbility2接口引入成功后,我们查看MSConnect.h文件,CMSConnect类的声明与接口映射均发生了变化,加入了对_IDTExtensibility2及其方法的引用;

    public IDispatchImpl<_IDTExtensibility2, &__uuidof(_IDTExtensibility2), &LIBID_AddInDesignerObjects, /* wMajor = */ 1>
    
    
    // _IDTExtensibility2 Methods
    public:
    STDMETHOD(OnConnection)(LPDISPATCH Application, ext_ConnectMode ConnectMode, LPDISPATCH AddInInst, SAFEARRAY * * custom)
    {
    return E_NOTIMPL;
    }
    STDMETHOD(OnDisconnection)(ext_DisconnectMode RemoveMode, SAFEARRAY * * custom)
    {
    return E_NOTIMPL;
    }
    STDMETHOD(OnAddInsUpdate)(SAFEARRAY * * custom)
    {
    return E_NOTIMPL;
    }
    STDMETHOD(OnStartupComplete)(SAFEARRAY * * custom)
    {
    return E_NOTIMPL;
    }
    STDMETHOD(OnBeginShutdown)(SAFEARRAY * * custom)
    {
    return E_NOTIMPL;
    }    

            五种事件的响应,我们插件中已然实现了,添加断点以直观地了解各个方法的调用顺序,不过在此之前我们还剩最后一步,就是注册插件!

      2.3、向MSWord中注册插件

          到目前为止,我们用ATL编写的ActiveX样例虽然引入了“_IDTExtensbility2"接口的支持,但宿主程序究竟是谁?我们没有定义。
        连接MSWord的方法也很简单,我们打开注册表“HKEY_CURRENT_USERSoftwareMicrosoftOfficeWordAddins”。


    “Addins”键值下定义了当前系统中,所有已经被注册的插件。MSWord在启动实例化插件时,会根据此键值指向的内容,分别实例化所有插件。
    截图中的“WordAddIn1”即为上一章中我们用C#_VSTO编写的工程,该插件的注册行为是在“WordAddIn1.dll.manifest”文件中描述的,内容如下:


    <vstov4:appAddIn application="Word" loadBehavior="3" keyName="WordAddIn1">
    <vstov4:friendlyName>WordAddIn1</vstov4:friendlyName>
    <vstov4:description>WordAddIn1</vstov4:description>
    </vstov4:appAddIn>


       MSOffice-Addins插件的注册,有最少三项内容是必须存在的:
    FriendlyName(String) :插件名称
    Description(String)     :插件描述
    LoadBehavior(DWORD) :插件的加载方式,有以下几种。

     扩展:MSOffice中其他应用,如Outlook、Execl和PowerPoint的插件加载方式也与此相同,请自行消化,本章不再作进一步讨论。

    注册MSWord插件的理论基础明白以后,写代码就容易多了。
    打开“MSConnect.rgs”文件,向文件末尾加入以下代码:

    HKCU
    {
        NoRemove Software
        {
            NoRemove Microsoft
            {
                NoRemove Office
                {
                    NoRemove Word
                    {
                        NoRemove Addins
                        {
                            ATLAddin.MSConnect
                            {
                                val Description = s 'ATLAddin Addin'
                                val FriendlyName = s 'ATLAddin Addin'
                                val LoadBehavior = d 3
                            }
                        }
                    }
                }
            }
        }
    }

    加入代码以后,编译运行吧。(如果人品不受伤的话,该插件是能通过的!)
    控件注册以后,请注意以下两点:
    注册表“HKEY_CURRENT_USERSoftwareMicrosoftOfficeWordAddins”键值下是否含有“ATLAddin.MSConnect”相应内容?
    在MSConnect.h文件的OnConnection方法中加入断点,在已经捕获断点的情况下,为什么该插件在Office整个启动关闭过程中,只进了OnConnection(),其他方法如OnDisconnection、OnStartupComplete全都没执行?

    PS:OnConnection() 函数要返回S_OK,否则,插件注册成功,但是Word加载项提示“COM加载项运行错误”

    第三节、WordAddin插件定义Ribbon菜单

       我们已经用ATL创建并注册了MSWord插件,此时在MSWord的"COM加载项"中我们是能看到自己所编写的"ATLAddin Addin"插件,如果不能看到,请先排查上节中在哪出了问题。如果实在不行,可以从本章的附件里下载样例工程。我已经把第二小节结束时的代码进行了备份上传,可自由选择。


       尝试回忆一下”IRibbonExtensibility“接口,对此不太清楚的情况下,可以浏览上一讲中的第二节“Ribbon的酒杯——IRibbonExtensibility”加深一下印象。
       传送门:第二讲跳转


     3.1 引入IRibbonExtensibility接口
       
          在类视图中选择"CMSConnect"节点,右键添加实现接口(Implement Interface)。
          在接口向导对话框中,类型库选择引入"Microsoft Office 14.0 Object Library<2.5>"的支持,在接口列表框中引入对“IRibbonExtensibility"接口的支持。


    注意:支持IRibbonExtensibility接口类型库有版本要求,最低版本为Office2007支持的‘Microsoft Office 12.0 Object Library<2.4>”,最新版本Office2013为“Microsoft Office 15.0 Object Library<2.7>” ,切记!!

     对IRibbonExtensibility接口的引入成功后,我们查看MSConnect.h文件:
     以加入对IRibbonExtensibility及其方法的引用;

    public IDispatchImpl<IRibbonExtensibility, &__uuidof(IRibbonExtensibility), &LIBID_Office, /* wMajor = */ 2, /* wMinor = */ 5>
    
    // IRibbonExtensibility Methods
    STDMETHOD(GetCustomUI)(BSTR RibbonID, BSTR * RibbonXml)
    {
    return E_NOTIMPL;
    }

      由于类型库版本的不同,此时编译工程,将会出现一些列编译错误,如图所示:

       根据报错信息,我们将stdafx.h文件中导入类型库语句,修改为:

    #import "C:Program FilesCommon FilesMicrosoft SharedOFFICE14MSO.DLL" 
    raw_interfaces_only, raw_native_types, no_namespace, named_guids, auto_search,
    exclude( "IAccessible"),exclude("DocumentProperties" )

    备注:以上修改信息,要根据你电脑中的具体错误进行相应修改!      

      3.2  生成Ribbon菜单

         基于GetCustomUI()方法,可以读入工程中指定Ribbon资源,使之在MSOffice的功能区中显示。
         我们将上一讲“WordAddIn1”工程中的Ribbon.xml文件拷贝到当前工程中的“ATLAddin”目录。在“ATLAddin”工程资源管理器中 Add-〉Existing Item ,将刚才拷贝的Ribbon.xml文件引入当前解决方案。
         再通过资源视图(Resource View),导入Ribbon.xml文件,类型定义为“XML”
         

         自此,Ribbon资源已经引入至当前工程,下一步需要的仅仅是读取该文件,交由MSOffice解析并绘制。
         完善GetCustomUI()方法中的读取文件行为,将以下内容替换MSConnect.h文件中的GetCustomUI()方法。

    // IRibbonExtensibility Methods
    STDMETHOD(GetCustomUI)(BSTR RibbonID, BSTR * RibbonXml)
    {
    if(!RibbonXml)
    return E_POINTER;
    
    *RibbonXml = GetXMLResource(IDR_XML1); 
    return S_OK;
    }
    
    HRESULT HrGetResource( int nId,
    LPCTSTR lpType,
    LPVOID* ppvResourceData, 
    DWORD* pdwSizeInBytes)
    {
    HMODULE hModule = _AtlBaseModule.GetModuleInstance();
    if (!hModule)
    return E_UNEXPECTED;
    HRSRC hRsrc = FindResource(hModule, MAKEINTRESOURCE(nId), lpType);
    if (!hRsrc)
    return HRESULT_FROM_WIN32(GetLastError());
    HGLOBAL hGlobal = LoadResource(hModule, hRsrc);
    if (!hGlobal)
    return HRESULT_FROM_WIN32(GetLastError());
    *pdwSizeInBytes = SizeofResource(hModule, hRsrc);
    *ppvResourceData = LockResource(hGlobal);
    return S_OK;
    }
    
    BSTR GetXMLResource( int nId)
    {
    LPVOID pResourceData = NULL;
    DWORD dwSizeInBytes = 0;
    HRESULT hr = HrGetResource(nId, TEXT( "XML"),
    &pResourceData, &dwSizeInBytes);
    if (FAILED(hr))
    return NULL;
    // Assumes that the data is not stored in Unicode.
    CComBSTR cbstr(dwSizeInBytes, reinterpret_cast<LPCSTR>(pResourceData));
    return cbstr.Detach();
    }
    
    SAFEARRAY* GetOFSResource( int nId)
    {
    LPVOID pResourceData = NULL;
    DWORD dwSizeInBytes = 0;
    if (FAILED(HrGetResource(nId, TEXT("OFS" ),
    &pResourceData, &dwSizeInBytes)))
    return NULL;
    SAFEARRAY* psa;
    SAFEARRAYBOUND dim = {dwSizeInBytes, 0};
    psa = SafeArrayCreate(VT_UI1, 1, &dim);
    if (psa == NULL)
    return NULL;
    BYTE* pSafeArrayData;
    SafeArrayAccessData(psa, ( void**)&pSafeArrayData);
    memcpy(( void*)pSafeArrayData, pResourceData, dwSizeInBytes);
    SafeArrayUnaccessData(psa);
    return psa;
    }

         编译运行吧,此节的修改不会引起编译错误。唯一需要注意的地方就是资源文件的声明,如“IDR_XML1”定义是否完整。
         
    注意:
         1、字符按编码问题:在Ribbon.xml中如果要定义中文信息,如中文标签名等,字符集一定要设置为 encoding="gb2312"。
         2、xmlns命名空间问题:
              Office2007版本          :xmlns="http://schemas.microsoft.com/office/2006/01/customui"
              Office2010及以上版本:xmlns="http://schemas.microsoft.com/office/2009/07/customui"
         
    3.3  Ribbon菜单回调与响应

          由于微软没有明确支持“RibbonCommand”响应的类型库,我们用MFC/ATL编写MSAddin插件时,要费一些精力。
          本章onButtonClick响应的方法,是改变IDispatch派发的接口映射,实现Ribbon菜单中命令的响应。
          按照上述指导思想,我和大家一起,一步一步完善ATLAddin最后一部分功能。

           1)修改Ribbon.xml文件,添加"onAction",代码如下:

    <?xml version="1.0" encoding="gb2312"?>
    <customUI onLoad="Ribbon_Load" xmlns="http://schemas.microsoft.com/office/2006/01/customui">
        <ribbon>
            <tabs>
                <tab id ="TabAddIns" label ="测试">
                    <group id ="group1" label ="New Group">
                        <button id ="button1"
                                label="button1"
                                imageMso="HappyFace"
                                onAction="onButton1_Click"
                                size="large" />
                        <button id ="button2"
                                label="button2"
                                onAction="onButton2_Click"
                                showImage="false" />
                    </group>
                </tab>
            </tabs>
        </ribbon>
    </customUI>

           

      2)修改COM接口映射表。将代码中,派发至_IDTExtensibility2的方法转而指向IMSConnect。
                     修改后的接口映射表如下:

                    BEGIN_COM_MAP(CMSConnect)
                                    COM_INTERFACE_ENTRY(IMSConnect)
    //                             COM_INTERFACE_ENTRY2(IDispatch, _IDTExtensibility2)
                                    COM_INTERFACE_ENTRY2(IDispatch, IMSConnect)
                                    COM_INTERFACE_ENTRY(_IDTExtensibility2)
                                    COM_INTERFACE_ENTRY(IRibbonExtensibility)
                    END_COM_MAP()

               3)在类视图“IMSConnect”节点,添加方法onButton1_Click和onButton2_Click,如图所示:  

                           

            在MSConnect.cpp中找到刚刚添加的两个方法,加入MessageBox测试代码。

    STDMETHODIMP CMSConnect::onButton1_Click(IDispatch* ribbonControl)
    {
    // TODO: Add your implementation code here
    MessageBoxW(NULL, L "Button1", L"ATL Addin" , MB_OK);
    return S_OK;
    }
    
    STDMETHODIMP CMSConnect::onButton2_Click(IDispatch* ribbonControl)
    {
    // TODO: Add your implementation code here
    MessageBoxW(NULL, L "Button2", L"ATL Addin" , MB_OK);
    return S_OK;
    }

               4)在MSConnect.h文件CMSConnect类中,重载Invoke方法,实现命令的响应。

    STDMETHOD(Invoke)(DISPID dispidMember, const IID &riid, LCID lcid, WORD wFlags, DISPPARAMS *pdispparams, VARIANT *pvarResult, EXCEPINFO *pexceptinfo, UINT *puArgErr)
    {
    if(dispidMember == 1)
    onButton1_Click(NULL);
    else
    onButton2_Click(NULL);
    return S_OK;
    }

      5)ATLAddin.idl ,添加如下代码响应

    interface IMSConnect : IDispatch{
        [id(1)] HRESULT onButton1_Click([in] IDispatch* ribbonControl);// 添加
    //    [id(2)] HRESULT onButton2_Click([in] IDispatch* ribbonControl);
    };

    编译后,效果

           虽然,这个例子可以创建成功,运行正常,但是想开发一个插件还是比较困难的,而且环境问题比较挑剔,#import出错后,根本无法解决,知道的人很少,网上资料也很少,

    还是回归VSTO吧

    ================================20200611 更新 已解决import问题转  这里

  • 相关阅读:
    开发了那么多项目,你能自己手写个健壮的链表出来吗?【华为云技术分享】
    高性能Web动画和渲染原理系列(3)——transform和opacity为什么高性能【华为云技术分享】
    pringBoot-MongoDB 索引冲突分析及解决【华为云技术分享】
    成为高手前必懂的TCP干货【华为云技术分享】
    Python爬虫从入门到精通——基本库re的使用:正则表达式【华为云技术分享】
    【我的物联网成长记2】设备如何进行选型?【华为云技术分享】
    多云架构落地设计和实施方案【华为云技术分享】
    dom的节点操作
    节点访问关系
    封装class类--分割类名后
  • 原文地址:https://www.cnblogs.com/nightnine/p/12619095.html
Copyright © 2011-2022 走看看