65.9K
CodeProject 正在变化。 阅读更多。
Home

事件接收器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.11/5 (3投票s)

2007年3月1日

CPOL

20分钟阅读

viewsIcon

56121

downloadIcon

1066

介绍在 ATL COM 插件的 C++ 上下文中使用的事件接收器。

目录

引言
类型库文件
三个新项目
应用解决方法
连接到 Microsoft Word
修改 Microsoft Word 主菜单
添加事件接收器
创建新对话框
编译和安装
部署

引言

(在文章顶部下载 WordFindFacility 源代码文件)

像 Microsoft Office 套件这样的 COM 友好型应用程序具有连接点,允许外部进程检测事件。当然,可以检测到什么类型的事件完全取决于主机应用程序的供应商愿意提供什么。然而,这些事件可能包括选择菜单项、打开和关闭文档或打开或关闭整个应用程序等内容。事件接收器由客户端进程用于检测这些事件。正如文章 使用 C++ 的 ATL COM 共享插件 - 简单方法 中提到的,事件接收器是检测插件本身添加到主机应用程序的菜单项选择的优雅方法。

连接点和事件接收器的主题包含在许多优秀的书中,包括 Dr Richard Grimes 的《Professional ATL COM Programming》,Wrox Press,1998 年,其详细信息可在 http://www.grimes.demon.co.uk/books/prof_atl.htm 获取。我无意在此转述这些文本的内容。我的目的是在 ATL COM 插件的上下文中以及 使用 C++ 的 ATL COM 共享插件 - 简单方法 文章中介绍事件接收器。为此,我将基本上重做 WordFindFacility 项目示例。再次强调,此示例使用了 Microsoft Visual Studio 2005 和 Microsoft Word 2003。对于已经完成 使用 C++ 的 ATL COM 共享插件 - 简单方法 文章中此示例的读者,我警告不要尝试修改现有代码。COM 接口一旦定义并分配了 guid,就会像你最糟糕的噩梦一样隐藏在每个黑暗的角落。

类型库文件

(在本文顶部下载修改后的 Microsoft Office 类型库文件)

事件接收器过程通常涉及使用 tlb 类型库文件。然而,tlb 文件通常不随 Microsoft Word 等应用程序提供。幸运的是,使用 C++ 的 ATL COM 共享插件 - 简单方法 中描述的 OLE/COM 对象查看器确实会创建 idl 文件,并且稍后描述的 MIDL 工具可以将 idl 文件编译成 tlb 文件。不幸的是,OLE/COM 对象查看器生成的 idl 文件的编译并不总是成功的。

WordFindFacility 项目确实需要一个 Microsoft Office tlb 文件,因此请在某处创建一个新文件夹,并复制 MSO.dll 文件,该文件通常位于“C:\Program Files\Common Files\Microsoft Shared\OFFICE11”文件夹中。要创建 idl 文件,请转到 Visual Studio 文件夹内的“Common7\Tools\Bin”文件夹,然后启动 OleView.exe。按带有三个红色三角形的工具栏按钮,并打开保存的 MSO.dll 文件。在 ITypeLib Viewer 窗口中,将文件另存为 MSO.IDL,保存在与 MSO.dll 文件相同的文件夹中。关闭 OLE/COM 查看器。

下一步是打开命令提示符窗口。首选 Visual Studio 提供的命令提示符窗口,因为它设置了适合 MIDL 等命令行工具的环境变量。要使用 Visual Studio 命令提示符窗口,请按 Windows **开始**按钮。然后选择 **所有程序,Microsoft Visual Studio 2005,Visual Studio 工具**和 **Visual Studio 2005 命令提示符**。导航到包含 MSO.dll 和 MSO.idl 文件的文件夹。

进入正确的文件夹后,您可以通过输入以下命令尝试将您的 MSO.idl 文件编译成 MSO.tlb 文件

midl MSO.idl

此编译将如预期失败。现在需要用记事本或您喜欢的文本编辑器打开 MSO.idl 文件并调试代码。首先要做的就是在第 11 行删除 `custom` 声明。早期版本的 Microsoft Office 没有这一行。接下来,在库 Office 块顶部的 `importlib` 声明的正下方添加以下行:

typedef float single;
coclass CommandBarComboBox;
coclass CommandBarButton;

然后更改 `interface Scripts : _IMsoDispObj` 块中的行,从这个

[in, optional, defaultvalue(<unprintable IDispatch*>)] IDispatch* Anchor, 

