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

功能区的Tab页可以定义各式各样的控件,比如按钮、下拉框、单选框、多选框等。

每个控件都有不同的钩子用来动态定义它的属性,本文演示的仅仅是按钮的各种钩子,其他类型的控件可以以此类推。

Step 1:按钮的显示文字 getLabel

  1. 修改RibbonManifest.xml,将label=""删除,添加getLabel="GetLabel"

    1
    <button id="loginButton" screentip="登录" getLabel="GetLabel" size="large" imageMso="WebPagePreview" onAction="ButtonClicked" />

    这表示显示文字将从GetLabel钩子中获取

  2. 在NativePPTAddin.idl中添加GetLabel的定义

    1
    2
    3
    4
    interface IRibbonCallback : IDispatch {
    [id(0x4000), helpstring("Button Callback")] HRESULT ButtonClicked([in]IDispatch *pControl);
    [id(0x4001), helpstring("GetLabel Callback")] HRESULT GetLabel([in] IDispatch *pControl, [out, retval] BSTR *pbstrReturnedVal);
    };
  3. 在Connect中添加GetLabel钩子的实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    STDMETHODIMP_(HRESULT __stdcall) CConnect::GetLabel(IDispatch * control, BSTR * returnedVal)
    {
    CComQIPtr<IRibbonControl> ribbonCtl(control);
    CComBSTR idStr;
    if (ribbonCtl->get_Id(&idStr) != S_OK)
    return S_FALSE;
    CComBSTR ret;
    if (idStr == OLESTR("loginButton")) {
    ret = OLESTR("登录");
    } else if (idStr == OLESTR("uploadButton")) {
    ret = OLESTR("上传");
    }
    *returnedVal = ret.Detach();
    return S_OK;
    }

    这个钩子的定义在这里可以找到。

Step 2:按钮的是否可见 getVisible

  1. 修改RibbonManifest.xml,添加getVisible="GetVisible"

    1
    2
    3
    4
    5
    6
    7
    8
    <tab id="NativePPTAddinTab" label="Native测试">
    <group id="userGroup" label="用户">
    <button id="loginButton" screentip="登录" getLabel="GetLabel" getVisible="GetVisible" size="large" imageMso="WebPagePreview" onAction="ButtonClicked" />
    </group>
    <group id="actionGroup" label="操作">
    <button id="uploadButton" screentip="上传" getLabel="GetLabel" getVisible="GetVisible" size="large" imageMso="WebPagePreview" onAction="ButtonClicked" />
    </group>
    </tab>
  2. 在NativePPTAddin.idl中添加GetVisible的定义

    1
    2
    3
    4
    5
    interface IRibbonCallback : IDispatch {
    [id(0x4000), helpstring("Button Callback")] HRESULT ButtonClicked([in]IDispatch *pControl);
    [id(0x4001), helpstring("GetLabel Callback")] HRESULT GetLabel([in] IDispatch *pControl, [out, retval] BSTR *pbstrReturnedVal);
    [id(0x4002), helpstring("GetVisible Callback")] HRESULT GetVisible([in] IDispatch *pControl, [out, retval] VARIANT_BOOL *pvarReturnedVal);
    };
  3. 在Connect中添加GetVisible钩子的实现

    我们将登录按钮设为可见,上传按钮设为不可见

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    STDMETHODIMP_(HRESULT __stdcall) CConnect::GetVisible(IDispatch * control, VARIANT_BOOL * returnedVal)
    {
    CComQIPtr<IRibbonControl> ribbonCtl(control);
    CComBSTR idStr;
    if (ribbonCtl->get_Id(&idStr) != S_OK)
    return S_FALSE;
    if (idStr == OLESTR("loginButton")) {
    *returnedVal = VARIANT_TRUE;
    } else if (idStr == OLESTR("uploadButton")) {
    *returnedVal = VARIANT_FALSE;
    }
    return S_OK;
    }
  4. 启动调试,我们将看到上传按钮已经不见了。

