使用MFC开发ActiveX(ocx)控件

作者: 时间: 出处:

中国电波传播研究所青岛分所 郎锐 2004-03-09 yesky

一、前言 二、建立工程框架 三、属性、方法以及事件的添加 四、实现属性表 五、在包容程序中使用 ActiveX 控件 六、小结 前言 ActiveX 控件是一种实现了一系列特定接口而使其在使用和外观上更象一 个控件的 COM 组件。ActiveX 控件这种技术涉及到了几乎所有的 COM 和 OLE 的技 术精华,如可链接对象、统一数据传输、OLE 文档、属性页、永久存储以及 OLE 自动化等。 ActiveX 控件作为基本的界面单元,必须拥有自己的属性和方法以适合不 同特点的程序和向包容器程序提供功能服务,其属性和方法均由自动化服务的 IDispatch 接口来支持。除了属性和方法外,ActiveX 控件还具有区别于自动化 服务的一种特性--事件。事件指的是从控件发送给其包容程序的一 种通知。与 窗口控件通过发送消息通知其拥有者类似,ActiveX 控件是通过触发事件来通知 其包容器的。事件的触发通常是通过控件包容器提供的 IDispatch 接口来调用 自动化对象的方法来实现的。 在设计 ActiveX 控件时就应当考虑控件可能会发生 哪些事件以及包容器程序将会对其中的哪些事 件感兴趣并将这些事件包含进来。 与自动化服务不同,ActiveX 控件的方法、属性和事件均有自定义(custom)和 库存(stock)两种不同的类 型。自定义的方法和属性也就是是普通的自动化方 法和属性,自定义事件则是自己选取名字和 Dispatch ID 的事件。而所谓的库存 方法、属性和事件则是使用了 ActiveX 控件规定了名字和 Dispatch ID 的"标准" 方法、属性和事件。 ActiveX 控件可以使 COM 组件从外观和使用上能与普通的窗口控件一样, 而且还提供了类似于设置 Windows 标准控件属性的属性页,使其能够在包 容器 程序的设计阶段对 ActiveX 控件的属性进行可视化设置。ActiveX 控件提供的这 些功能使得对其的使用将是非常方便的。本文下面即以 MFC 为工 具对 ActiveX 控件的开发进行介绍。 建立工程框架 通过"MFC ActiveX ControlWizard"向导可以非常容易的建立一个 MFC ActiveX 控件工程框架。按照默认的选项将建立如图 1 所示的工程结构:

图 1 使用缺省选项建立的 ActiveX 控件工程结构 其中, _DSample68 和_DSample68Events 这两个接口将为客户程序提供本控 件的属性、方法以及可能响应的事件。全局函数 DllRegisterServer()和 DllUnregisterServer()分别用于控件在注册表的注册和注销,一般不需要对 其进行改动。 应用程序类从 COleControlModule 继承。而 COleControlModule 有是从 CWinApp 派生,提供了初始化控件模块的功能。 CSample68PropPage 的基类是 COlePropertyPage,CDialog 类的派生类,主要负责对属性页中对图形界面下用 户控件属性 的显示。 控件类 CSample68Ctrl 类是这几个类中比较重要的一个类, 大部分实质性工作都在该类完成, 其基类为 COleControl, CWnd 和 CCmdTarget 从 继承, 因此能够为控件对象提供与 MFC 窗口对象相同的功能同时也提供了一系列 事件触发函数和一个分发映射表,使 ActiveX 控件 能够同包容器程序有效地进 行交互。 该类的派生类将可以在满足特定的条件时向控件的包容器发送消息或是 触发事件,以通知包容器程序在控件内有一些重要的事件 发生。分发映射表是 其中很重要的一个部分,负责向包容器程序暴露控件提供的方法和属性。图 2 展示了 COleControl 类在控件与包容器通信中所起的作 用。可以看出, ActiveX 控件与其包容器之间的所有通信过程都是由 COleControl 来完成的:

图 2 COleControl 在 ActiveX 控件与包容器通信中的作用 控件类对基类 COleControl 的 OnDraw()函数进行了重载,向导生成了如 下缺省代码, 其作用是在控件的客户区绘制一个椭圆。在编程过程中通常要对其 进行替换:

void CSample68Ctrl::OnDraw( CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid) { // TODO: Replace the following code with your own drawing code. pdc->FillRect(rcBounds, CBrush::FromHandle((HBRUSH)GetStockObject(WHITE_BRUSH))); pdc->Ellipse(rcBounds); }

图 3 插入 ActiveX 控件

图 4 插入的待测试控件 对向导生成的代码进行编译后,将产生扩展名为 ocx 的 ActiveX 控件。 ActiveX 控件并不能独立运行,只能在包容器程序中才能够运行。通常,为 了 调试方便而多使用 VC++附带的 ActiveX Control Test Container 工具以在测试 阶段对 ActiveX 控件进行调试。 在测试工具的客户区点击鼠标右键,并选中弹出 菜单的"Insert New Control…"菜单项,将弹出图 3 所示的对话框,左侧的列表 框中列出了当前系统中所有注册的 ActiveX 控件, 选中要测试的控件并将其插入 到测试程 序即可通过"Control"菜单下的各菜单项对控件的方法、 属性以及事件 等进行测试。在位于下方的分割视图中将跟踪显示出调试记录(参见图 4)。 属性、方法以及事件的添加

图 5 属性的添加

图 6 方法的添加 对 ActiveX 控件属性、方法和事件的添加均有库存和自定义两种。其中对 属性和方法的添加在 MFC ClassWizard 对话框的 Automation 页中通过按钮"Add Property…"和"Add Method…"弹出如图 5 和图 6 所示的添加属性和添加方法的 对话框来完成。对于库存属性和方法,可以直接从 External name 组合框的下拉 列表中选取, Implementation 项将自动设置为 Stock。对于自定义属性和方法的 添加与在自动化对象中为接口添加属性和 方法的过程一样,ClassWizard 将 在.odl 文件和控件类生成相应的代码,下面给出的是在控件类中实现的部分分 发映射代码:

…… // Dispatch maps //{{AFX_DISPATCH(CSample68Ctrl) CString m_message; afx_msg void OnMessageChanged(); afx_msg short GetXPos(); afx_msg void SetXPos(short nNewValue); afx_msg short GetYPos(); afx_msg void SetYPos(short nNewValue); afx_msg short MessageLen(); //}}AFX_DISPATCH DECLARE_DISPATCH_MAP() // Dispatch and event IDs public: enum { //{{AFX_DISP_ID(CSample68Ctrl) dispidMessage = 1L, dispidXPos = 2L, dispidYPos = 3L, dispidMessageLen = 4L, //}}AFX_DISP_ID }; …… BEGIN_DISPATCH_MAP(CSample68Ctrl, COleControl) //{{AFX_DISPATCH_MAP(CSample68Ctrl) DISP_PROPERTY_NOTIFY(CSample68Ctrl, "Message", m_message, OnMessageChanged, VT_BSTR) DISP_PROPERTY_EX(CSample68Ctrl, "XPos", GetXPos, SetXPos, VT_I2) DISP_PROPERTY_EX(CSample68Ctrl, "YPos", GetYPos, SetYPos, VT_I2)

DISP_FUNCTION(CSample68Ctrl, "MessageLen", MessageLen, VT_I2, VTS_NONE) DISP_STOCKPROP_BACKCOLOR() DISP_STOCKPROP_CAPTION() DISP_STOCKPROP_FORECOLOR() //}}AFX_DISPATCH_MAP END_DISPATCH_MAP() ……

在这里共添加了一个自定义方法 MessageLen ) ( 和三种库存属性 BackColor、 Caption 和 ForeColor 分别 表示控件的背景色、 ( 标题和前台色)两个以 Get/Set 、 方式获取的自定义属性 XPos、YPos 和一个以成员变量方式实现的自定义属性 Message。 这几个自定义属性分别表示要显示字符串的 x、y 坐标和要显示的内 容。 对于采取 Get/Set 方式获取的属性,应当在控件类中为其添加相应的成员函 数,并修 改其 Get、Set 成员函数的实现过程:

short m_nYPos; short m_nXPos; …… short CSample68Ctrl::GetXPos() { return m_nXPos; } void CSample68Ctrl::SetXPos(short nNewValue) { m_nXPos = nNewValue; SetModifiedFlag(); } short CSample68Ctrl::GetYPos() { return m_nYPos; } void CSample68Ctrl::SetYPos(short nNewValue) { m_nYPos = nNewValue; SetModifiedFlag(); }

对于以成员变量方式创建的属性 Message,向导还为其生成了一个消息响应 函数:

void CSample68Ctrl::OnMessageChanged() { SetModifiedFlag(); }

只要该属性的值被更改,OnMessageChanged()函数即会被调用。 为了使上述属性设置如背景色、前景色等能够与控件实际联系起来,需要替 换控件类 OnDraw()函数中由向导生成的那部分代码。例如,下面这段代码即 以前面添加的属性设置作为参数值,在控件中显示一串字符:

// 用背景色设置画刷 CBrush Brush(TranslateColor(GetBackColor())); // 用前台色设置字体颜色 pdc->SetTextColor(TranslateColor(GetForeColor())); // 绘制背景 pdc->FillRect(rcBounds, &Brush); // 设置字体背景透明 pdc->SetBkMode(TRANSPARENT); // 显示字符 pdc->TextOut(m_nXPos, m_nYPos, m_message);

为了使属性设置更改后, 其效果能够立即在控件上显示出来,应当在与属性 设置相关的函数实现中调用 InvalidateControl()以更新控件的显示。 可以编译程序并在 ActiveX Control Test Container 工具中对其进行测试。 在插入控件后,通过"Invoke Methods…"菜单项弹出如图 7 所示的对话框。在 Method Name 组合框中可以选择要测试的属性和方法。其中,对于属性的测试分 别有 ProgGet 和 ProgSet 的说明以指出是对属性值的获取与设置。 Parameter 在 编辑框中输入要设置的参数及其对应的参数类型,点击 SetValue 按钮将把该参 数值添加到参数列表框, 最后点击 Invoke 按钮将在 控件应用设置的属性并执行 指定的方法。对于有返回值的方法,其执行结果将在 Return 编辑框中显示。如 果出现了异常操作,在 Exception 编辑框中 将会显示出相应的异常错误信息。 图 8 给出了经过属性设置的控件界面。

图 7 对属性、方法的测试

图 8 设置了属性后的控件 对于控件属性的添加,在 MFC ClassWizard 对话框的 ActiveX Events 页中 通过"Add Event…"按钮弹出如图 9 所示的"Add Event"事件添加对话框。 与方法、 属性的添加类似, External name 组合框中可以输入要添加的自定义事件名称, 在 也可以从下拉列表选择库存事件。Implementation 项将根据所要添加的事件类 型而自动设置 Stock 或 Custom 选项。ActiveX 控件将通过添加的事件来通知容 器程序有特定的事件发生,库存事件多为键盘、鼠标事件,将由 COleControl 自动进行处理。对于自定义事件,则只是在.odl 文件和控件类中添加了事件映 射表等必要的代码(代码附下),至于应当在何种条件下 触发该事件须由开发 人员自行编写代码。

图 9 事件的添加

dispinterface _DSample68Events { properties: // Event interface has no properties methods: // NOTE - ClassWizard will maintain event information here. // Use extreme caution when editing this section. //{{AFX_ODL_EVENT(CSample68Ctrl) [id(1)] void MsgOut(); //}}AFX_ODL_EVENT }; …… // Event maps //{{AFX_EVENT(CSample68Ctrl) void FireMsgOut() {FireEvent(eventidMsgOut,EVENT_PARAM(VTS_NONE));} //}}AFX_EVENT DECLARE_EVENT_MAP() // Dispatch and event IDs public: enum { //{{AFX_DISP_ID(CSample68Ctrl) …… eventidMsgOut = 1L, //}}AFX_DISP_ID }; …… BEGIN_EVENT_MAP(CSample68Ctrl, COleControl) //{{AFX_EVENT_MAP(CSample68Ctrl)

EVENT_CUSTOM("MsgOut", FireMsgOut, VTS_NONE) //}}AFX_EVENT_MAP END_EVENT_MAP()

上述代码添加了一个 MsgOut 的自定义事件, 可以在通过调用 FireMsgOut ) ( 来激发。 下面对 Message 属性的 OnMessageChanged 消息响应函数进行修改, () 每当 Message 属性内容被更改都会调用该函数,在该函数中调用此前添加的 MessageLen()方法以确定更改后的 Message 属性的字符串长度,在长度大于 10 时调用 FireMsgOut()触发 MsgOut 事件:

void CSample68Ctrl::OnMessageChanged() { InvalidateControl(); if (MessageLen() >= 10) FireMsgOut(); SetModifiedFlag(); }

图 10 选择要记录的事件 在用 ActiveX Control Test Container 对刚添加的事件进行测试时,首先 通过"Control"菜单下的"Logging…"菜单项弹出如图 10 所示的对话框,并 从 "Events"属性页中选中要跟踪记录的事件。当通过 Invoke Methods 对话框设置 Message 属性的内容超过 10 个字符后,位于程序框架下方的分割视图将记录控 件所触发的 MsgOut 事件(如图 11 所示)。

图 11 对事件的测试 实现属性表 属性 表是 ActiveX 控件所特有的一种技术,可以在包容器程序处于设计阶 段时为其提供一个可视化的人机交互界面, 并可以通过其对控件的自定义属性和 库存属性 进行设置。在用向导生成程序框架的同时即已经生成了一个空的用于 管理自定义属性的属性页。在代码上通过控件类实现文件中的属性页 ID 表对其 进行维护:

BEGIN_PROPPAGEIDS(CSample68Ctrl, 1) PROPPAGEID(CSample68PropPage::guid) END_PROPPAGEIDS(CSample68Ctrl)

这里的 CSample68PropPage 类是从 COlePropertyPage 派生出来的,而 COlePropertyPage 的 基类又是 CDialog,因此不难发现 CSample68PropPage 与 通常的对话框类是比较相似的。 可以象处理对话框一样在资源视图中为缺省的属 性 页添加与自定义属性相关的交互用控件,并通过 ClassWizard 将这些控件与 类成员变量建立绑定关系。 但是有一点不同, 就是在绑定成员变量时还要与控 件 中的相应属性建立起对应关系。如图 12 所示,在 Optional property name 组合 框中输入自定义属性名或是直接从下拉列表选择库存属性名,ClassWizard 向导 将在属性页类的 DoDataExchange 函数中 添加控件、 () 变量和属性的绑定代码:

void CSample68PropPage::DoDataExchange(CDataExchange* pDX) { //{{AFX_DATA_MAP(CSample68PropPage)

DDP_Text(pDX, IDC_MESSAGE, m_sMessage, _T("Message") ); DDX_Text(pDX, IDC_MESSAGE, m_sMessage); DDP_Text(pDX, IDC_TITLE, m_sCaption, _T("Caption") ); DDX_Text(pDX, IDC_TITLE, m_sCaption); DDP_Text(pDX, IDC_XPOS, m_nXPos, _T("XPos") ); DDX_Text(pDX, IDC_XPOS, m_nXPos); DDP_Text(pDX, IDC_YPOS, m_nYPos, _T("YPos") ); DDX_Text(pDX, IDC_YPOS, m_nYPos); //}}AFX_DATA_MAP DDP_PostProcessing(pDX); }

图 12 成员变量、控件与属性的绑定 这里只是在向导生成的缺省属性页中实现了自定义属性的可视化设置。虽 然也可以用相同的方法为库存属性进行设置, 但是更多的还是采用添加库存属性 页 ID 的 方法来直接使用库存属性页来对其进行维护。例如,对于库存属性 BackColor 和 ForeColor,可以通过 ID 号为 CLSID_CcolorPropPage 的库存属性 页来进行设置,在将其添加到属性页 ID 表的同时一定要注意修改 BEGIN_PROPPAGEIDS()宏的属性页计数,否则将会引起系统的崩溃:

BEGIN_PROPPAGEIDS(CSample68Ctrl, 2) PROPPAGEID(CSample68PropPage::guid) PROPPAGEID(CLSID_CColorPropPage) END_PROPPAGEIDS(CSample68Ctrl)

继续在 ActiveX Control Test Container 中测试控件,将其插入后选择 "Edit"菜单的"Properties…"菜单项,将弹出入图 13 所示的属性表。该属性表 共有三个属性 页,其中第一个属性页为刚才编辑的自定义属性页,第二个属性

页 (如图 14 所示) 即为 CLSID_CcolorPropPage 所指定的颜色属性页 (为库存 属 性页),最后一个属性页则是向导自动添加的扩展属性页。在属性表中设置了相 应的属性后,点击"应用"按钮即可让控件使用新的属性。这与在"Invoke Methods"对话框中所完成的功能一样,但显然要方便的多。而且在包容器程序的 设计阶段,也是通过该属性表来完成控件与客户的属性设置交互的。

图 13 控件的属性表

图 14 颜色属性页 在包容程序中使用 ActiveX 控件 对于 ActiveX 控件的包容器程序,并不需要象使用 OLE 文档服务器或 ActiveX 文档服务器对象那样编写特定的包容器程序框架,直接将控件添加到工 程并在对话框上创建即可对其进行使用。 通过"Project"菜单下的"Add To Project"菜单项弹出的"Components and Controls…"子菜单项打开一个"Components and Controls Gallery"对话框,进 入到 Registered ActiveX Controls 目录下,选取前面创建的 ActiveX 控件,并 将其添加到工程。 向导将会在工程中添加一个关于此 ActiveX 控件的包装类,并 在"Controls"工具栏中添加一个表示此控件的图标。 可以象使用其他的标准控件 一样将其放置到对话框资源中,并修改其缺省属性。除此之外,还可以在 程序 中通过对控件包装类成员函数的使用来动态更改控件的属性设置。例如,下面这 段代码通过包装类对象 m_ctrlTest 在程序运行期间动态设置了控件的 XPos、 YPos 以及 Message 属性:

// 更新显示 UpdateData(); // 动态更改控件的 Message 属性 m_ctrlTest.SetMessage(m_sInput); // 设置显示坐标 m_ctrlTest.SetXPos(10); m_ctrlTest.SetYPos(10);

图 15 添加事件响应函数 在资源视图中用鼠标右键点击放置于对话框上的 ActiveX 控件,并从弹出 菜单中选择"Events…"菜单项, 将弹出如图 15 所示的对话框,在左边的 列表框 中显示了控件提供的事件, 双击事件将在包容器程序中添加相应的事件处理函数 和事件映射表,并可以在响应控件发出的事件后进行相应的处理:

BEGIN_EVENTSINK_MAP(CSample69Dlg, CDialog) //{{AFX_EVENTSINK_MAP(CSample69Dlg) ON_EVENT(CSample69Dlg, IDC_SAMPLE68CTRL1, 1 /* MsgOut */, OnMsgOutSample68ctrl1, VTS_NONE) //}}AFX_EVENTSINK_MAP END_EVENTSINK_MAP()

…… void CSample69Dlg::OnMsgOutSample68ctrl1() { // 得到输入字符数 int nNum = m_ctrlTest.MessageLen(); // 回显信息 m_sInput.Format("输入字符太多,共输入了%d 个字符", nNum); // 显示信息 UpdateData(FALSE); }

从上述对 ActiveX 控件的使用过程可以看出其与标准控件的使用并没有什 么太大的区别, 通过包装类使得在客户程序中对控件属性、方法的使用可以象使 用普通 MFC 类一样简单。另外,在控件的包装类中还提供有 Create()方法, 使在程序运行期间也能够动态创建控件。 小结 尽管 ActiveX 控件从技术上集成了 COM 和 OLE 的许多精华技术, 但由于 MFC 对 ActiveX 控件提供了强大的支持,使得对 ActiveX 控件的 开发成为一件非常 容易的事情。 但要深刻理解 ActiveX 控件技术,还要对一些基础技术有一个基本 的概念,本文的目的并不在于介绍如何编写一个 ActiveX 控件,而是通过对控 件的创建过程的分析而使读者能够对 ActiveX 控件的开发有一个新的认识。 本文 所述代码在 Windows 2000 Professional 下由 Microsoft Visual C++ 6.0 编译 通过。


相关文档

使用MFC开发ActiveX控件
MFC开发ActiveX控件全过程
VC2005从开发MFC ActiveX ocx控件到发布到.net网站的全部过程
用MFC开发ActiveX控件详解
用MFC ActiveX Control Wizard生成的OCX控件
基于MFC的ActiveX控件开发
使用Delphi快速开发ActiveX控件 收藏 ActiveX控件也就是一般所说的OCX控件
MFC_ActiveX_ocx
运用MFC来实现ActiveX控件的方法
MFC ActiveX 控件开发指南
电脑版