变为这样:

[in, optional, defaultvalue(0)] IDispatch* Anchor, 

就像早期版本的 Microsoft Office 一样。

现在,将以 `MsoOrgChartOrientation` 开头并以 `MsoDiagramNodeType` 结尾的 typedef 语句组从其当前位置移动到库 Office 块开头附近的 typedef 语句组的末尾。此早期块中的最后一个 `typedef` 是 `MsoFeatureInstall`。最后,将以 `MsoAlertButtonType` 开头并以 `MsoTargetBrowser` 结尾的 typedef 语句组移动到 `MsoDiagramNodeType` 之后。

此时,mso.idl 文件应该可以编译,但会产生很多警告消息。应该注意的是,这个编辑和编译过程有点折衷。显然,如果您不打算使用那些难以编译的特定接口,那么您不必过于担心。然而,这里的目的是创建一个可以用于多个项目的 tlb 文件,因此投入一些精力来弄清楚这一点是值得的。

三个新项目

正如本文 引言 中所讨论的,将使用事件接收器重做早期文章 使用 C++ 的 ATL COM 共享插件 - 简单方法 中的示例。因此,第一件事就是创建一个新的共享插件项目。启动 Visual Studio 2005 并开始一个新项目。在“新建项目”对话框中,项目类型其他项目类型扩展性。模板是共享插件,项目名称为“WordFindFacility”。在共享插件向导的第 1 页,选择使用 Visual C++/ATL 创建插件。在第 2 页,主机应用程序为 Microsoft Word(取消勾选其他)。在第 3 页,将“Word Find Facility”设置为名称和描述。在第 4 页,勾选我希望我的插件在主机应用程序加载时加载我的插件应可供安装它的计算机上的所有用户使用,而不仅仅是安装它的人。在第 5 页,按**完成**。

如果您还没有这样做,现在是时候创建两个 MFC 项目,如文章 MFC 方法 中所述。其中一个项目当然是为了生成 Microsoft Word 接口的 COM 包装器。另一个用于通用的 Microsoft Office 接口。在这两个项目中,项目都是 MFC 应用程序,其中应用程序类型为单个文档,并在高级功能页面上勾选了自动化复选框。在这些项目中,从主菜单中选择**项目**和**添加类**。要添加的类是来自类型库的 MFC 类。在“来自类型库添加类”向导对话框中,使用单选按钮选择从文件添加类,然后按“...”按钮定位相关文件。对于 Microsoft Word,文件是 MSWORD.OLB,通常位于“C:\Program Files\Microsoft Office\OFFICE11”文件夹中。对于 Microsoft Office,文件是 MSO.dll,通常位于“C:\Program Files\Common Files\Microsoft Shared\OFFICE11”文件夹中。在这两种情况下,都按“>>”添加所有类。

应用解决方法

创建 COM 接口包装器后,需要将以下文件从包含 Microsoft Word COM 接口包装器的 MFC 项目复制到新的 WordFindFacility 项目中
  • CApplication.h
  • CDocument0.h
  • CWords.h
  • CRange.h
此外,还需要将以下文件从包含 Microsoft Office COM 接口包装器的 MFC 项目复制到新的 WordFindFacility 项目中
  • CCommandBarPopup.h
  • CCommandBarButton.h
  • CCommandBar0.h
  • CCommandBars.h
  • CCommandBarControl.h
  • CCommandBarControls.h
最后,需要将 ATL COM 共享插件 - 简单方法 文章的“解决方法”部分创建的 DotDispatchDriver.hDotDispatchDriver.cppDotOleErrors.hDotOleErrors.cpp 文件复制到新的 WordFindFacility 项目中。将文件物理复制到 WordFindFacility 文件夹后,还必须通过在 Visual Studio 的解决方案资源管理器中右键单击 `WordFindFacility` 树节点来将它们添加到项目中。在弹出菜单中选择**添加**和**现有项**,然后在“添加现有项”对话框中选择文件。现在为了完成从 MFC 到 ATL 的过渡,所有名为 COleDispatchDriver 的变量都将被查找并替换为变量名 `CDotDispatchDriver`。另外,每个包含对 CDotDispatchDriver 引用的头文件也必须在文件顶部附近包含以下行:

#pragma once
#include "DotDispatchDriver.h"

同时,在这些头文件的顶部附近,必须删除以 `#import` 开头的行。如果您一直仔细地按照文本进行操作,现在可能是尝试第一次编译的好时机。

