参考资料:
VSTO添加右键菜单:
VSTO 为Office已有右键菜单添加自己的菜单项(word,Excel)_tianyu0910的博客-CSDN博客
C++使用office类型库添加右键菜单:
在 Office 系列软件中创建 COM 插件工具条,并实现工具条上的弹出菜单 - azhisoft - C++博客
C++实现office插件:
C++开发Office插件:实现Word插件_Cynhard85的博客-CSDN博客_c++ word 插件
Word的COM加载项开发指南:
VSTO自定义UI:
VSTO之旅系列(三):自定义Excel UI - Learning hard - 博客园
VSTO Office二次开发对PPT自定义任务窗格测试 - SanMaoSpace - 博客园
https://www.cnblogs.com/qianyaoyuan/archive/2013/04/13/3018109.html
RibbonX:
https://blog.csdn.net/nodeman/category_7214804.html
捕获office事件:
谈如何捕获OFFICE软件的事件 _wallimn的博客-CSDN博客
开发OFFICE插件总结 - 迷宫の未来的小窝 - C++博客
VSTO捕获PPT事件:
谈如何捕获OFFICE软件的事件 _wallimn的博客-CSDN博客
如何查看office事件的DISPID:
如何查看office事件的DISPID_zero_226的博客-CSDN博客_查看office 的 dispid 大全
1.使用VSTO开发插件
如果在开发前需要验证实现是否达到预期效果,可以使用VSTO进行快速验证。
1.1创建VSTO工程
确保已经在Visual Studio installer中安装Office/SharePoint开发
也可以直接打开下面工程,编译后按F5运行
功能如下:
2.使用类型库实现PPT的交互
2.1向指定位置插入幻灯片
其他功能与上述流程差异不大,在PptMgr中有两个公有的函数InsertPicture和InsertSlide,直接使用即可,如果要实现插入文字,可参考压缩包的工程和InsertPicture流程
3.插入右键菜单项
在插入菜单项前,首先要知道目标菜单的名称,如果不清楚,可以使用以下代码,功能是对所有菜单插入一个以自身菜单名命名的菜单项,这是比较旧以前写的代码,AddBackMenuBarButton函数与现在的有点差异,但逻辑没变。
C++ void CKisOfficeAddin::SaveCommandbars(IDispatch * Application) { ...... switch(emComponent) { case enum_msoffice_component_type__word: { ...... int nCommandbarsCount = 0; m_spCommandbars->get_Count(&nCommandbarsCount); for(int i =1;i <= nCommandbarsCount;i++) { CComVariant vParam(i); CComPtr<Office::CommandBar> spBar = NULL; m_spCommandbars->get_Item(vParam, &spBar); BSTR pStrBarName = NULL; spBar->get_Name(&pStrBarName); CString strBarName((LPCTSTR)pStrBarName); BSTR MenuName = strBarName.AllocSysString(); BOOL bResCode = AddBackMenuBarButton(MenuName, MenuName, MenuName, IDB_BMP_TEMPLATE_MENU_ITEM, IDB_BMP_TEMPLATE_MENU_MASK, m_spBarButtonThumbnails); } ...... } break; ......
|
插入右键菜单项的逻辑主要在AddBackMenuBarButton函数中,在插入前需要调用ResetMenubar来清除菜单项,如果不清除的话,下次启动office会发现菜单中存在重复添加的菜单项。
说明一下为何需要设置屏蔽图,因添加的图片只能是bmp格式的,而bmp不支持透明,所以如果需要实现图片空白区域是透明的话,就需要设置屏蔽图,屏蔽图黑色区域为要显示的部分,白色区域是透明部分
4.监听office事件
4.1.为添加的菜单项注册点击事件
1.在.cpp中定义回调函数信息
CC_STDCALL 函数调用约定参数从右向左压入堆栈
VT_EMPTY指回调函数返回值为空
2表示有两个参数
{VT_DISPATCH,VT_BYREF | VT_BOOL}表示第一个参数类型是IDispatch*,第二个参数类型是VARIANT_BOOL*
_ATL_FUNC_INFO 说明:
https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2012/c5ks5t9w(v=vs.110)
C++ _ATL_FUNC_INFO OnClickBarButtonWordTextInfo = {CC_STDCALL,VT_EMPTY,2,{VT_DISPATCH,VT_BYREF | VT_BOOL}}; |
2.在.h中继承IDispEventSimpleImpl,并设置事件映射
IDispEventSimpleImpl模板中的第一个参数是事件唯一标识符,用户自定义的
BUTTON_CLICK_DISPID为事件的DISID,这里要特别说明的是,word事件中的startUp它的DISID也是0x01
C++ #define MAPLINK_ID_BAR_EVENS_BASE 10 #define BUTTON_CLICK_DISPID 0x01 class IKisOfficeBarBtnEvent: public IDispEventSimpleImpl<MAPLINK_ID_BAR_EVENS_BASE + 1,IKisOfficeBarBtnEvent, &__uuidof(Office::_CommandBarButtonEvents)> { public: typedef IDispEventSimpleImpl<MAPLINK_ID_BAR_EVENS_BASE + 1,IKisOfficeBarBtnEvent, &__uuidof(Office::_CommandBarButtonEvents)> CommandBarButtonEventsWordText;
//回调函数 virtual void __stdcall OnClickBarButtonWordText(IDispatch * /*Office::_CommandBarButton**/ ctrl, VARIANT_BOOL * cancelDefault) = 0; //事件与回调函数的映射 BEGIN_SINK_MAP(IKisOfficeBarBtnEvent) SINK_ENTRY_INFO(MAPLINK_ID_BAR_EVENS_BASE + 1,__uuidof(Office::_CommandBarButtonEvents),/*dispid*/ BUTTON_CLICK_DISPID, OnClickBarButtonWordText, &OnClickBarButtonWordTextInfo) END_SINK_MAP() } |
3.在.cpp中对事件进行注册和反注册,并在invoke中添加代码
m_spBarButtonWordTextSearch是调用完AddBackMenuBarButton后返回的_CommandBarButton
C++ //注册事件,在AddBackMenuBarButton后调用 IKisOfficeBarBtnEvent::CommandBarButtonEventsWordText::DispEventAdvise((IDispatch*)m_spBarButtonWordTextSearch); //反注册,在OnDisconnection中调用 IKisOfficeBarBtnEvent::CommandBarButtonEventsWordText::DispEventUnadvise((IDispatch*)m_spBarButtonWordTextSearch); |
C++ HRESULT __stdcall CKisOfficeAddin::Invoke( DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr ) { ...... if(m_wordApp) { static BOOL bStartUpEvent = FALSE; //word启动时会触发Startup事件,其dispid也是1,这里防止与按钮事件冲突 switch(dispIdMember) { case BUTTON_CLICK_DISPID: { if(bStartUpEvent) { return IKisOfficeBarBtnEvent::CommandBarButtonEventsWordText::Invoke(dispIdMember, riid, lcid, wFlags, pDispParams, pVarResult, pExcepInfo, puArgErr); } bStartUpEvent = TRUE; } break; ...... }
...... } |
完成以上三步,点击菜单按钮便会调用OnClickBarButtonWordText函数。
4.2监听office自身事件
基本步骤跟按钮一致,需要注意的点是每个事件对应的DISID都不一样,可以使用以下方式查看
https://blog.csdn.net/zero_226/article/details/7289934
如果需要对word监听多个事件,那么只需要注册一次即可,假如对每一个事件都执行一次注册,那么触发事件时会调用多次invoke,原因不明
C++ //假如需要监听4个事件 class IKisOfficeWordEvent: public IDispEventSimpleImpl<MAPLINK_ID_WORD_EVENS_BASE, IKisOfficeWordEvent, &__uuidof(Word::ApplicationEvents4)>, public IDispEventSimpleImpl<MAPLINK_ID_WORD_EVENS_BASE + 1,IKisOfficeWordEvent, &__uuidof(Word::ApplicationEvents4)>, public IDispEventSimpleImpl<MAPLINK_ID_WORD_EVENS_BASE + 2,IKisOfficeWordEvent, &__uuidof(Word::ApplicationEvents4)>, public IDispEventSimpleImpl<MAPLINK_ID_WORD_EVENS_BASE + 3,IKisOfficeWordEvent, &__uuidof(Word::ApplicationEvents4)>, public IDispEventSimpleImpl<MAPLINK_ID_WORD_EVENS_BASE + 4,IKisOfficeWordEvent, &__uuidof(Word::ApplicationEvents4)> { public: typedef IDispEventSimpleImpl<MAPLINK_ID_WORD_EVENS_BASE,IKisOfficeWordEvent, &__uuidof(Word::ApplicationEvents4)> WordEventBase; typedef IDispEventSimpleImpl<MAPLINK_ID_WORD_EVENS_BASE + 1,IKisOfficeWordEvent, &__uuidof(Word::ApplicationEvents4)> WordWindowActivateEvent; typedef IDispEventSimpleImpl<MAPLINK_ID_WORD_EVENS_BASE + 2,IKisOfficeWordEvent, &__uuidof(Word::ApplicationEvents4)> WordDocumentBeforeCloseEvent; typedef IDispEventSimpleImpl<MAPLINK_ID_WORD_EVENS_BASE + 3,IKisOfficeWordEvent, &__uuidof(Word::ApplicationEvents4)> WordRightClickEvents; typedef IDispEventSimpleImpl<MAPLINK_ID_WORD_EVENS_BASE + 4,IKisOfficeWordEvent, &__uuidof(Word::ApplicationEvents4)> WordDocumentOpenEvent; ...... } //只注册一次即可 IKisOfficeWordEvent::WordEventBase::DispEventAdvise((IDispatch*)m_wordApp);
//如果对每一个事件都注册,则某一个事件触发时,invoke会调用4次 IKisOfficeWordEvent::WordWindowActivateEvent::DispEventAdvise((IDispatch*)m_wordApp); IKisOfficeWordEvent::WordDocumentBeforeCloseEvent::DispEventAdvise((IDispatch*)m_wordApp); IKisOfficeWordEvent::WordRightClickEvents::DispEventAdvise((IDispatch*)m_wordApp); IKisOfficeWordEvent::WordDocumentOpenEvent::DispEventAdvise((IDispatch*)m_wordApp); |
5.使用RibbonX添加右键菜单项
因PPT选中图片的右键菜单使用遍历的方式无法找到它的菜单名,预研发现可以通过RibbonX添加右键菜单项,但是只是对office2010及以上的版本适用
参考资料如下:
其中officeTool目录下的文件记录了所有菜单idMso
下面演示如何使用工具OfficeCustomUIEditorFiles快速验证RibbonX,并实现插入菜单项功能
1.在安装目录下创建ppt文件
2.运行CustomUIEditor.exe,并打开对应ppt文件
3.选择2010版本
4.在出现的.xml上添加以下内容
HTML <customUI xmlns="http://schemas.microsoft.com/office/2009/07/customui"> <contextMenus> <contextMenu idMso="ContextMenuTextEdit"> <button id="customButton" label="myMenuItem" onAction="OnChild" /> </contextMenu> </contextMenus> </customUI> |
5.点击第二个红框Validate进行验证,通过会出现以下提示,记得保存
6.打开新创建的ppt文件myPPT.pptx,便会看到新添加的菜单项
注意事项:如果Insert选择了2007,则要使用以下命名空间,并且不能使用contextMenus,否则点击Validate会报错
C++ <customUI xmlns="http://schemas.microsoft.com/office/2006/01/customui"> |
为何最终没有使用这个方法?
因为当前使用的RibbonX中有用到<officeMenu>,这个控件在2009/06的命名空间中没有,那就表示不支持<officeMenu>,为了兼容以前的版本,就没有使用<contextMenus>进行菜单项插入
如何查找菜单的idMso?
例如上面用到的ContextMenuTextEdit
解压PowerPoint幻灯片右键添加菜单.rar,在officeTool下找到PowerPointControls.xlsx