前兩天,在CSDN瞎逛悠,見一老兄問到此問,卻沒有人作答(頂的人倒還不少,國內的論壇是不是都這樣?),還發了些牢騷,俺也順便跟著發了點牢騷:)
于是坐下來靜下心研究了一下,今日終于成了正果,不敢私吞成果,特搬弄出來,讓大家分享分享(切,無非就是虛榮而已啦,把自己說得那么偉大?!)!
點擊這里下載工程源代碼
我看還是做一篇教程寫好了,寫清楚一點,呵呵:)
哦,先說明白,俺用的是VC6啊(俺的工程是以Outlook插件為例的,因為CSDN上那位老兄問的就是Outlook,所以咱就對癥下藥啦,不過Office系列的插件都大同小異啦,只是做具體功能時才會天差地別哦):
1. 新建ATL工程,全部默認,完成Wizard。
2. 新建ATL Object,選擇Simple Object,其他默認。
3. 打開stdafx.h,這里需要導入幾個庫:
#import "C:\Documents and Settings\Administrator\My Documents\msoffice9\mso9.dll" rename_namespace("Office")
using namespace Office;
#import "C:\Program Files\Common Files\Microsoft Shared\VBA\VBA6\VBE6EXT.OLB" rename_namespace("VBE6")
using namespace VBE6;
#import "C:\Documents and Settings\Administrator\My Documents\msoffice9\MSOUTL9.OLB" named_guids,rename_namespace("MSOutlook")
using namespace MSOutlook;
4. 打開.rgs文件,需要添加幾行注冊腳本:
HKCU
{
NoRemove Software
{
NoRemove Microsoft
{
NoRemove Office
{
NoRemove Outlook
{
NoRemove Addins
{
'OutlookAddin.COutlookAddinSample'
{
val FriendlyName = s 'Azhi sample'
val Description = s 'Azhi sample'
val LoadBehavior = d '00000003'
val CommandLineSafe = d '00000000'
}
}
}
}
}
}
}
這里注意了:OutlookAddin.COutlookAddinSample這玩意是該COM的ProgID。
5. 現在打開COutlookAddinSample.h文件。
a. 先引入一個庫:
#import "C:\Program Files\Common Files\Designer\MSADDNDR.DLL" raw_interfaces_only, raw_native_types, no_namespace, named_guids
b. 在接口(類)定義中添加對_IDTExtensibility2接口的支持:
class ATL_NO_VTABLE CCOutlookAddinSample :
.
.
public IDispatchImpl<_IDTExtensibility2, &IID__IDTExtensibility2, &LIBID_AddInDesignerObjects>
.
c. 添加基于_IDTExtensibility2接口方法的重載:
.
// _IDTExtensibility2
public:
STDMETHOD(OnConnection)(IDispatch * Application, ext_ConnectMode ConnectMode, IDispatch * AddInInst, SAFEARRAY * * custom);
STDMETHOD(OnDisconnection)(ext_DisconnectMode RemoveMode, SAFEARRAY * * custom);
STDMETHOD(OnAddInsUpdate)(SAFEARRAY * * custom);
STDMETHOD(OnStartupComplete)(SAFEARRAY * * custom);
STDMETHOD(OnBeginShutdown)(SAFEARRAY * * custom);
.
.
d. 定義一個變量保存Outlook的Application實例:
private:
CComPtr<MSOutlook::_Application> m_spApp;
e. 修改COM_MAP映射表:
.
// COM_INTERFACE_ENTRY(IDispatch) // 刪除此行,以下兩行皆為新添加的
COM_INTERFACE_ENTRY2(IDispatch, ICOutlookAddinSample)
COM_INTERFACE_ENTRY(_IDTExtensibility2)
.
.
至此,COutlookAddinSample.h文件的第一輪修改已經完成。
好了,我們接著改(為我們的COM按鈕添加事件響應):
f. 回到文件頂部,添加申明:
extern _ATL_FUNC_INFO OnClickButtonInfo1;
extern _ATL_FUNC_INFO OnClickButtonInfo2;
g. 回到類定義處,添加對按鈕事件的支持:
class ATL_NO_VTABLE CCOutlookAddinSample :
.
.
public IDispEventSimpleImpl<1,CCOutlookAddinSample,&__uuidof(Office::_CommandBarButtonEvents)>,
public IDispEventSimpleImpl<2,CCOutlookAddinSample,&__uuidof(Office::_CommandBarButtonEvents)>
.
h. 在類中添加類型定義:
typedef IDispEventSimpleImpl<1,CCOutlookAddinSample, &__uuidof(Office::_CommandBarButtonEvents)> CommandButtonEvents1;
typedef IDispEventSimpleImpl<2,CCOutlookAddinSample, &__uuidof(Office::_CommandBarButtonEvents)> CommandButtonEvents2;
i. 添加事件連接映射:
BEGIN_SINK_MAP(CCOutlookAddinSample)
SINK_ENTRY_INFO(1,__uuidof(Office::_CommandBarButtonEvents),/*dispid*/ 0x01, OnClickButton1, &OnClickButtonInfo1)
SINK_ENTRY_INFO(2,__uuidof(Office::_CommandBarButtonEvents),/*dispid*/ 0x01, OnClickButton2, &OnClickButtonInfo2)
END_SINK_MAP()
j. 添加兩個方法以響應按鈕事件:
// ICOutlookAddinSample
public:
VOID __stdcall OnClickButton1(IDispatch * /*Office::_CommandBarButton**/ Ctrl,VARIANT_BOOL * CancelDefault);
VOID __stdcall OnClickButton2(IDispatch * /*Office::_CommandBarButton**/ Ctrl,VARIANT_BOOL * CancelDefault);
k. 定義兩個變量保存按鈕實例:
private:
.
.
CComPtr<Office::_CommandBarButton> m_spButton1;
CComPtr<Office::_CommandBarButton> m_spButton2;
.
恩,至此該文件就全部修改完畢了(別得意哦,下面還有很多事要做啊)。
6. 打開COutlookAddinSample.cpp文件。
a. 定義事件連接變量:
_ATL_FUNC_INFO OnClickButtonInfo1 = {CC_STDCALL,VT_EMPTY,2,{VT_DISPATCH,VT_BYREF | VT_BOOL}};
_ATL_FUNC_INFO OnClickButtonInfo2 = {CC_STDCALL,VT_EMPTY,2,{VT_DISPATCH,VT_BYREF | VT_BOOL}};
b. 添加按鈕事件響應方法:
VOID __stdcall CCOutlookAddinSample::OnClickButton1(IDispatch* /*Office::_CommandBarButton* */ Ctrl,VARIANT_BOOL * CancelDefault)
{
MessageBox(GetForegroundWindow(),"¹þ¹þ£¬²âÊÔ°´Å¥£¡","sample",MB_OK | MB_ICONINFORMATION);
}
VOID __stdcall CCOutlookAddinSample::OnClickButton2(IDispatch* /*Office::_CommandBarButton* */ Ctrl,VARIANT_BOOL * CancelDefault)
{
MessageBox(GetForegroundWindow(),"¹þ¹þ£¬²âÊÔµ¯³ö²Ëµ¥£¡","sample",MB_OK | MB_ICONINFORMATION);
}
c. 添加繼承自_IDTExtensibility2接口的方法:
STDMETHODIMP CCOutlookAddinSample::OnConnection(IDispatch * Application, ext_ConnectMode ConnectMode, IDispatch * AddInInst, SAFEARRAY * * custom)
{
return S_OK;
}
STDMETHODIMP CCOutlookAddinSample::OnDisconnection(ext_DisconnectMode RemoveMode, SAFEARRAY * * custom)
{
return S_OK;
}
STDMETHODIMP CCOutlookAddinSample::OnAddInsUpdate(SAFEARRAY * * custom)
{
return E_NOTIMPL;
}
STDMETHODIMP CCOutlookAddinSample::OnStartupComplete(SAFEARRAY * * custom)
{
return E_NOTIMPL;
}
STDMETHODIMP CCOutlookAddinSample::OnBeginShutdown(SAFEARRAY * * custom)
{
return E_NOTIMPL;
}
休息一下,到這里,這個插件的基本框架已經搭建完畢。
以下我們接著完成按鈕和彈出菜單的創建以及按鈕事件到方法的連接:
e. 在OnConnection方法中首先獲得CommandBars接口指針
.
CComQIPtr<MSOutlook::_Application> spApp(Application);
ATLASSERT(spApp);
CComPtr<MSOutlook::_Explorer> spExplorer;
spExplorer = spApp->ActiveExplorer();
ATLASSERT(spExplorer);
CComPtr<Office::_CommandBars> spCmdBars;
HRESULT hr = spExplorer->get_CommandBars(&spCmdBars);
.
.
在Outlook中比Word以及Excel都要多用一個方法才能得到CommandBars。
在Word以及Excel中直接可以通過Application對象獲得:spApp->get_CommandBars(&spCmdBars);
f. 接著創建自己的CommandBar:
CComVariant vName("sample");
CComVariant vPos(Office::msoBarTop);
CComVariant vTemp(VARIANT_TRUE);
CComVariant vEmpty(DISP_E_PARAMNOTFOUND,VT_ERROR);
CComPtr<Office::CommandBar> spNewCmdBar;
spNewCmdBar = spCmdBars->Add(vName, vPos, vEmpty, vTemp);
// 得到CommandBar的Controls集合,通過集合的Add方法添加自己的按鈕以及其他
CComPtr<Office::CommandBarControls> spBarControls;
spBarControls = spNewCmdBar->GetControls();
ATLASSERT(spBarControls);
g. 為自己的CommandBar添加按鈕:
CComVariant vToolBarType(Office::msoControlButton);
CComVariant vShow(VARIANT_TRUE);
CComPtr<Office::CommandBarControl> spNewBar;
spNewBar = spBarControls->Add(vToolBarType, vEmpty, vEmpty, vEmpty, vShow);
ATLASSERT(spNewBar);
CComQIPtr<Office::_CommandBarButton> spCmdButton1(spNewBar);
ATLASSERT(spCmdButton1);
spCmdButton1->PutStyle(Office::msoButtonIconAndCaption);
spCmdButton1->PutVisible(VARIANT_TRUE);
spCmdButton1->PutCaption("sample1");
spCmdButton1->PutEnabled(VARIANT_TRUE);
spCmdButton1->PutTooltipText("sample1");
spCmdButton1->PutTag("sample1");
HBITMAP hBmp = (HBITMAP)LoadImage(_Module.GetResourceInstance(),MAKEINTRESOURCE(IDB_SAMPLE),IMAGE_BITMAP,0,0,LR_LOADMAP3DCOLORS);
::OpenClipboard(NULL);
::EmptyClipboard();
::SetClipboardData(CF_BITMAP,(HANDLE)hBmp);
::CloseClipboard();
::DeleteObject(hBmp);
hr = spCmdButton1->PasteFace();
if(FAILED(hr))return hr;
h. 好了,我們就要創建彈出菜單了,看好了:
// 首先我們需要創建一個Popup類型的CommandBarButton
// 實際上微軟稱他為:CommandBarPopup
CComVariant vPopupType(Office::msoControlPopup);
spNewBar = spBarControls->Add(vPopupType, vEmpty, vEmpty, vEmpty, vShow);
ATLASSERT(spNewBar);
CComQIPtr<Office::CommandBarPopup> spNewPopup(spNewBar);
ATLASSERT(spNewPopup);
spNewPopup->PutVisible(VARIANT_TRUE);
spNewPopup->PutCaption("popup");
spNewPopup->PutEnabled(VARIANT_TRUE);
spNewPopup->PutTooltipText("popup");
spNewPopup->PutTag("popup");
// 同樣可以有自己的Controls集合
// 以后往該集合中添加的按鈕都是這個Popup中的一個菜單項啦
CComPtr<Office::CommandBarControls> spPopupControls;
spPopupControls = spNewPopup->GetControls();
ATLASSERT(spPopupControls);
i. 恩,創建一個菜單項試試:
spNewBar = spPopupControls->Add(vToolBarType, vEmpty, vEmpty, vEmpty, vShow);
ATLASSERT(spNewBar);
CComQIPtr<Office::_CommandBarButton> spCmdButton2(spNewBar);
ATLASSERT(spCmdButton2);
spCmdButton2->PutStyle(Office::msoButtonIconAndCaption);
spCmdButton2->PutVisible(VARIANT_TRUE);
spCmdButton2->PutCaption("sample2");
spCmdButton2->PutEnabled(VARIANT_TRUE);
spCmdButton2->PutTooltipText("sample2");
spCmdButton2->PutTag("sample2");
hBmp = (HBITMAP)LoadImage(_Module.GetResourceInstance(),MAKEINTRESOURCE(IDB_SAMPLE),IMAGE_BITMAP,0,0,LR_LOADMAP3DCOLORS);
::OpenClipboard(NULL);
::EmptyClipboard();
::SetClipboardData(CF_BITMAP,(HANDLE)hBmp);
::CloseClipboard();
::DeleteObject(hBmp);
hr = spCmdButton2->PasteFace();
if(FAILED(hr))return hr;
j. 現在可以保存變量以及進行事件連接了:
m_spApp = spApp;
m_spButton1 = spCmdButton1;
hr = CommandButtonEvents1::DispEventAdvise((IDispatch*)m_spButton1);
if(FAILED(hr))return hr;
m_spButton2 = spCmdButton2;
hr = CommandButtonEvents2::DispEventAdvise((IDispatch*)m_spButton2);
if(FAILED(hr))return hr;
到這里,OnConnection方法就已經完成了:)
7. 在OnDisconnection方法中斷開事件的連接:
HRESULT hr = CommandButtonEvents1::DispEventUnadvise((IDispatch*)m_spButton1);
if(FAILED(hr))return hr;
hr = CommandButtonEvents2::DispEventUnadvise((IDispatch*)m_spButton2);
if(FAILED(hr))return hr;
好了,終于又完成一篇文章了。
寫的很累啊,即使是像我這樣CP,也覺得很累啊(老了?!)!
呵呵,也不知道寫的是不是清楚,或許大家看起來很覺得不知所然吧?
唉,文學細胞太少,看樣子不是做文學的料啊,實在不明白的還是請大家讀我的工程源代碼吧:)
原始工程編譯之后LINK失?。?
你的代碼是針對office 2000的(mso9.dll、msoutl9.olb)
我使用OFFICE XP,修改了這兩個文件,但錯誤是
LIBCMT.lib(crt0.obj) : error LNK2001: unresolved external symbol _main
看起來和我的修改無關
我已經測試過了,程序在Windows2000+OfficeXP環境下修改的,運行良好。
BTW,如果是新建工程把代碼CP進去,并且將程序編譯成Release模式的話,則需要修改:
在“工程|設置|C/C++”中將“_ATL_MIN_CRT”宏定義去除即可。
肯定是你們沒仔細,把事件連接錯了。
沒做過這玩意啊,不過感覺挺簡單的啊
對話框可以用非模式對話框,設置成最頂層
鼠標位置則可以使用GetCursorPos函數得到
至于你說的鼠標點擊播放畫面的事件,應該在SDK里有Sample吧