连接到 Microsoft Word

在编译器一切正常的情况下,终于可以开始编写 C++ 代码了。首先,需要一种调用 Microsoft Word 的机制。参考 WordFindFacility 项目中向导生成的 Connect.cpp 源文件,有一个名为 `OnConnection` 的方法,该方法传递一个 `IDispatch` 指针,可以很好地达到此目的。因此,在 Connect.h 头文件中,在其他 include 声明中添加以下行:

#include "CApplication.h"

然后将以下行添加到 `CConnect` 类声明中:

private: 
    CApplication m_oWordApp; 

现在回到 Connect.cpp 源代码,将以下行添加到前面讨论的 `OnConnection` 方法中:

try
{ 
    m_oWordApp.AttachDispatch(pApplication); 
}
catch( DotOleException* e )
{ 
    DotOleErrors::OleErrorMessages(e); 
}
catch( DotOleDispatchException* e )
{ 
    DotOleErrors::DispatchErrorMessages(e); 
}

此外,必须将以下行添加到其他 include 声明中:

#include "DotOleErrors.h"
这里所做的是将 `IDispatch` 指针包装起来。然而,在使用这些东西时,必须牢记,尽管看起来如此,但这里正在进行进程间通信,因此建议经常使用 `try`/`catch` 语句。

修改 Microsoft Word 主菜单

下一项任务是修改 Microsoft Word 主菜单以包含我们的单词查找功能。这可能是将菜单项添加到现有菜单栏选项,但在此示例中,还将添加一个新的菜单栏选项。因此,在 Connect.h 头文件中,在其他 include 声明中添加以下行:

#include "CCommandBarPopup.h"
#include "CCommandBarButton.h"


然后将以下行添加到 `CConnect` 类声明中:

private:
    CCommandBarPopup m_oWordFindPopup;
    CCommandBarButton m_oWordFindButton;

这样添加的两个变量声明分别代表新的菜单栏选项和新的菜单项。同时在 Connect.h 文件中,还可以将以下行添加到 `CConnect` 类中:

private:
    void AddMenuItems();

    void RemoveMenuItems();

当然,这些是用于添加和删除单词查找功能菜单项的方法的声明。切换回 Connect.cpp 文件,将新方法 `AddMenuItems` 添加到文件末尾,以以下行开头:

void CConnect::AddMenuItems()
{
    try
    {
        CCommandBars oCommBars = m_oWordApp.get_CommandBars();
        CCommandBar0 oMenuBar = oCommBars.get_ActiveMenuBar();
        CCommandBarControls oMenuBarControls = oMenuBar.get_Controls();

        long count = oMenuBarControls.get_Count();
        for (long iii=1; iii<=count; iii++)
        {
            CComVariant vLong(iii);
            CCommandBarControl oControl = oMenuBarControls.get_Item(vLong);
            if ( oControl.get_Caption().Compare( "&Window" ) == 0 )
            {
                MsoControlType type = msoControlPopup;
                m_oWordFindPopup = oMenuBarControls.Add( CComVariant(type),
                                 vOpt, vOpt, CComVariant(iii), vOpt);
                m_oWordFindPopup.put_Tag("WordFindFacility Popup");
                m_oWordFindPopup.put_Caption("&My Menu");
                m_oWordFindPopup.put_Visible(TRUE);
                m_oWordFindPopup.put_TooltipText(
                                    "A Fairly Useless Word Find Facility");
                break;
            }
        }

这里正在做的是获取 Microsoft Word 主菜单栏控件,并逐一检查每个菜单栏选项以定位标准的 **Window** 选项。然后,在 **Window** 选项之前插入一个新的菜单栏选项。为此新菜单栏选项设置了许多属性,包括标题为 My Menu。现在当然必须添加菜单项本身。这可以通过以下代码完成,该代码附加到上面显示的 `AddMenuItems` 方法的代码中:

CCommandBarControls controls = m_oWordFindPopup.get_Controls();


MsoControlType type = msoControlButton;
m_oWordFindButton = controls.Add( CComVariant(type), vOpt, vOpt,
                                  CComVariant(1), vOpt);
m_oWordFindButton.put_Tag("WordFindFacility Popup");
m_oWordFindButton.put_Caption("&Find...");

m_oWordFindButton.put_Visible(TRUE);
m_oWordFindButton.put_TooltipText("A Fairly Useless Word Find Facility");
MsoButtonStyle style = msoButtonCaption;

m_oWordFindButton.put_Style(style);
m_oWordFindButton.put_OnAction( "!<WordFindFacility.Connect>" );

这里向新创建的菜单栏选项添加了一个标题为 Find... 的菜单项。为了完成 `AddMenuItems` 方法,将强制性的 try 块的 catch 附加到末尾,如下所示:

}
catch( DotOleException* e )
{
    DotOleErrors::OleErrorMessages(e);
}
catch( DotOleDispatchException* e )
{
    DotOleErrors::DispatchErrorMessages(e);
}

`RemoveMenuItems` 方法的代码如下:

void CConnect::RemoveMenuItems()
{
    try
    {
        CCommandBars oCommBars = m_oWordApp.get_CommandBars();
        CCommandBar0 oMenuBar = oCommBars.get_ActiveMenuBar();
        CCommandBarControls oMenuBarControls = oMenuBar.get_Controls();

        long count = oMenuBarControls.get_Count();
        for (long iii=1; iii<=count; iii++))
        {
            CComVariant vLong(iii);
            CCommandBarPopup oPopup = oMenuBarControls.get_Item(vLong);
            if ( oPopup.get_Caption().Compare( "&My Menu" ) == 0 )
            {
                CCommandBarControls oPopupControls = oPopup.get_Controls();
                while ( oPopupControls.get_Count() > 0 )
                {
                    CCommandBarButton button = oPopupControls.get_Item(
                                                   CComVariant((long)1) );
                    button.Delete(vOpt);
                }
                oPopup.Delete(vOpt);
                count--;
            }
        }
    }
    catch( DotOleException* e )
    {
        DotOleErrors::OleErrorMessages(e);
    }
    catch( DotOleDispatchException* e )
    {
        DotOleErrors::DispatchErrorMessages(e);
    }    }
}

