Office2000下内部COM插件的编程实现(之二)

    技术2022-05-11  128

     

    在Office应用程序中,尽管菜单和工具栏按钮看上去不太一样,但实质上它们是相同类型的对象。CommandBars集合包含程序中的所有命令条,如:工具条和菜单条。每一个CommandBars集合都有一个CommandBar对象和它对应,CommandBar 对象可以包含其它的 CommandBar 对象,这些对象是作为按钮或菜单命令来用的。每一个CommandBar都将通过CommandBarControls 对象被引用,CommandBarControls又可以包含一组CommandBarControl对象。每一个CommandBarControl可以包含一个CommandBar对象,并可以通过它来存取控件属性。每一个CommandBarControl对象,实际是对应CommandBarControls中的控件集合。CommandBarControl可以有三种表现形式:

    l         弹出式(CommandBarPopup):相当于菜单条的一个菜单项。

    l         组合框(CommandBarComboBox):类似于工具条中组合框控件。它包括一个工具栏和紧接着工具栏的一个下拉箭头。单击该按钮,将显示出更多的带图标的菜单命令。

    l         按钮(CommandBarButton):相当于标准的工具栏按钮,即带有图标的按钮。

     

    在下面的示例程序中,我们将在Outlook2K中新建一个工具条并在其上添加二个按钮,并且在其菜单“工具”中新建一个菜单条,这些操作都可以在OnConnection接口涵数中完成。

    首先,我们需要在工程中导入Office和Outlook类型库,可以在Stdafx.h文件中加入下面语句(注意:其中路径可根据Office所装路径自行设定):

    // 导入工程所需Office2K及Outlook2K类型库

    #import "e:/Program Files/Microsoft Office/Office/mso9.dll" rename_namespace("Office"), named_guids

    using namespace Office;

     

    #import "e:/Program Files/Microsoft Office/Office/MSOUTL9.olb" rename_namespace("Outlook"), raw_interfaces_only, named_guids

    using namespace Outlook;

     

    其次,让我们来在Outlook中新建一个工具条,并且在其上添加两个按钮。

    代码如下:

    // 装缷插件时处理

        STDMETHOD(OnConnection)(IDispatch * Application, ext_ConnectMode ConnectMode, IDispatch * AddInInst, SAFEARRAY * * custom)

        {

            CComPtr < Office::_CommandBars> spCmdBars;

           

            // Outlook应用接口_Application

            CComQIPtr <Outlook::_Application> spApp(Application);

            ATLASSERT(spApp);

     

            // 获取CommandBars接口

            CComPtr<Outlook::_Explorer> spExplorer;    

            spApp->ActiveExplorer(&spExplorer);

            HRESULT hr = spExplorer->get_CommandBars(&spCmdBars);

            if(FAILED(hr))

                return hr;

            ATLASSERT(spCmdBars);

     

            // 新增一个工具条及其上两个位图按钮

            CComVariant vName("新增Outlook2K工具条插件");

            CComPtr <Office::CommandBar> spNewCmdBar;

           

            // 新增工具条位置

            CComVariant vPos(1);

           

            CComVariant vTemp(VARIANT_TRUE); // 临时       

            CComVariant vEmpty(DISP_E_PARAMNOTFOUND, VT_ERROR);        

            // 用Add方法在指定位置新增一工具条并让spNewCmdBar指向它

            spNewCmdBar = spCmdBars->Add(vName, vPos, vEmpty, vTemp);

           

            // 获取新增工具条的CommandBarControls,从而在其上添加按钮

            CComPtr < Office::CommandBarControls> spBarControls;

            spBarControls = spNewCmdBar->GetControls();

            ATLASSERT(spBarControls);

           

            //MsoControlType::msoControlButton = 1

            CComVariant vToolBarType(1);

            //显示工具条

            CComVariant vShow(VARIANT_TRUE);

           

            CComPtr < Office::CommandBarControl> spNewBar;

            CComPtr < Office::CommandBarControl> spNewBar2;

           

            // 用CommandBarControls中的Add方法新增第一个按钮,并让spNewBar指向它

            spNewBar = spBarControls->Add(vToolBarType, vEmpty, vEmpty, vEmpty, vShow);

            ATLASSERT(spNewBar);

            // 用CommandBarControls中的Add方法新增第二个按钮,并让spNewBar2指向它

            spNewBar2 = spBarControls->Add(vToolBarType, vEmpty, vEmpty, vEmpty, vShow);

            ATLASSERT(spNewBar2);

           

            // 为每一个按钮指定_CommandBarButton接口,从面可以指定按钮的显示风格等

            CComQIPtr < Office::_CommandBarButton> spCmdButton(spNewBar);

            CComQIPtr < Office::_CommandBarButton> spCmdButton2(spNewBar2);

           

            ATLASSERT(spCmdButton);

            ATLASSERT(spCmdButton2);

           

            // 设置位图按钮风格,位图为32x32大小,将其放入剪切板中用PasteFace()贴在指定按钮上

            HBITMAP hBmp =(HBITMAP)::LoadImage(_Module.GetResourceInstance(),

                MAKEINTRESOURCE(IDB_BITMAP),IMAGE_BITMAP,0,0,LR_LOADMAP3DCOLORS);

           

            ::OpenClipboard(NULL);

            ::EmptyClipboard();

            ::SetClipboardData(CF_BITMAP, (HANDLE)hBmp);

            ::CloseClipboard();

            ::DeleteObject(hBmp);      

     

            // 粘贴前设置显示风格

            spCmdButton->PutStyle(Office::msoButtonIconAndCaption);

           

            hr = spCmdButton->PasteFace();

            if (FAILED(hr))

                return hr;

           

            spCmdButton->PutVisible(VARIANT_TRUE);

            spCmdButton->PutCaption(OLESTR("按钮1"));

            spCmdButton->PutEnabled(VARIANT_TRUE);

            spCmdButton->PutTooltipText(OLESTR("按钮1提示信息"));

            spCmdButton->PutTag(OLESTR("按钮1标志"));

           

            // 显示新增工具条

            spNewCmdBar->PutVisible(VARIANT_TRUE);

           

            // 设置第二个工具条按钮风格

            spCmdButton2->PutStyle(Office::msoButtonIconAndCaption);

           

            // 第二个按钮指定位图为Outlook2K中预先定义的位图

            spCmdButton2->PutFaceId(1760); 

           

            spCmdButton2->PutVisible(VARIANT_TRUE);

            spCmdButton2->PutCaption(OLESTR("按钮2"));

            spCmdButton2->PutEnabled(VARIANT_TRUE);

            spCmdButton2->PutTooltipText(OLESTR("按钮2提示信息"));

            spCmdButton2->PutTag(OLESTR("按钮2标志"));

            spCmdButton2->PutVisible(VARIANT_TRUE);

           

            m_spButton = spCmdButton;

            m_spButton2 = spCmdButton2;

            ……

     

    接着,让我们在菜单“工具”中新建一个菜单条。

    代码如下:

            _bstr_t bstrNewMenuText(OLESTR("新增菜单条"));

            CComPtr < Office::CommandBarControls> spCmdCtrls;

            CComPtr < Office::CommandBarControls> spCmdBarCtrls;

            CComPtr < Office::CommandBarPopup> spCmdPopup;

            CComPtr < Office::CommandBarControl> spCmdCtrl;

           

            CComPtr < Office::CommandBar> spCmdBar;

     

            // 通过CommandBar获取Outlook主菜单

            hr = spCmdBars->get_ActiveMenuBar(&spCmdBar);

            if (FAILED(hr))

                return hr;

     

            // 获取菜单条的CommandBarControls

            spCmdCtrls = spCmdBar->GetControls();

            ATLASSERT(spCmdCtrls);

           

            // 在第5个"工具"菜单下新增一菜单条

            CComVariant vItem(5);

            spCmdCtrl= spCmdCtrls->GetItem(vItem);

            ATLASSERT(spCmdCtrl);

           

            IDispatchPtr spDisp;

            spDisp = spCmdCtrl->GetControl();

           

            // 获取菜单条CommandBarPopup接口

            CComQIPtr < Office::CommandBarPopup> ppCmdPopup(spDisp); 

            ATLASSERT(ppCmdPopup);

           

            spCmdBarCtrls = ppCmdPopup->GetControls();

            ATLASSERT(spCmdBarCtrls);

           

            CComVariant vMenuType(1); // 控件类型 - menu

            CComVariant vMenuPos(6); 

            CComVariant vMenuEmpty(DISP_E_PARAMNOTFOUND, VT_ERROR);

            CComVariant vMenuShow(VARIANT_TRUE); // 菜单将显示

            CComVariant vMenuTemp(VARIANT_TRUE); // 临时       

           

           

            CComPtr < Office::CommandBarControl> spNewMenu;

            // 用Add方法创建新的菜单条

            spNewMenu = spCmdBarCtrls->Add(vMenuType, vMenuEmpty, vMenuEmpty, vMenuEmpty, vMenuTemp);

            ATLASSERT(spNewMenu);

           

            spNewMenu->PutCaption(bstrNewMenuText);

            spNewMenu->PutEnabled(VARIANT_TRUE);

            spNewMenu->PutVisible(VARIANT_TRUE);

           

            // 利用CommandBarButton来在菜单条前显示位图

            CComQIPtr < Office::_CommandBarButton> spCmdMenuButton(spNewMenu);

            ATLASSERT(spCmdMenuButton);

            spCmdMenuButton->PutStyle(Office::msoButtonIconAndCaption);

           

            // 同新增工具条第一个按钮位图相同方法

            spCmdMenuButton->PasteFace();

     

            // 显示菜单

            spNewMenu->PutVisible(VARIANT_TRUE);

     

            m_spMenu = spCmdMenuButton;

     

    这样,通过在Outlook中通过上面提到的方法加载COM插件,就可以看到如图一所示的界面效果了,但是点击时没有响应,最后就让我们来解决这个问题。

    工具条按钮CommandBarButton派发接口的响应事件是_CommandBarButtonEvents。ATL提供了二种模板类IDispEventImpl<>和IDispEventSimpleImpl<>来实现接口事件的接收,这里我们使用IDispEventSimpleImpl来实现(因为它不需要额外的类型库信息)。它需要设置SINK(接收)映射,通过_ATL_SINK_INFO结构来回调参数信息,最终通过DispEventAdviseDispEventUnadvise来与源接口连接或断开。实现方法如下:

    1.       COutlookAddin继承类中加入IDispEventSimpleImpl继承,代码如下:

    class ATL_NO_VTABLE COutlookAddin :

        public CComObjectRootEx<CComSingleThreadModel>,

        ……

        public IDispEventSimpleImpl<1,COutlookAddin,&__uuidof(Office::_CommandBarButtonEvents)>

     

    2.       声明_ATL_SINK_INFO结构回调参数信息。在OutlookAddin.h文件中加入下面语句:

    // 按钮事件响应信息声明

    extern _ATL_FUNC_INFO OnClickButtonInfo;

     

            在OutlookAddin.cpp文件中加入定义语句,如下:

                     // 按钮事件响应信息定义

    _ATL_FUNC_INFO OnClickButtonInfo ={CC_STDCALL,VT_EMPTY,2,{VT_DISPATCH,VT_BYREF | VT_BOOL}};

     

    3.       加入Sink映射,如下:

    EGIN_SINK_MAP(COutlookAddin)

       SINK_ENTRY_INFO(1, __uuidof(Office::_CommandBarButtonEvents),/*dispid*/ 0x01, OnClickButton1, &OnClickButtonInfo)

       SINK_ENTRY_INFO(2, __uuidof(Office::_CommandBarButtonEvents),/*dispid*/ 0x01, OnClickButton2, &OnClickButtonInfo)

        SINK_ENTRY_INFO(3, __uuidof(Office::_CommandBarButtonEvents),/*dispid*/ 0x01, OnClickMenu, &OnClickButtonInfo)

    END_SINK_MAP()

     

    4.       加入事件涵数。在OutlookAddin.h中加入声明:

    void __stdcall OnClickButton1(IDispatch * /*Office::_CommandBarButton**/ Ctrl,VARIANT_BOOL * CancelDefault);

     

    在OutlookAddin.cpp中加入实现:

    // 工具条按钮1点击事件响应涵数

    void __stdcall COutlookAddin::OnClickButton1(IDispatch* /*Office::_CommandBarButton* */ Ctrl,VARIANT_BOOL * CancelDefault)

    {

        USES_CONVERSION;

        CComQIPtr<Office::_CommandBarButton> pCommandBarButton(Ctrl);

           

        HINSTANCE result=ShellExecute(NULL, _T("open"), _T("http://www.vckbase.com"), NULL,NULL, SW_SHOW);

    }

     

    5.       最后,打开或断开与接口的连接。方法如下:

    l         在OnConnection接口涵数的最后部分,加入下面代码来打开连接:

    CommandButton1Events::DispEventAdvise((IDispatch*)m_spButton);

    l         在OnDisconnection接口涵数中,加入下面代码来断开连接:

    CommandButton1Events::DispEventUnadvise((IDispatch*)m_spButton);

     

    到此就完成一个Office内部插件的最小需求了,大家可以编译后打开Outlook2000看看效果如何,详细代码可参看文章所带示例源码,内有详细注释。

     

    参考文献:

    Building an Office2K COM addin with VC++/ATL -- Amit Dey

    ATL开发指南(第二版) – Tom Armstrong & Ron Patton

     

    联系方式:

    编者EMAIL:jingzhou_xu@163.com

    未来工作室(Future Studio)


    最新回复(0)