《北塔教你做插件 从RibbonX开始》第三讲:再建Ribbon——ATL的实现方法

前言:
本文将介绍如何使用ATL标准模板库实现Office 插件的开发工作,重点为Addin插件连接以及Ribbon菜单载入。
PS:本章示例的实现环境为VS2010。
目录:
  1. 实现MSOffice_Addin插件的基础
  2. 用ATL创建WordAddin插件
  3. WordAddin插件定义Ribbon菜单

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

Method

Description

OnConnection

This method is called when the add-in is loaded in Visual Studio.

OnStartupComplete

This method is called when Visual Studio finishes loading.

OnAddInsUpdate

This method is called when an add-in loads or unloads from Visual Studio.

OnBeginShutdown

This method is called when Visual Studio is closed.

OnDisconnection

This method is called when the add-in is unloaded from Visual Studio.


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

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

备注:
MSDN中对该接口的定义如下:
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_USER\Software\Microsoft\Office\Word\Addins”。


“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) :插件的加载方式,有以下几种。
LoadBehavior描述
0断开,不加载该控件
1连接,加载该控件
2启动时加载,主应用程序启动时加载并连接COM加载项
31与2 情况之和 
8触发加载方式

  扩展: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
                        }
                    }
                }
            }
        }
    }
}
加入代码以后,编译运行吧。(如果人品不受伤的话,该插件是能通过的!)
控件注册以后,请注意以下两点:
  1. 注册表“HKEY_CURRENT_USER\Software\Microsoft\Office\Word\Addins”键值下是否含有“ATLAddin.MSConnect”相应内容?
  2. 在MSConnect.h文件的OnConnection方法中加入断点,在已经捕获断点的情况下,为什么该插件在Office整个启动关闭过程中,只进了OnConnection(),其他方法如OnDisconnection、OnStartupComplete全都没执行?
PS:以上两问题供大家思考,答案请留意本章“总结与后记” 


第三节、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 Files\Common Files\Microsoft Shared\OFFICE14\MSO.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;
}

         开始编译链接吧,看看我们的成果。样例虽然简单,但我们已经使用ATL完成了Ribbon的创建与响应。麻雀虽小,五脏俱全。

       

总结与后记:
          [第二节问题答疑]
       问题1,控件注册问题:注意权限控制,如在Win7系统手动注册时需要具备管理员权限方可写入注册信息。
       问题2,当调用OnConnection方法时,返回值为非“S_OK”状态,系统认为该插件连接失败,Startup Complete也就无从谈起了。

       用ATL编写RibbonAddin还有很多需要详细介绍的地方,如COM中的双接口、RibbonXML中的多控件定义、Invoke动态派发等等。本系列教程只是帮助你快速了解Ribbon的实现方法,其他内容,如果大家有意了解,请给我留言,我有能力的话再和大家继续探讨。
       本教程的源代码,我分了三个部分:
       1)、第二小节讲解之后的代码。(5枚硬币)  下载地址
       2)、第三小节讲解之后的代码。(10枚硬币)下载地址
       3)、彩蛋版本ATLAddin代码。   (15枚硬币)

PS:要价小贵,但所有的积分都是大家对我的鼓励。
PPS:《北塔教你做插件从RibbonX开始》 第三讲完成以后,我打算再做一些关于OfficeObj与Ribbon互相作用的一些应用,如关于电子印章的操作。 敬请期待!

北塔版权所有,转载请注明出处:http:
     

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值