此方法与 `AddMenuItems` 方法非常相似。再次获取对 Microsoft Word 主菜单栏的控件,并沿菜单栏进行搜索。然而,在这种情况下,搜索目标是 **My Menu** 菜单选项的出现。如果找到任何内容,则删除菜单栏选项及其下的菜单项。此方法已编写得相当健壮。当事情出现严重问题时,相同的菜单选项可能会出现多次。因此,进行全面的清理是明智的。

显然,以下 include 声明必须出现在 Connect.cpp 源文件顶部的其他 include 声明中,以考虑 `AddMenuItems` 和 `RemoveMenuItems` 方法中使用的额外数据类型:

#include "CCommandBar0.h"
#include "CCommandBars.h"
#include "CCommandBarControl.h"
#include "CCommandBarControls.h"

显然,除非作为插件过程的一部分调用 `AddMenuItems` 和 `RemoveMenuItems` 方法,否则它们不会产生任何影响。因此,将以下行添加到 Connect.cpp 源文件中的向导生成的 `OnStartupComplete` 方法中:

RemoveMenuItems();
AddMenuItems();

然后将以下行添加到向导生成的 `OnBeginShutdown` 方法中:

RemoveMenuItems();

添加事件接收器

现在是时候将本文 类型库文件 部分创建的 MSO.tlb 文件复制到 Visual Studio 生成的 WordFindFacility 文件夹中了。完成后,将以下行添加到 Connect.h 文件中,紧接在 include 声明之后:

#import "mso.tlb" \
    no_namespace, named_guids, raw_interfaces_only, raw_native_types
static const int ANY_NUMBER = 99;

顾名思义,变量 `ANY_NUMBER` 可以是任何数字。关键是它永远不能改变。在添加上述行之后,立即添加以下内容:

class CConnect;

typedef IDispEventImpl<ANY_NUMBER, CConnect, &DIID__CommandBarButtonEvents,
                    &LIBID_Office, 2, 3> FindMenuItemEventImpl;

这里使用了 ATL 模板来定义一个名为 `FindMenuItemEventImpl` 的类。参数“`CommandBarButtonEvents`”和“`Office`”及其各自的 uid 在 mso.tlb 中定义,正如在 mso.idl 文件中可以看到的。数字“2”和“3”是 Microsoft Office 版本号,在 mso.idl 文件顶部附近声明。现在 `FindMenuItemEventImpl` 类需要成为 `CConnect` 类派生的类之一,因此将 `CConnect` 类派生的类列表更改为:

