C++编写PowerPoint插件(三):按钮的各种钩子
本系列描述的是如何使用C++/COM来编写PowerPoint插件,使用的开发工具是 Visual Studio 2017。
功能区的Tab页可以定义各式各样的控件,比如按钮、下拉框、单选框、多选框等。
每个控件都有不同的钩子用来动态定义它的属性,本文演示的仅仅是按钮的各种钩子,其他类型的控件可以以此类推。
Step 1:按钮的显示文字 getLabel
- 修改RibbonManifest.xml,将 - label=""删除,添加- getLabel="GetLabel"- 1 - <button id="loginButton" screentip="登录" getLabel="GetLabel" size="large" imageMso="WebPagePreview" onAction="ButtonClicked" /> - 这表示显示文字将从GetLabel钩子中获取 
- 在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);
 };
- 在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
- 修改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>
- 在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);
 };
- 在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;
 }
- 启动调试,我们将看到上传按钮已经不见了。  
Step 3:按钮的图片 image
- 通常情况下,我们会将图片添加到资源中 - 在资源视图中,右键NativePPTAddin.rc节点,添加资源 ->导入->选择需要的文件。 - 导入成功后,资源视图大概长这样:  
- 修改RibbonManifest.xml,删除之前写的imageMso,添加image=”204” (204是在Resource.h中的登录按钮的资源ID) - 1 - <button id="loginButton" screentip="登录" getLabel="GetLabel" getVisible="GetVisible" image="204" size="large" onAction="ButtonClicked" /> 
- 此时,我们需要实现CustomUI的loadImage钩子才能正常显示图片。 - 修改RibbonManifest.xml,添加loadImage钩子。 - 1 - <customUI xmlns="http://schemas.microsoft.com/office/2006/01/customui" loadImage="CustomUILoadImage"> 
- 在NativePPTAddin.idl中的IRibbonCallback接口中添加CustomUILoadImage的定义 - 1 - [id(0x4004), helpstring("customUI LoadImage Callback")] HRESULT CustomUILoadImage([in] BSTR *pbstrImageId, [out, retval] IPictureDisp ** ppdispImage); 
- 然后在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;
 }
- 再实现CustomUILoadImage钩子 - 1 
 2
 3
 4- STDMETHODIMP_(HRESULT __stdcall) CConnect::CustomUILoadImage(BSTR * imageId, IPictureDisp ** returnedVal) 
 {
 return HrGetImageFromResource(_wtoi(*imageId), TEXT("PNG"), returnedVal);
 }
- 启动调试,我们将看到登录按钮的图片已经好了。  
Step 4:按钮的图片 getImage
- 修改RibbonManifest.xml,上传按钮我们使用getImage钩子 - 1 - <button id="uploadButton" screentip="上传" getLabel="GetLabel" getVisible="GetVisible" getImage="GetImage" size="large" onAction="ButtonClicked" /> 
- 在NativePPTAddin.idl中的IRibbonCallback接口中添加GetImage的定义 - 1 - [id(0x4003), helpstring("GetImage Callback")] HRESULT GetImage([in] IDispatch *pControl, [out, retval] IPictureDisp ** ppdispImage); 
- 在资源视图中导入上传按钮  
- 在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;
 }
- 看看效果  
下一篇我们将演示如何集成DuiLib,点击按钮弹出DuiLib对话框。
完整的代码在这里。
Reference
- https://docs.microsoft.com/en-us/previous-versions/office/developer/office-2010/ee941475(v=office.14)
- https://docs.microsoft.com/en-us/previous-versions/office/developer/office-2010/ee691833(v=office.14)
- https://vimsky.com/zh-tw/examples/detail/cpp-ex---CComBSTR---class.html
- https://stackoverflow.com/questions/66237978/c-gdi-how-to-get-and-load-image-from-resource