【转】【学习笔记】《北塔教你做插件 从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问题转  这里

原文地址:https://www.cnblogs.com/nightnine/p/12619095.html