public CComObjectRootEx<ccomsinglethreadmodel />,
public CComCoClass<CConnect, &CLSID_Connect>,
public IDispatchImpl<AddInDesignerObjects::_IDTExtensibility2,
       &AddInDesignerObjects::IID__IDTExtensibility2,
       &AddInDesignerObjects::LIBID_AddInDesignerObjects, 1, 0>

变为这样:

public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CConnect, &CLSID_Connect>,
public IDispatchImpl<AddInDesignerObjects::_IDTExtensibility2,
                 &AddInDesignerObjects::IID__IDTExtensibility2,
                 &AddInDesignerObjects::LIBID_AddInDesignerObjects, 1, 0>,
public FindMenuItemEventImpl

要创建事件接收器映射,请在 Connect.h 文件中的 `CConnect` 类内的 COM 映射声明之后添加以下行:

BEGIN_SINK_MAP(CConnect)

END_SINK_MAP()

此时,项目应该可以编译了。不幸的是,还差一点点。有几个名称冲突需要处理。其中两个可以通过更改之前输入的“import”声明来轻松修复:

#import "mso.tlb" \
   no_namespace, named_guids, raw_interfaces_only, raw_native_types, \
   rename("RGB","RGB2"), rename("DocumentProperties","DocumentProperties2")

其中一个名称冲突没那么容易解决。为此,有必要返回到本文 类型库文件 部分创建的 mso.idl 文件。用文本编辑器打开此 mso.idl 文件,并将字符串“IAccessible”的所有出现都替换为字符串“IAccessible2”。使用 MIDL 工具重新编译,并将生成的 mso.tlb 文件复制到 WordFindFacilty 项目文件夹。现在 WordFindFacility 项目将可以编译。

现在是时候专门识别要使用的 Microsoft Office 事件了。可以使用文本编辑器和 mso.idl 文件或 mso.dll 文件的 OLE/COM 对象查看器来完成此操作。无论哪种方式,您都会发现 `CommandBarButtonEvents` 定义如下:

interface ICommandBarButtonEvents : IDispatch {
    [id(0x00000001), helpcontext(0x00038271)]
    void Click( [in] CommandBarButton* Ctrl, 
                [in, out] VARIANT_BOOL* CancelDefault);
};

此类事件定义可以包含任意数量的事件,但显然在这种情况下只有一个,即“Click”,其 ID 为 0x00000001。顺便说一句,值得注意的是 MSO.dll 包含许多事件定义。事实上,MSWORD.OLB 和所有 Microsoft Office 类型库都包含更多的事件定义。因此,可以检测到的事件列表确实相当全面。总之,回到 ID 为 0x00000001 的 Click 事件,使用由此获得的信息,将以下条目添加到 Connect.h 文件中的事件接收器映射块:

SINK_ENTRY_EX(ANY_NUMBER, DIID__CommandBarButtonEvents, 0x00000001, 
              OnFindMenuItemClick))

通过将以下行添加到 Connect.h 文件中的 `CConnect` 类内的 `CConnect` 类,可以定义 `OnFindMenuItemClick` 方法:

public:
    void _stdcall OnFindMenuItemClick(LPDISPATCH button, 
                                      VARIANT_BOOL* cancelDefault);

请注意,传递到 `OnFindMenuItemClick` 方法的参数必须与 MSO.idl 文件中 `CommandBarButtonEvents` Click 事件定义的参数兼容。当然,`OnFindMenuItemClick` 方法需要一些可执行代码,因此将以下行附加到 Connect.cpp 源文件:

void _stdcall CConnect::OnFindMenuItemClick(LPDISPATCH button, 
                                            VARIANT_BOOL* cancelDefault)
{
}

现在,除非您告诉 Microsoft Word 您想要接收事件,否则您实际上不会收到任何事件。因此,将以下行添加到 Connect.cpp 源文件中的 `AddMenuItems` 方法的末尾:

HRESULT hr;

hr = FindMenuItemEventImpl::DispEventAdvise( m_oWordFindButton );
if (FAILED(hr))
{

    DotOleErrors::HRErrorMessages(hr,"DispEventAdvise FAIL");
}

变量 `m_oWordFindButton` 当然是新创建的 **Find...** 菜单项,将在其上接收事件。以下行也需要添加到 `RemoveMenuItems` 方法的开头:

HRESULT hr;