Step 3:按钮的图片 image

  1. 通常情况下,我们会将图片添加到资源中

    在资源视图中,右键NativePPTAddin.rc节点,添加资源 ->导入->选择需要的文件。

    导入成功后,资源视图大概长这样:

  2. 修改RibbonManifest.xml,删除之前写的imageMso,添加image=”204” (204是在Resource.h中的登录按钮的资源ID)

    1
    <button id="loginButton" screentip="登录" getLabel="GetLabel" getVisible="GetVisible" image="204" size="large" onAction="ButtonClicked" />
  3. 此时,我们需要实现CustomUI的loadImage钩子才能正常显示图片。

    修改RibbonManifest.xml,添加loadImage钩子。

    1
    <customUI xmlns="http://schemas.microsoft.com/office/2006/01/customui" loadImage="CustomUILoadImage">
  4. 在NativePPTAddin.idl中的IRibbonCallback接口中添加CustomUILoadImage的定义

    1
    [id(0x4004), helpstring("customUI LoadImage Callback")] HRESULT CustomUILoadImage([in] BSTR *pbstrImageId, [out, retval] IPictureDisp ** ppdispImage);
  5. 然后在Connect中实现它,添加一个函数,通过资源ID生成图片

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    HRESULT HrGetImageFromResource(int nId, LPCTSTR lpType, IPictureDisp ** ppdispImage)
    {
    LPVOID pResourceData = NULL;
    DWORD len = 0;
    HRESULT hr = HrGetResource(nId, lpType, &pResourceData, &len);
    if (FAILED(hr)) {
    return E_UNEXPECTED;
    }
    IStream* pStream = nullptr;
    HGLOBAL hGlobal = nullptr;
    // copy image bytes into a real hglobal memory handle
    hGlobal = ::GlobalAlloc(GHND, len);
    if (hGlobal) {
    void* pBuffer = ::GlobalLock(hGlobal);
    if (pBuffer) {
    memcpy(pBuffer, reinterpret_cast<BYTE*>(pResourceData), len);
    HRESULT hr = CreateStreamOnHGlobal(hGlobal, TRUE, &pStream);
    if (SUCCEEDED(hr)) {
    // pStream now owns the global handle and will invoke GlobalFree on release
    hGlobal = nullptr;

    PICTDESC pic;
    memset(&pic, 0, sizeof pic);
    Gdiplus::Bitmap *png = Gdiplus::Bitmap::FromStream(pStream);
    HBITMAP hMap = NULL;
    png->GetHBITMAP(Gdiplus::Color(), &hMap);
    pic.picType = PICTYPE_BITMAP;
    pic.bmp.hbitmap = hMap;

    OleCreatePictureIndirect(&pic, IID_IPictureDisp, true, (LPVOID*)ppdispImage);
    }
    }
    }
    if (pStream) {
    pStream->Release();
    pStream = nullptr;
    }
    if (hGlobal) {
    GlobalFree(hGlobal);
    hGlobal = nullptr;
    }
    return S_OK;
    }
  6. 再实现CustomUILoadImage钩子

    1
    2
    3
    4
    STDMETHODIMP_(HRESULT __stdcall) CConnect::CustomUILoadImage(BSTR * imageId, IPictureDisp ** returnedVal)
    {
    return HrGetImageFromResource(_wtoi(*imageId), TEXT("PNG"), returnedVal);
    }
  7. 启动调试,我们将看到登录按钮的图片已经好了。

Step 4:按钮的图片 getImage

  1. 修改RibbonManifest.xml,上传按钮我们使用getImage钩子

    1
    <button id="uploadButton" screentip="上传" getLabel="GetLabel" getVisible="GetVisible" getImage="GetImage" size="large" onAction="ButtonClicked" />
  2. 在NativePPTAddin.idl中的IRibbonCallback接口中添加GetImage的定义

    1
    [id(0x4003), helpstring("GetImage Callback")] HRESULT GetImage([in] IDispatch *pControl, [out, retval] IPictureDisp ** ppdispImage);
  3. 在资源视图中导入上传按钮

  4. 在Connect类中实现它

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    STDMETHODIMP_(HRESULT __stdcall) CConnect::GetImage(IDispatch * control, IPictureDisp ** returnedVal)
    {
    CComQIPtr<IRibbonControl> ribbonCtl(control);
    CComBSTR idStr;
    if (ribbonCtl->get_Id(&idStr) != S_OK)
    return S_FALSE;
    if (idStr == OLESTR("loginButton")) {
    // do nothing
    // 登录按钮使用image属性定义
    } else if (idStr == OLESTR("uploadButton")) {
    return HrGetImageFromResource(IDB_PNG_UPLOAD, TEXT("PNG"), returnedVal);
    }
    return S_OK;
    }
  5. 看看效果

下一篇我们将演示如何集成DuiLib,点击按钮弹出DuiLib对话框。

完整的代码在这里

Reference