C++编写PowerPoint插件(二):功能区添加Tab页以及按钮

本系列描述的是如何使用C++/COM来编写PowerPoint插件,使用的开发工具是 Visual Studio 2017。

Step 1:清理 pch.h

pch.h是预编译头文件,如果你在更老的Visual Studio版本上进行开发,这个文件可能是StdAfx.h

  1. 删除pch.h中原来的import语句,原来的import语句长这样

    #import "C:\Program Files (x86)\Common Files\Designer\MSADDNDR.DLL" raw_interfaces_only, raw_native_types, no_namespace, named_guids, auto_search
    
  2. 加入下面的代码

    #import "libid:AC0714F2-3D04-11D1-AE7D-00A0C90F26F4" \
    auto_rename auto_search raw_interfaces_only rename_namespace("AddinDesign")
    
    // Office type library (i.e., mso.dll).
    #import "libid:2DF8D04C-5BFA-101B-BDE5-00AA0044DE52" \
        auto_rename auto_search raw_interfaces_only rename_namespace("Office")
    using namespace AddinDesign;
    using namespace Office;
    

Step 2:修改 Connect.h,实现IRibbonExtensibility接口

  1. 我们添加一些typedef来简化代码的编写,再加上IRibbonExtensibility接口的实现

    大概长这样:

    typedef IDispatchImpl<_IDTExtensibility2, &__uuidof(_IDTExtensibility2), &__uuidof(__AddInDesignerObjects), /* wMajor = */ 1>
    IDTImpl;
    
    typedef IDispatchImpl<IRibbonExtensibility, &__uuidof(IRibbonExtensibility), &__uuidof(__Office), /* wMajor = */ 2, /* wMinor = */ 5>
    RibbonImpl;
    
    // CConnect
    
    class ATL_NO_VTABLE CConnect :
     public CComObjectRootEx<CComSingleThreadModel>,
     public CComCoClass<CConnect, &CLSID_Connect>,
     public IDispatchImpl<IConnect, &IID_IConnect, &LIBID_NativePPTAddinLib, /*wMajor =*/ 1, /*wMinor =*/ 0>,
        public IDTImpl,
        public RibbonImpl
    
  2. 添加下面的代码到 ATL COM MAP

    COM_INTERFACE_ENTRY(IRibbonExtensibility)
    
  3. 实现IRibbonExtensibility接口

    STDMETHOD(GetCustomUI)(BSTR RibbonID, BSTR * RibbonXml)
    {
        if(!RibbonXml)
            return E_POINTER;
        *RibbonXml = CComBSTR("XML GOES HERE");  
        return S_OK;
    }
    

    这个接口的输出参数RibbonXml是用来定义功能区Tab页的UI,格式是XML。

Step 3:添加XML到工程

  1. 右键NativePPTAddin工程,添加->新建项,选择XML
build-cplusplus-addin-for-ppt-9.png
  1. 打开刚刚添加的RibbonManifest.xml,输入以下内容

    <?xml version="1.0" encoding="gb2312"?>
    <customUI xmlns="http://schemas.microsoft.com/office/2006/01/customui">
      <ribbon>
        <tabs>
          <tab id="NativePPTAddinTab" label="Native测试">
            <group id="userGroup" label="用户">
              <button id="loginButton" screentip="登录" label="登录" size="large" imageMso="WebPagePreview" onAction="ButtonClicked" />
            </group>
            <group id="actionGroup" label="操作">
              <button id="uploadButton" screentip="上传" label="上传" size="large" imageMso="WebPagePreview" onAction="ButtonClicked" />
            </group>
          </tab>
        </tabs>
      </ribbon>
    </customUI>
    

    在此XML中,我们定义了两个组(用户和操作),在每个组中都有一个按钮。

Step 4:实现GetCustomUI接口

  1. 在实现之前,我们先将Connect.h中函数定义的位置转移到Connect.cpp。(光标移到函数名,Alt+Enter -> 移动定义位置)

  2. 切换到资源视图,展开NativePPTAddin节点,在NativePPTAddin.rc节点右键,添加资源

  3. 在弹出的对话框中选择导入

  4. 在文件选择对话框中,将文件类型改为所有文件。然后选择刚刚创建的RibbonManifest.xml

  5. 此时会弹出自定义资源类型对话框,输入XML