if ( m_oWordFindButton != NULL )
{
    hr = FindMenuItemEventImpl::DispEventUnadvise( m_oWordFindButton );
    if (FAILED(hr))
    {
        DotOleErrors::HRErrorMessages(hr,"DispEventUnAdvise FAIL");

    }

    m_oWordFindButton.ReleaseDispatch();

}

这比一些礼貌的家务活要多得多。Microsoft Word 在释放所有此类事件接收器之前不会关闭。请记住偶尔检查 Windows 任务管理器,以确保一切正常。无论如何,上述代码要求变量 `m_oWordFindButton` 最初设置为 `NULL`,因此在 Connect.cpp 源文件的 `CConnect` 构造函数中,添加以下行:

m_oWordFindButton = NULL;

创建新对话框

好了,到目前为止,我们在 Microsoft Word 中有一个新的菜单项,但它并没有做什么。是时候创建一个对话框了。因此,启动 Visual Studio,然后在解决方案资源管理器窗口中右键单击 WordFindFacility 项目。从弹出菜单中选择**添加**和**类...**。在“添加类”对话框中,从树视图中选择*ATL*,将模板选择为*ATL 对话框*。按**添加**,键入“WordFind”作为短名称,然后按**完成**。就个人而言,我发现一些 ATL 向导将类方法的可执行体放在头文件中的习惯非常令人恼火。以至于我有时会做的第一件事就是复制粘贴来纠正这一点。我鼓励您进入 WordFind.hWordFind.cpp 并做同样的事情 - 仅在头文件中声明方法。现在,从解决方案资源管理器切换到资源视图,并在树视图中双击带有标签 IDD_WORDFIND 的对话框。使用资源编辑器进行操作,直到得到类似如下的内容:

**Close** 按钮只是默认的 **OK** 按钮,但标题已修改。**Find** 按钮是新的,其 ID 为 `IDC_FIND`,使用“属性”窗口设置。在“属性”窗口中,单击闪电按钮,并添加一个方法来处理 **Find** 按钮的 `BN_CLICK` 事件。默认方法名称是可以的。使用“属性”窗口将编辑控件命名为 `IDC_WORD`。

现在来编写新对话框的一些代码。首先,ATL 提供了一个包装器用于其对话框控件,这里将在编辑控件上使用它。因此,在 WordFind.h 头文件中的 `CWordFind` 类中添加以下行:

private:
    CWindow m_cWord;

然后将以下行添加到 `OnInitDialog` 方法中,我相信它现在应该在 WordFind.cpp 源文件中了:

m_cWord.Attach( GetDlgItem( IDC_WORD ) );

将在构造期间将 Microsoft Word 文档传递给 `CWordFind` 类,因此在 WordFind.h 文件顶部附近的包含声明中添加以下行:

#include "CDocument0.h"

在同一个文件中,修改 `CWordFind` 构造函数的声明如下:

CWordFind( CDocument0* poDocument );;
同样在同一个文件中,将以下行添加到 WordFind 类中:

private:
    CDocument0* m_poDocument;

最后,修改构造函数代码,我相信它现在应该在 WordFind.cpp 源文件中,如下所示:

CWordFind::CWordFind( CDocument0* poDocument )
{
    m_poDocument = poDocument;
}

在单词搜索过程中,需要维护表示当前单词索引和单词总数的变量。因此,需要在 WordFind.h 头文件顶部附近的包含声明中添加以下行:

#include "CWords.h"

然后需要在同一个文件中的 CWordFind 类中添加以下代码行:

private:
    long m_lTotalNumberOfWords;
    long m_lWordIndex;
    CWords m_oWords;

最后,需要在 WordFind.cpp 源文件中的 CWordFind 类构造函数中添加以下行:

m_lWordIndex = 1;
m_oWords = poDocument->get_Words();
m_lTotalNumberOfWords = m_oWords.get_Count();

这里发生的事情相当简单。从提供的文档中获取一个代表文档中单词的对象。然后从“Words”对象中获取单词的数量。

在继续进行单词查找例程之前,还需要另一个 include 声明。这次在 WordFind.cpp 源文件中,需要在其他 include 声明中添加以下行:

#include "CRange.h"

现在来处理单词搜索。将以下行添加到 WordFind.cpp 源文件中的 `OnBnClickedFind` 方法中:

CString text;

m_cWord.GetWindowText( text );

while ( m_lWordIndex <= m_lTotalNumberOfWords )

