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
4interface 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
15STDMETHODIMP_(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
5interface 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
13STDMETHODIMP_(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
43HRESULT 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
4STDMETHODIMP_(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
14STDMETHODIMP_(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