build-cplusplus-addin-for-ppt-10.png
  1. 添加下面的代码到Connect.cpp用来处理XML文件

    namespace {
    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;
    }
    } // End of anonymous namespace
    
  2. 修改GetCustomUI函数的实现

    STDMETHODIMP_(HRESULT __stdcall) CConnect::GetCustomUI(BSTR RibbonID, BSTR * RibbonXml)
    {
        if (!RibbonXml)
            return E_POINTER;
        *RibbonXml = GetXMLResource(IDR_XML1);
        return S_OK;
    }
    
  3. 验证一下。启动调试我们将看到功能区有我们新加的Tab页。

build-cplusplus-addin-for-ppt-11.png

Step 5:实现按钮点击事件

  1. 打开 NativePPTAddin.idl 文件

    IDL是用来描述软件组件接口的一种计算机语言。IDL通过一种独立于编程语言的方式来描述接口,使得在不同平台上运行的对象和用不同语言编写的程序可以相互通信交流;比如,一个组件用C++写成,另一个组件用Java写成。

  2. 添加接口。代码大概长这样

    [
        object,
        uuid(CE895442-9981-4315-AA85-4B9A5C7739D8),
        dual,
        nonextensible,
        helpstring("IRibbonCallback Interface"),
        pointer_default(unique)
    ]
    interface IRibbonCallback : IDispatch {
        [id(0x4000), helpstring("Button Callback")] HRESULT ButtonClicked([in]IDispatch* pControl);
    };
    
  3. 修改NativePPTAddin.idl中Connect的定义为

    coclass Connect
    {
     interface IConnect;
     [default] interface IRibbonCallback;
    };
    
  4. 实现IRibbonCallback接口

    在Connect.h中添加如下typedef

    typedef IDispatchImpl<IRibbonCallback, &__uuidof(IRibbonCallback)>
    CallbackImpl;
    

    同时修改Connect类的继承关系

    class ATL_NO_VTABLE CConnect :
     public CComObjectRootEx<CComSingleThreadModel>,
     public CComCoClass<CConnect, &CLSID_Connect>,
     public IDispatchImpl<IConnect, &IID_IConnect, &LIBID_NativePPTAddinLib, /*wMajor =*/ 1, /*wMinor =*/ 0>,
        public IDTImpl,
        public RibbonImpl,
        public CallbackImpl
    

    添加接口到ATL COM MAP

    BEGIN_COM_MAP(CConnect)
        COM_INTERFACE_ENTRY2(IDispatch, IRibbonCallback)
     COM_INTERFACE_ENTRY(IConnect)
     COM_INTERFACE_ENTRY2(IDispatch, _IDTExtensibility2)
        COM_INTERFACE_ENTRY(_IDTExtensibility2)
        COM_INTERFACE_ENTRY(IRibbonExtensibility)
        COM_INTERFACE_ENTRY(IRibbonCallback)
    END_COM_MAP()
    
  5. 实现ButtonClicked接口

    STDMETHODIMP_(HRESULT __stdcall) CConnect::ButtonClicked(IDispatch * ribbon)
    {
        MessageBoxW(NULL, L"Button Clicked!", L"NativePPTAddin", MB_OK);
        return S_OK;
    }
    
  6. 验证一下。启动调试后我们点击按钮,将会弹出消息框。

build-cplusplus-addin-for-ppt-12.png
  1. 修改一下ButtonClicked函数,实现不同按钮点击弹出不同的消息框。

    STDMETHODIMP_(HRESULT __stdcall) CConnect::ButtonClicked(IDispatch * control)
    {
        CComQIPtr<IRibbonControl> ribbonCtl(control);
        CComBSTR idStr;
        WCHAR msg[64];
        if (ribbonCtl->get_Id(&idStr) != S_OK)
            return S_FALSE;
        if (idStr == OLESTR("loginButton")) {
            swprintf_s(msg, L"I am loginButton");
        } else if (idStr == OLESTR("uploadButton")) {
            swprintf_s(msg, L"I am uploadButton");
        }
        MessageBoxW(NULL, msg, L"NativePPTAddin", MB_OK);
        return S_OK;
    }
    

下一篇,我们将描述如何使用其他钩子来实现按钮的可见性、图片、显示文字等功能。

完整的代码在这里

Reference

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容