{


    CRange oRange = m_oWords.Item( m_lWordIndex );
    m_lWordIndex++;
    CString word = oRange.get_Text();
    word.TrimRight();
    if ( text == word )
    {
        oRange.Select();
        return 0;
    }
}
::MessageBox( NULL, text + " Not Found", "Not Found", MB_OK|MB_ICONSTOP );

这里首先从编辑控件检索文本。然后使用单词索引遍历文档,检索并与编辑控件中的文本进行比较,每个索引值引用的单词。如果找到匹配项,则选择该单词,使其变为黑色。如果文档中未找到任何匹配项,则会显示错误消息。可以说,给出的代码并不难理解,但弄清楚在第一个地方使用哪些命令确实很难。在这方面,我唯一的建议是在 ATL COM 共享插件 - 简单方法 文章的 主机 COM 接口帮助 部分中给出的。

当然,当选择新的 **Find...** 菜单项时,需要调用此对话框,因此将以下行添加到 Connect.cpp 源文件中的 `OnFindMenuItemClick` 方法中:

try
{
    CDocument0 oActiveDocument = m_oWordApp.get_ActiveDocument();
    CWordFind dlg( &oActiveDocument );
    dlg.DoModal();
}
catch( DotOleException* e )
{
    DotOleErrors::OleErrorMessages(e);
}
catch( DotOleDispatchException* e )
{
    DotOleErrors::DispatchErrorMessages(e);
}

此代码需要一些额外的 include 声明,因此在 Connect.cpp 源文件顶部的其他 include 声明中,添加以下行:

#include "WordFind.h"
#include "CDocument0.h"

此时,您可能会注意到我对定义 COM 包装器类的局部变量的使用似乎过多。任何有 COM 经验的人都听说过引用计数器。引用计数器决定了 COM 对象的生命周期。本文描述的 COM 包装器在很大程度上隐藏了所有这些引用计数器方面。然而,不能完全忽略引用计数器的存在。因此,必须非常严格地控制 COM 包装器类的构造和析构。我认为大量使用局部变量是一种相当痛苦的实现这种严格控制的方法。在开发共享插件时,偶尔检查 Windows 任务管理器,以确保主机应用程序在您认为应该关闭时确实关闭,这是值得的。如果关闭没有按预期发生,并且您一直在虔诚地使用 COM 包装器类,那么请检查您是否理解每个 COM 包装器类实例的生命周期。

编译和安装

要编译和安装插件,请按以下步骤操作:
  1. 编译 WordFindFacility 项目。为此,在解决方案资源管理器窗口中右键单击 WordFindFacility 项目名称,然后选择**重新生成**
  2. 编译 WordFindFacilitySetup 项目。为此,在解决方案资源管理器窗口中右键单击 WordFindFacilitySetup 项目名称,然后选择**重新生成**
  3. 安装插件。为此,在解决方案资源管理器窗口中右键单击 WordFindFacilitySetup 项目名称,然后选择**安装**
现在,如果您启动 Microsoft Word,键入一些文本,然后选择**My Menu**和**Find...**,您的新单词查找功能应该就可以运行了。此时,我应该提到,如果您最终得到了一个即使卸载插件也无法消失的菜单栏选项,那么摆脱它的过程如下。首先,在 Microsoft Word 中,从主菜单中选择**工具**和**自定义...**。然后只需用鼠标左键抓取不需要的菜单栏选项,将其拖到显示的“自定义”对话框中,然后关闭对话框。

部署

现在您已成功创建了一个插件,您无疑想要某种方式来分发它。在项目开发过程中,安装插件只是一个右键单击 WordFindFacilitySetup 并选择**安装**的简单过程。然而,如果您查看 Visual Studio 作为项目的一部分创建的 WordFindFacilitySetup 文件夹,您会发现 WordFindFacilitySetup 本身就是一个独立的文件夹,其中包含其自己的 Debug 和 Release 文件夹。在 Debug 或 Release 文件夹中是 WordFindFacilitySetup.msi 文件,这几乎是安装插件所需的一切。msi 文件当然是 Windows Installer 数据库文件,在本例中,它实际上嵌入了插件的可执行代码。顺便说一句,Microsoft 实际上有一个用于创建和编辑 msi 文件的工具,称为 orca.exe。详细信息可在 http://msdn.microsoft.com/library/default.asp?url=/library/en-us/msi/setup/orca_exe.asp 找到。

要删除插件,需要使用通过**开始**按钮和**控制面板**访问的添加或删除程序

© . All rights reserved.