目录
1.12 AfxOleRegisterControlClass 39
第1章 VC++6.0创建控件
1.1 目标
本章的目标是使用MFC创建一个下图所示的ActiveX控件。
图1.1 ActiveX控件
1.1.1 方法
将给此控件增加一个方法:
void Show(BOOL bShow);
当bShow为TRUE 时,显示控件;
当bShow为FALSE 时,隐藏控件。
1.1.2 属性
属性名 |
说明 |
Text |
控件显示的文本 |
Font |
控件显示文本的字体 |
ColorText |
控件显示文本的颜色 |
ColorInner |
椭圆内的颜色 |
ColorOuter |
椭圆外的颜色 |
LDownCount |
控件内,鼠标左键按下的次数 |
1.1.3 事件
鼠标左键在控件内部按下时,会产生LDown事件:
void LDown(BOOL bInner)
当bInner为TRUE 时,表示鼠标在椭圆内;
当bInner为FALSE 时,表示鼠标在椭圆外。
1.2 创建项目
运行VC++6.0,新建"MFC ActiveX ControlWizard"项目,如下图所示。配置好项目名称、项目目录后,单击"OK"按钮。
图1.2 新建ActiveX项目
VC++6.0显示创建ActiveX控件的向导,界面如下图所示。
"How many controls would you like your project to have"用来设置本项目里有几个ActiveX控件。这里取默认值1。
"runtime license"表示运行时是否需要授权,这里取默认值"No runtime license",即运行时无需授权。
"Would you like source file comments to be generated?"表示代码里是否生成形如"//TODO"这样的注释。这里取默认值"Yes,please"。
"Would you like help files to be generated?"表示是否生成帮助文件,这里取默认值"NO help files"。
配置完成后,单击"Next"按钮。
图1.3 创建ActiveX控件——第一步
VC++6.0显示向导的第二步,界面如下图所示。
在OcxMFC这个下拉列表框内选择某个控件(上图有几个控件数,这里就有几个选项)。单击"Edit Names..."按钮,将显示图1.5所示的界面。用于编辑选中控件的名称。
"Activates when visible"表示控件显示时,将把焦点移到该控件上。
"Invisible at runtime"表示运行时,该控件不可见。
"Available in "Insert Object" dialog"如果希望Word、Excel……这些ActiveX容器能够插入该控件,请勾中此项。
"Has an "About" box"勾中此复选框,向导会给该控件增加AboutBox方法,客户端程序调用此方法时,将显示本控件的关于对话框。
"Acts as a simple frame control"设置控件为框架控件,亦即其它控件可以放在该控件内部。框架控件移动时,它内部的控件跟着一起移动。
"Which windows class,if any,should this control subclass"用于选择一个窗口类,控件将子类化这个窗口类。
单击"Advanced..."按钮,将显示图1.6所示的界面,用于配置高级选项。
这里不做任何改动,直接单击"Finish"按钮。
图1.4 创建ActiveX控件——第二步
图1.5 创建ActiveX控件——编辑名称
图1.6 创建ActiveX控件——高级选项
VC++6.0显示如下图所示。单击"OK"按钮,完成项目的创建。
图1.7 创建ActiveX控件确认界面
1.3 项目结构
下图是创建的项目结构
图1.8 项目结构
1.3.1 COM接口
_DOcxMFC和_DOcxMFCEvents这两个COM接口分别对应于ocxMFC.odl文件中的dispinterface _DOcxMFC和dispinterface _DOcxMFCEvents。前者用于客户端程序访问ActiveX控件;后者用于将控件内部的事件通知给客户端。
1.3.2 COM类
COcxMFCCtrl是实现控件功能最重要的一个类,其基类依次为COleControl、CWnd、CCmdTarget。它对应的是ocxMFC.odl文件中的coclass OcxMFC,其定义如下:
[ uuid(091AEC6D-3B16-4F1A-95FA-94E8C48E3366), helpstring("OcxMFC Control"), control ] coclass OcxMFC { [default] dispinterface _DOcxMFC; [default, source] dispinterface _DOcxMFCEvents; }; |
也就是说COM类OcxMFC实现了COM接口_DOcxMFC和_DOcxMFCEvents。
客户端创建控件,首先要创建COM类OcxMFC,其实就是实例化一个COcxMFCCtrl对象。获得COM接口_DOcxMFC之后,就可以访问控件了。控件产生的事件可以通过COM接口_DOcxMFCEvents传递给客户端。
1.3.3 属性页
下图是在Word文档中插入了一个日历控件,要想修改它的属性,如:显示的字体。可以鼠标右键单击控件,然后单击【日历对象】【属性】菜单项
图1.9 Word中修改ActiveX控件属性
Word将显示日历控件的属性页。可在此修改控件的属性。
图1.10 日历控件属性页
在图1.8中,COcxMFCPropPage可以实现属性页的一个页面。它的基类依次为COlePropertyPage、CDialog、CWnd。
1.3.4 应用程序类
在图1.8中,应用程序类COcxMFCApp的基类依次为:COleControlModule、CWinApp。它提供了初始化控件模块的功能。
1.3.5 注册与注销
在图1.8中,全局函数 DllRegisterServer 和DllUnregisterServer 分别用于控件的注册和注销。
当执行 regsvr32 ocxMFC.ocx 时将调用DllRegisterServer函数,往注册表里写入注册信息;当执行 regsvr32 /u ocxMFC.ocx 时将调用DllUnregisterServer函数,清除注册表里ocxMFC.ocx的注册信息。
这两个函数里,第一行代码均为:
AFX_MANAGE_STATE(_afxModuleAddrThis);
这说明:MFC创建的ActiveX控件,本质上是一个MFC Regular DLL。
1.4 方法
创建的项目中,已经有了一个方法AboutBox,调用此方法将显示该控件的关于对话框。其实现代码如下:
void COcxMFCCtrl::AboutBox() { CDialog dlgAbout(IDD_ABOUTBOX_OCXMFC); dlgAbout.DoModal(); } |
代码很简单,不过这里有意思的是:第一行代码不再是AFX_MANAGE_STATE(...);这说明:在调用此方法之前,MFC已经自动切换了模块状态。
1.4.1 增加
增加方法有两种途径:
1、鼠标右键单击"_DOcxMFC",然后鼠标左键单击弹出菜单中的【Add Method...】将显示图1.13所示的界面。注意:如果clw文件不存在,此方法会失败。请按下Ctrl+W创建clw文件之后再试一次。
图1.11
2、按下快捷键Ctrl+W,将显示类向导对话框。进入"Automation"页面,Class name请选择"COcxMFCCtrl",然后单击"Add Method..."按钮。也将显示图1.13所示的界面。
图1.12
下图就是"增加方法"的对话框:
External name 是客户端调用此方法时用到的名称;
Internal name 是本项目内部,此方法的名称;
Return type 是方法的返回值,这里选择void;
Parameter list 表示参数列表。这里只有一个参数 bShow
TRUE表示显示控件,FALSE表示隐藏控件。
方法有两种类型:Stock(库存)、Custom(自定义)。库存方法由COcxMFCCtrl的基类COleControl实现;自定义方法由COcxMFCCtrl编码实现。这里,Show是一个自定义方法。
图1.13 增加方法
1.4.2 删除
按下快捷键Ctrl+W,将显示类向导对话框。进入"Automation"页面。Class name请选择"COcxMFCCtrl"。然后在External names里选择要删除的方法或属性,单击"Delete"按钮即可删除选中项。最后请单击"OK"按钮。
下图中,External names列表中的"M"表示Method(方法);"C"表示Custom属性(自定义属性)、"S"表示Stock属性(库存属性)。
图1.14 删除方法
1.5 属性
删除属性与删除方法的操作相同,这里只说明如何增加属性。
增加属性有两种方法:
1、鼠标右键单击"_DOcxMFC",然后鼠标左键单击弹出菜单中的【Add Property...】将显示图1.17所示的界面。注意:此方法要求clw文件已存在。
图1.15
2、按下快捷键Ctrl+W,将显示类向导对话框。进入"Automation"页面,Class name请选择"COcxMFCCtrl",然后单击"Add Property..."按钮。也将显示图1.17所示的界面。
图1.16
下图就是"增加属性"的对话框。External name 是客户端调用此属性时用到的名称。
图1.17 增加属性
1.5.1 Text属性
下图是增加Text属性的界面。
注意:"Stock"单选框是可用的,说明COcxMFCCtrl的基类COleControl已经提供了Text属性。
选择"Stock"将表示Text是一个库存属性。它的存取完全由COleControl负责。
也可以选择"Get/Set methods",它将用COcxMFCCtrl::SetText和COcxMFCCtrl::GetText这两个函数,对Text属性进行存、取。也就是说,此时Text属性改由COcxMFCCtrl负责存取。
这里选择"Stock",单击"OK"按钮,完成Text属性的增加。这个Text属性就是一个库存属性。
图1.18 增加Text属性
1.5.2 Font属性
下图是增加Font属性的界面,它跟Text属性一样,也是库存属性。
图1.19 增加Font属性
1.5.3 ColorText属性
下图是增加ColorText属性的界面。关于它有几点需要说明:
1、"Stock"单选框不可用,意味着它不再是库存属性;
2、因为ColorText改变后,需要立即更新控件的显示,因此在这里选择了"Get/Set methods"。在COcxMFCCtrl::SetColorText函数里,完成ColorText属性的改变及控件显示的更新。
ColorInner、ColorOuter属性与ColorText属性的增加操作完全相同。
注意:下图虽然可以添加参数,但是添加参数后,属性将更改为方法。
图1.20 增加ColorText属性
1.5.4 LDownCount属性
下图是增加LDownCount属性的界面。改变这个属性,不用更新控件显示,因此没有必要再使用"Get/Set methods",可以使用"Member variable"。
注意:Notification function,它的默认名称为On*Changed,这里就是OnLDownCountChanged。客户端修改LDownCount属性时,将调用COcxMFCCtrl::OnLDownCountChanged函数。下图的Notification function为空,表示修改LDownCount属性时不会调用任何函数。
图1.21 增加LDownCount属性
1.6 事件
1.6.1 增加
增加事件有两种方法:
1、鼠标右键单击"_DOcxMFCEvents",然后鼠标左键单击弹出菜单中的【Add Event...】将显示图1.24所示的界面。注意:此方法要求clw文件已存在。
图1.22
2、按下快捷键Ctrl+W,将显示类向导对话框。进入"Automation"页面,Class name请选择"COcxMFCCtrl",然后单击"Add Event..."按钮。也将显示图1.24所示的界面。
图1.23 ActiveX事件页面
下图就是"增加事件"的对话框:
图1.24
External name 是客户端响应此事件时用到的名称。
Internal name 是本项目内部激发此事件时用到的函数名。
1.6.2 删除
在图1.23中,"External name"列表里选中要删除的事件,然后单击"Delete"按钮,最后单击"OK"按钮。
1.7 编码
1.7.1 给COcxMFCCtrl添加三个成员变量
代码如下:
protected: OLE_COLOR m_clrText; OLE_COLOR m_clrInner; OLE_COLOR m_clrOuter; |
1.7.2 初始化成员变量
具体就是初始化成员变量:m_lDownCount、m_clrText、m_clrInner、m_clrOuter。
COcxMFCCtrl::COcxMFCCtrl() { InitializeIIDs(&IID_DOcxMFC, &IID_DOcxMFCEvents); // TODO: Initialize your control's instance data here. m_lDownCount = 0; m_clrText = RGB(0,0,0); m_clrInner = RGB(255,255,255); m_clrOuter = RGB(0,255,0); } |
1.7.3 实现Show方法
void COcxMFCCtrl::Show(BOOL bShow) { ShowWindow(bShow ? SW_SHOW : SW_HIDE); } |
1.7.4 实现属性存取
OLE_COLOR COcxMFCCtrl::GetColorText() { return m_clrText; } void COcxMFCCtrl::SetColorText(OLE_COLOR nNewValue) { if(m_clrText != nNewValue) { m_clrText = nNewValue; SetModifiedFlag(); InvalidateControl(); } } OLE_COLOR COcxMFCCtrl::GetColorInner() { return m_clrInner; } void COcxMFCCtrl::SetColorInner(OLE_COLOR nNewValue) { if(m_clrInner != nNewValue) { m_clrInner = nNewValue; SetModifiedFlag(); InvalidateControl(); } } OLE_COLOR COcxMFCCtrl::GetColorOuter() { return m_clrOuter; } void COcxMFCCtrl::SetColorOuter(OLE_COLOR nNewValue) { if(m_clrOuter != nNewValue) { m_clrOuter = nNewValue; SetModifiedFlag(); InvalidateControl(); } } |
需要说明的是:
1、SetModifiedFlag说明控件的某些属性值发生了变化。ActiveX容器(如:VB6.0)在知道某个ActiveX控件的属性改变后,退出程序前会自动提示用户保存;
2、InvalidateControl将使控件的显示区域无效,会导致控件的重新绘制。
1.7.5 绘制控件
绘制控件的代码应该在COcxMFCCtrl::OnDraw里完成,具体代码如下:
void COcxMFCCtrl::OnDraw(CDC* pdc, const CRect& rcBounds ,const CRect& rcInvalid) { pdc->FillSolidRect(rcBounds,TranslateColor(m_clrOuter)); CBrush brhInner(TranslateColor(m_clrInner)); HGDIOBJ brhOld = pdc->SelectObject(brhInner.m_hObject); pdc->Ellipse(rcBounds); pdc->SelectObject(brhOld); CString s = InternalGetText(); pdc->SetBkMode(TRANSPARENT); pdc->SetTextColor(TranslateColor(m_clrText)); RECT rc = rcBounds; CFont* pOldFont = SelectStockFont(pdc); pdc->DrawText(s,&rc,DT_SINGLELINE | DT_VCENTER | DT_CENTER); pdc->SelectObject(pOldFont); } |
说明:
1、TranslateColor是COleControl的成员函数,用于把OLE_COLOR转换为COLORREF。OLE_COLOR与COLORREF一样,都是32位整数。COLORREF其实只用到了24位,高8位始终为零。OLE_COLOR高8位等于零时,它就是COLORREF,否则它表示特殊的颜色,如:0x80000001表示桌面颜色。这个时候,就需要COleControl::TranslateColor把OLE_COLOR转换为COLORREF;
2、InternalGetText是COleControl的成员函数,用来获得Text属性值。其返回值为CString。此外,COleControl::SetText、COleControl::GetText也可用于存取Text属性值,不过GetText返回的是BSTR,用完之后必须SysFreeString,比InternalGetText要繁琐些;
3、SelectStockFont(pdc) 是COleControl的成员函数,它表示pdc选用Font属性所指定的字体。
1.7.6 实现LDown事件
按下快捷键Ctrl+W,将显示类向导对话框。给COcxMFCCtrl类增加消息WM_LBUTTONDOWN的处理函数,如图1.25所示。
响应该消息的代码如下:
void COcxMFCCtrl::OnLButtonDown(UINT nFlags, CPoint point) { ++m_lDownCount; //计数值加一 RECT rc; GetClientRect(&rc); double a = rc.right / 2.0; double b = rc.bottom / 2.0; a = (point.x - a) / a; b = (point.y - b) / b; FireLDown(a * a + b * b < 1.0); COleControl::OnLButtonDown(nFlags, point); } |
注意:
1、FireLDown将触发LDown事件。客户端处理完该事件后,它才返回;
2、COleControl::OnLButtonDown将SetCapture(hWndOcx)。hWndOcx是控件窗口句柄。因此,客户端处理LDown事件时,最好不要弹出对话框。
图1.25
1.7.7 保存、恢复属性值
完成上述步骤,即可编译本项目,生成的ocxMFC.ocx将自动注册。VB6.0里也可以使用这个控件了,如下图所示:
图1.26 VB6.0里使用控件
控件一共有六个属性:Text、Font、ColorText、ColorInner、ColorOuter、LDownCount。除了Text和Font外,其它属性的值都不会被保留。也就是说:使用VB6.0修改了ColorText、ColorInner、ColorOuter、LDownCount的值,下次再打开项目时,这四个属性值又恢复成构造函数COcxMFCCtrl::COcxMFCCtrl里的值。
使用记事本打开VB6.0的窗体文件Form.frm,可以发现:它只保存了Text和Font属性的值,其它四个属性值并未保存。
Begin OCXMFCLib.OcxMFC OcxMFC1 Height = 1695 Left = 1920 TabIndex = 3 Top = 960 Width = 2055 _Version = 65536 _ExtentX = 3625 _ExtentY = 2990 _StockProps = 20 Text = "777" BeginProperty Font {0BE35203-8F91-11CE-9DE3-00AA004BB851} Name = "新宋体" Size = 15.75 Charset = 0 Weight = 400 Underline = 0 'False Italic = 0 'False Strikethrough = 0 'False EndProperty End |
为此,需要修改COcxMFCCtrl::DoPropExchange函数,具体代码如下:
void COcxMFCCtrl::DoPropExchange(CPropExchange* pPX) { ExchangeVersion(pPX, MAKELONG(_wVerMinor, _wVerMajor)); COleControl::DoPropExchange(pPX); // TODO: Call PX_ functions for each persistent custom property. PX_Long (pPX,_T("LDownCount"),m_lDownCount,0); PX_Color(pPX,_T("ColorText") ,m_clrText,RGB(0,0,0)); PX_Color(pPX,_T("ColorInner"),m_clrInner,RGB(255,255,255) ); PX_Color(pPX,_T("ColorOuter"),m_clrOuter,RGB(0,255,0)); } |
增加了PX_Long、PX_Color这四行代码后,ColorText、ColorInner、ColorOuter、LDownCount的值也能够被VB6.0(ActiveX容器)保存起来,以便再次载入控件时恢复属性值。
注意:
1、对于库存属性,不再需要增加PX_???代码。COleControl::DoPropExchange负责完成此项工作。
2、PX_???的具体含义请查阅MSDN。
1.7.8 属性页
在图1.10中,可以看到日历控件的属性页。通过属性页,可以方便的编辑控件的属性。
1.7.8.1 编辑Text、LDownCount属性
鼠标右键单击COcxMFCPropPage,在弹出菜单中,单击【Go To Dialog Editor】菜单项。
图1.27
编辑IDD_PROPPAGE_OCX_MFC界面如下:
图1.28
按下Ctrl+W,启动类向导,为两个文本框IDC_TEXT、IDC_COUNT增加变量。下图为IDC_TEXT增加了变量CString m_sText,且这个变量与Text属性绑定在一起。所谓绑定就是m_sText的值与Text属性值保持同步,一个值改变了另一个的值也跟着改变。为文本框IDC_COUNT增加变量、绑定属性值与IDC_TEXT的类似。
为文本框分配变量,并绑定属性值,其实就是修改了函数COcxMFCPropPage::DoDataExchange。代码如下:
void COcxMFCPropPage::DoDataExchange(CDataExchange* pDX) { //{{AFX_DATA_MAP(COcxMFCPropPage) DDP_Text(pDX, IDC_TEXT, m_sText, _T("Text") ); DDX_Text(pDX, IDC_TEXT, m_sText); DDP_Text(pDX, IDC_COUNT, m_Count, _T("LDownCount") ); DDX_Text(pDX, IDC_COUNT, m_Count); //}}AFX_DATA_MAP DDP_PostProcessing(pDX); } |
1.7.8.2 增加库存页面
在图1.10中,页面"字体"、"颜色"是系统已预制好的页面。可以把这种库存页面增加到控件的属性页。方法就是修改如下代码:
BEGIN_PROPPAGEIDS(COcxMFCCtrl, ) PROPPAGEID(COcxMFCPropPage::guid) PROPPAGEID(CLSID_CFontPropPage) //增加"字体"库存页面 PROPPAGEID(CLSID_CColorPropPage) //增加"颜色"库存页面 END_PROPPAGEIDS(COcxMFCCtrl) |
注意上面的数字3,表示属性页将有3个页面。
1.7.8.3 运行效果
在VB6.0中加载编译好的控件。在设计状态下,鼠标右键单击控件,弹出菜单中单击【Properties...】菜单项。
图1.29
显示如下面三张图片:
图1.30 VB6.0查看控件属性页——页面一
图1.31 VB6.0查看控件属性页——页面二
图1.32 VB6.0查看控件属性页——页面三
说明:
1、属性页显示过程:单击图1.29中的【Properties...】菜单项,会给控件发送消息。消息映射表如下:
BEGIN_MESSAGE_MAP(COcxMFCCtrl, COleControl) ... ... ... ON_OLEVERB(AFX_IDS_VERB_PROPERTIES, OnProperties) END_MESSAGE_MAP() |
这样就会调用OnProperties函数。在COleControl的声明中,OnProperties是一个虚函数。COcxMFCCtrl没有覆盖该函数,因此会调用COleControl::OnProperties函数。COleControl::OnProperties会根据如下代码显示一个有3个页面的属性页。
BEGIN_PROPPAGEIDS(COcxMFCCtrl, ) PROPPAGEID(COcxMFCPropPage::guid) PROPPAGEID(CLSID_CFontPropPage) //增加"字体"库存页面 PROPPAGEID(CLSID_CColorPropPage) //增加"颜色"库存页面 END_PROPPAGEIDS(COcxMFCCtrl) |
2、页面一可用来修改Text和LDownCount属性。进入该页面,将调用COcxMFCPropPage::DoDataExchange函数,把属性值传给绑定的变量,再更新到页面。离开该页面或按下"确定"、"应用"按钮,也会调用COcxMFCPropPage::DoDataExchange函数,把界面的输入值传给绑定变量,再传递给属性值;
3、页面一中修改Text属性值,退出VB6.0会提示是否保存;而修改LDownCount属性值,退出VB6.0时可能不会提示保存。因为修改Text属性值会调用SetModifiedFlag函数,而修改LDownCount属性值时不会调用SetModifiedFlag函数。如果只是修改LDownCount,VB6.0会认为用户对控件未做任何修改,也就不会提示用户保存;
4、页面二允许修改所有为字体类型的属性,这里就是Font属性;
5、页面三允许修改所有OLE_COLOR类型的属性,这里就是ColorText、ColorInner、ColorOuter三个属性。
1.8 类型库
作为C/C++程序员,要使用其他程序员编写的动态库或静态库,需要头文件。通过头文件,才能知道动态库或静态库里有什么函数,函数的参数、返回值情况……同样的,客户端程序要使用COM组件,也需要类似头文件功能的东西,这就是类型库。
可以编写文本格式的odl或idl文件,然后使用midl.exe将其编译为二进制的tlb文件——这就是类型库文件。
对于ActiveX控件而言,编译完成后,类型库就已经被嵌入ocx文件。如下图所示:
图1.33
这个类型库,是如何嵌入ocx文件的呢?使用记事本打开rc文件。可以看到如下代码。其实就是嵌入了ocxMFC.tlb文件。
图1.34
VC++6.0编译时,首先调用midl.exe编译ocxMFC.odl,生成ocxMFC.tlb。然后调用rc.exe把rc文件编译成res文件。最后通过link.exe把res文件嵌入ocx文件。这里需要注意rc.exe,它编译时需要把ocxMFC.tlb嵌入到res文件里,那么它是如何查找ocxMFC.tlb的呢?它会首先在dsp文件所在目录进行查找,然后才是到编译目录(如:Debug、Release)去查找。所以,一定不能把ocxMFC.tlb和dsp文件放在同一目录。因为编译生成的最新的ocxMFC.tlb在编译目录下,它不会被及时嵌入到ocx文件里。
1.8.1 VC++6.0的一个BUG
编辑odl文件时,该文件里的字符串尽量不要使用汉字。否则这些字符串随时可能会丢失、变成乱码。
1.9 控件注册与注销
1.9.1 命令
注册控件可使用如下任意一条命令。它们原理相同:都是载入ocxMFC.ocx,然后调用DllRegisterServer函数
regsvr32 ocxMFC.ocx |
Rundll32.exe ocxMFC.ocx,DllRegisterServer |
注销控件可使用如下任意一条命令。它们原理相同:都是载入ocxMFC.ocx,然后调用DllUnregisterServer函数
regsvr32 /u ocxMFC.ocx |
Rundll32.exe ocxMFC.ocx,DllUnregisterServer |
注意:
1、regsvr32和Rundll32.exe将自动判断ocxMFC.ocx是32位的还是64位的;
2、在64位Windows上,32位控件不能放到C:\Windows\System32目录进行注册,而应该放到C:\Windows\SysWOW64目录进行注册。C:\Windows\System32是用来存放64位控件的。
1.9.2 注册表
注册的实质其实是修改注册表,以ocxMFC.ocx为例,注册时写入的信息有六处:
1、类型库
注册表 |
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\TypeLib\{DAD6A33F-6756-43C6-BCE3-E48E361127CE}\1.0] @="ocxMFC ActiveX Control module" [HKEY_LOCAL_MACHINE\SOFTWARE\Classes\TypeLib\{DAD6A33F-6756-43C6-BCE3-E48E361127CE}\1.0\0\win32] @="G:\\VC\\ocxMFC\\Release\\ocxMFC.ocx" [HKEY_LOCAL_MACHINE\SOFTWARE\Classes\TypeLib\{DAD6A33F-6756-43C6-BCE3-E48E361127CE}\1.0\FLAGS] @="2" [HKEY_LOCAL_MACHINE\SOFTWARE\Classes\TypeLib\{DAD6A33F-6756-43C6-BCE3-E48E361127CE}\1.0\HELPDIR] @="G:\\VC\\ocxMFC\\Release" |
odl文件 |
[ uuid(DAD6A33F-6756-43C6-BCE3-E48E361127CE), version(1.0), helpfile("ocxMFC.hlp"), helpstring("ocxMFC ActiveX Control module"), control ] library OCXMFCLib |
代码 |
const GUID CDECL BASED_CODE _tlid = { 0xdad6a33f, 0x6756, 0x43c6, { 0xbc, 0xe3, 0xe4, 0x8e, 0x36, 0x11, 0x27, 0xce } }; |
2、COM接口_DOcxMFCEvents
注册表 |
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Interface\{FEF5A10F-950D-4F29-B2BF-AB449A21BD65}] @="_DOcxMFCEvents" [HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Interface\{FEF5A10F-950D-4F29-B2BF-AB449A21BD65}\ProxyStubClsid32] @="{00020420-0000-0000-C000-000000000046}" [HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Interface\{FEF5A10F-950D-4F29-B2BF-AB449A21BD65}\TypeLib] @="{DAD6A33F-6756-43C6-BCE3-E48E361127CE}" "Version"="1.0" |
odl文件 |
[ uuid(FEF5A10F-950D-4F29-B2BF-AB449A21BD65), helpstring("Event interface for OcxMFC Control") ] dispinterface _DOcxMFCEvents |
代码 |
const IID BASED_CODE IID_DOcxMFCEvents = { 0xfef5a10f, 0x950d, 0x4f29, { 0xb2, 0xbf, 0xab, 0x44, 0x9a, 0x21, 0xbd, 0x65 } }; |
3、COM接口_DOcxMFC
注册表 |
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Interface\{EC3B9652-4EAD-4FBD-AB11-5E2095423BAA}] @="_DOcxMFC" [HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Interface\{EC3B9652-4EAD-4FBD-AB11-5E2095423BAA}\ProxyStubClsid32] @="{00020420-0000-0000-C000-000000000046}" [HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Interface\{EC3B9652-4EAD-4FBD-AB11-5E2095423BAA}\TypeLib] @="{DAD6A33F-6756-43C6-BCE3-E48E361127CE}" "Version"="1.0" |
odl文件 |
[ uuid(EC3B9652-4EAD-4FBD-AB11-5E2095423BAA), helpstring("Dispatch interface for OcxMFC Control"), hidden ] dispinterface _DOcxMFC |
代码 |
const IID BASED_CODE IID_DOcxMFC = { 0xec3b9652, 0x4ead, 0x4fbd, { 0xab, 0x11, 0x5e, 0x20, 0x95, 0x42, 0x3b, 0xaa } }; |
4、COM类
注册表 |
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{091AEC6D-3B16-4F1A-95FA-94E8C48E3366}] @="OcxMFC Control" [HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{091AEC6D-3B16-4F1A-95FA-94E8C48E3366}\Control] @="" [HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{091AEC6D-3B16-4F1A-95FA-94E8C48E3366}\InprocServer32] @="G:\\VC\\ocxMFC\\Release\\ocxMFC.ocx" "ThreadingModel"="Apartment" [HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{091AEC6D-3B16-4F1A-95FA-94E8C48E3366}\MiscStatus] @="0" [HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{091AEC6D-3B16-4F1A-95FA-94E8C48E3366}\MiscStatus\1] @="131473" [HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{091AEC6D-3B16-4F1A-95FA-94E8C48E3366}\ProgID] @="OCXMFC.OcxMFCCtrl.1" [HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{091AEC6D-3B16-4F1A-95FA-94E8C48E3366}\ToolboxBitmap32] @="G:\\VC\\ocxMFC\\Release\\ocxMFC.ocx, 1" [HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{091AEC6D-3B16-4F1A-95FA-94E8C48E3366}\TypeLib] @="{DAD6A33F-6756-43C6-BCE3-E48E361127CE}" [HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{091AEC6D-3B16-4F1A-95FA-94E8C48E3366}\Version] @="1.0" |
odl文件 |
[ uuid(091AEC6D-3B16-4F1A-95FA-94E8C48E3366), helpstring("OcxMFC Control"), control ] coclass OcxMFC |
代码 |
IMPLEMENT_OLECREATE_EX(COcxMFCCtrl ,"OCXMFC.OcxMFCCtrl.1" ,0x91aec6d, 0x3b16, 0x4f1a, 0x95, 0xfa, 0x94, 0xe8, 0xc4, 0x8e, 0x33, 0x66) |
5、属性页面类
注册表 |
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{3AE04D9E-7833-42C3-8A28-566DE94AA395}] @="OcxMFC Property Page" [HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{3AE04D9E-7833-42C3-8A28-566DE94AA395}\InprocServer32] @="G:\\VC\\ocxMFC\\Release\\ocxMFC.ocx" |
odl文件 |
无 |
代码 |
IMPLEMENT_OLECREATE_EX(COcxMFCPropPage ,"OCXMFC.OcxMFCPropPage.1" ,0x3ae04d9e, 0x7833, 0x42c3, 0x8a, 0x28, 0x56, 0x6d, 0xe9, 0x4a, 0xa3, 0x95) |
6、ProgID——OCXMFC.OcxMFCCtrl.1
注册表 |
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\OCXMFC.OcxMFCCtrl.1] @="OcxMFC Control" [HKEY_LOCAL_MACHINE\SOFTWARE\Classes\OCXMFC.OcxMFCCtrl.1\CLSID] @="{091AEC6D-3B16-4F1A-95FA-94E8C48E3366}" |
odl文件 |
无 |
代码 |
IMPLEMENT_OLECREATE_EX(COcxMFCCtrl ,"OCXMFC.OcxMFCCtrl.1" ,0x91aec6d, 0x3b16, 0x4f1a, 0x95, 0xfa, 0x94, 0xe8, 0xc4, 0x8e, 0x33, 0x66) |
注意:
1、HKEY_LOCAL_MACHINE的某些内容会映射到HKEY_CLASSES_ROOT。如:HKEY_LOCAL_MACHINE\SOFTWARE\Classes\TypeLib\{DAD6A33F-6756-43C6-BCE3-E48E361127CE}会被映射到HKEY_CLASSES_ROOT\TypeLib\{DAD6A33F-6756-43C6-BCE3-E48E361127CE}。删除前者,后者也跟着消失。
1.9.2.1 64位Windows
使用VC++2010,可以开发ocxMFC的32位、64位两种版本。这两种版本的ocx注册后,能正常被32位、64位程序调用。显然,在64位Windows下,ActiveX控件的注册是有些特殊的。
对于HKEY_LOCAL_MACHINE\SOFTWARE\Classes\TypeLib\???这样的注册表项,在64位Windows下有三项。但事实上,它们只是一项的三个名字,删除任何一个,另外两个也就消失了。
HKEY_LOCAL_MACHINE\SOFTWARE\Classes\TypeLib\??? HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Wow6432Node\TypeLib\??? HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Classes\TypeLib\??? |
对于HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\???或HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Interface\???这样的注册表项,在64位Windows下也有三项。第一项只有64位程序能够访问;第二、三项其实是同一项的两个名字,它们可以被32位或64位程序访问。
HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID或Interface\??? HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Wow6432Node\CLSID或Interface\??? HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Classes\CLSID或Interface\??? |
更加明确的说明:
1、64位程序可直接访问HKEY_LOCAL_MACHINE\SOFTWARE\Classes\TypeLib\???。32位程序访问HKEY_LOCAL_MACHINE\SOFTWARE\Classes\TypeLib\???时会被映射至HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Wow6432Node\TypeLib\???或HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Classes\TypeLib\???。但是这三项其实是一回事,所以类型库的注册对于32位、64位程序是没有分别的;
2、64位程序可直接访问HKLM\SOFTWARE\Classes\CLSID\???。32位程序访问HKLM\SOFTWARE\Classes\CLSID\???时会被映射至HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Wow6432Node\CLSID\???或HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Classes\CLSID\???。也就是说:COM接口、COM类的注册对于32位、64位程序是有区别的。换句话说就是32位、64位ActiveX控件的COM接口、COM类注册信息会写入不同的注册表项;
3、对于HKEY_LOCAL_MACHINE\SOFTWARE\Classes\OCXMFC.OcxMFCCtrl.1的访问,32位程序和64位程序没有区别。也就是说ProgID的注册,32位、64位程序也是没有分别的。
1.10 ProgID
创建ActiveX控件时,图1.5中的OCXMFC.OcxMFCCtrl.1就是控件的ProgID。
下面的VB6.0代码,利用ProgID创建ActiveX控件实例。
Dim obj as Object set obj = CreateObject("OCXMFC.OcxMFCCtrl.1") |
其执行步骤:
1、根据ProgID获取COM组件的COM类ID。
函数CLSIDFromProgID 可完成此项工作,其实质是读取注册表
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\OCXMFC.OcxMFCCtrl.1\CLSID] @="{091AEC6D-3B16-4F1A-95FA-94E8C48E3366}" |
2、根据CLSID,加载COM组件,然后返回COM接口指针
函数CoCreateInstance可完成此项工作,它也会读取注册表:
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{091AEC6D-3B16-4F1A-95FA-94E8C48E3366}\InprocServer32] @="G:\\VC\\ocxMFC\\Release\\ocxMFC.ocx" ……………… |
1.10.1 处理重复
使用VC++创建ActiveX控件时,有可能会发生ProgID重复的问题。图1.4中单击"Finish"按钮后,如果ProgID在注册表中已经存在,会弹出如下对话框:
图1.35
单击"是"按钮,新建控件的COM类ID(CLSID)会沿用上图提示ProgID的CLSID。单击"否"按钮,新建控件的CLSID会自动生成一个全新的ID。比较稳妥的作法应该是单击"否"按钮。
VC++2010里,对于ProgID的重复处理不会再弹出对话框。而是直接生成一个全新的CLSID。
1.11 控件位图
VC++6.0加入本控件后,"Controls"里将显示该控件的位图。
图1.36
在ocxMFC项目里,可以修改这个位图——IDB_OCXMFC。
图1.37
在图1.36中,位图的显示需要查询注册表:
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{091AEC6D-3B16-4F1A-95FA-94E8C48E3366}\ToolboxBitmap32] @="G:\\VC\\ocxMFC\\Release\\ocxMFC.ocx, 1" |
也就是显示ocxMFC.ocx里ID号为1的位图。下图是使用eXeScope查看ocxMFC.ocx里ID号为1的位图。
图1.38
1.12 AfxOleRegisterControlClass
在图1.4中,可以对创建的控件进行配置,其实质是配置AfxOleRegisterControlClass的参数。
如下面的代码
AfxOleRegisterControlClass(AfxGetInstanceHandle(), m_clsid, m_lpszProgID, IDS_OCXMFC, IDB_OCXMFC, afxRegApartmentThreading | afxRegInsertable, _dwOcxMFCOleMisc | OLEMISC_ACTIVATEWHENVISIBLE, _tlid, _wVerMajor, _wVerMinor); |
afxRegInsertable 相当于勾中图1.4的"Available in "Insert Object" dialog"。这样此控件就能够被Word、Excel……作为对象插入。
顺便提一下:"Available in "Insert Object" dialog"不仅仅增加了afxRegInsertable,它还增加了一行代码,使得Word插入该控件后,右键单击该控件后可以编辑它。
BEGIN_MESSAGE_MAP(COcxMFCCtrl, COleControl) //{{AFX_MSG_MAP(COcxMFCCtrl) //}}AFX_MSG_MAP ON_OLEVERB(AFX_IDS_VERB_EDIT, OnEdit) //增加的代码 ON_OLEVERB(AFX_IDS_VERB_PROPERTIES, OnProperties) END_MESSAGE_MAP() |
OLEMISC_ACTIVATEWHENVISIBLE 相当于勾中图1.4中的"Activates when visible"。此外,OLEMISC_INVISIBLEATRUNTIME相当于勾中图1.4中的"Invisible at runtime"更多选项请查阅MSDN。
第2章 VC++2010创建控件
2.1 创建项目
运行VC++2010,新建"MFC ActiveX Control"项目,如下图所示。配置好项目名称、项目目录后,单击"OK"按钮。
图2.1 新建ActiveX项目
VC++2010显示创建ActiveX控件的向导,界面如下面四张图所示。
图2.2 创建ActiveX控件向导——页面一
图2.3 创建ActiveX控件向导——页面二
图2.4 创建ActiveX控件向导——页面三
图2.5 创建ActiveX控件向导——页面四
2.2 项目结构
下图是创建的项目结构
图2.6 项目结构
与VC++6.0项目结构基本一致。最大的区别在于:增加了类型库ocxMFCLib和COM类ocxMFC,并把_DocxMFC和_DocxMFCEvents这两个COM接口移到了类型库ocxMFCLib下。如此显示,结构更加清晰。
2.3 增加方法
鼠标右键单击"_DOcxMFC",然后鼠标左键单击弹出菜单中的【Add】【Add Method...】。
图2.7 增加方法右键菜单
VC++2010显示如下面两张图。可见:与VC++6.0相比,数据类型更加丰富、更符合COM规范(VC++6.0里没有VARIANT_BOOL,只有BOOL),此外增加了IDL Attributes,对IDL文件的控制更加细致。
图2.8 增加方法——页面一
图2.9 增加方法——页面二
2.4 增加属性
与增加方法类似,只是在图2.7中单击【Add Property...】菜单项。界面显示如下面两张图。
图2.10 增加属性——页面一
图2.11 增加属性——页面二
2.5 增加事件
根据VC++6.0的传统,应该是鼠标右键单击"_DOcxMFCEvents",然后鼠标左键单击弹出菜单中的【Add Event...】。可惜不是这样,应该是鼠标右键单击"CocxMFCCtrl",然后再单击【Add Event...】。
图2.12 增加事件右键菜单
增加事件的界面如下图所示:
图2.13 增加事件
2.6 删除方法、属性、事件
在VC++.NET里删除方法、属性、事件,似乎没有快捷的方法,只能手动修改ocxMFC.idl、ocxMFCCtrl.h、ocxMFCCtrl.cpp这三个文件。即便在VC++2010恢复了类向导之后,这种情况依然没有改观。
第3章 VC++6.0使用控件
3.1 增加控件的操作
单击VC++6.0的【Project】【Add To Project】【Components and controls】
图3.1
鼠标双击"Registered ActiveX Controls",进入这个目录
图3.2
鼠标左键单击选中要插入的ActiveX控件,然后单击"Insert"按钮。
图3.3
如果是Windows7系统,需要注意:
1、单击"Insert"按钮之前,一定要把文件名后面的".lnk"删除掉;
2、插入ActiveX控件后,请立即退出VC++6.0,然后再次运行。因为此时VC++6.0随时可能崩溃。
单击上图的"Insert"按钮,VC++6.0会提示是否插入此控件?请单击"确定"按钮。
图3.4
VC++6.0会根据此控件的类型库信息创建COM接口_DOcxMFC的包装类。在下图中,可以修改包装类的名称及文件名称,然后单击"OK"按钮。
至此,此控件即被插入VC++项目里。Controls窗口里增加了该控件的图标,使用它可以在对话框上增加ActiveX控件。
图3.5
3.2 增加控件的实质
增加控件的过程中,可以看到VC++6.0为控件的COM接口_DOcxMFC生成了一个C++包装类,根据这个包装类即可访问控件的属性和方法。
还有一项工作就是在dsp文件的最后增加了如下语句
# Section Test : {EC3B9652-4EAD-4FBD-AB11-5E2095423BAA} # 2:5:Class:COcxMFC # 2:10:HeaderFile:ocxmfc.h # 2:8:ImplFile:ocxmfc.cpp # End Section # Section Test : {091AEC6D-3B16-4F1A-95FA-94E8C48E3366} # 2:21:DefaultSinkHeaderFile:ocxmfc.h # 2:16:DefaultSinkClass:COcxMFC # End Section |
{EC3B9652-4EAD-4FBD-AB11-5E2095423BAA}是COM接口_DOcxMFC的GUID,这一段表示:_DOcxMFC的包装类是COcxMFC,并指明了包装类的头文件和实现文件。
{091AEC6D-3B16-4F1A-95FA-94E8C48E3366}是COM类的GUID。这一段表示:控件发出的事件由COcxMFC负责接收,并指明了COcxMFC的头文件。
3.3 控件名称重复
插入控件时,列表列出的是控件的名称。即图3.3里的"OcxMFC Control"就是插入控件的名称。
控件名称在注册表中的位置如下表所示。注意:{091AEC6D-3B16-4F1A-95FA-94E8C48E3366}是控件COM类的GUID
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{091AEC6D-3B16-4F1A-95FA-94E8C48E3366}] @="OcxMFC Control" |
创建ActiveX项目时,可在图1.5中修改控件名称。
编译ActiveX项目时,可在odl文件中修改控件名称。
[ uuid(091AEC6D-3B16-4F1A-95FA-94E8C48E3366), helpstring("OcxMFC Control"), control ] coclass OcxMFC |
因为ActiveX控件可能不是同一人开发,所以名称重复在所难免。此时,会发生什么情况?
在控件名称重复的情况下,图3.3的列表只能列出重名控件中的一个。插入控件时需要仔细检查控件的全路径名称(即Path to control),如果这个全路径名称不正确,就需要修改注册表,修改某些控件的名称。
第4章 VC++2010使用控件
4.1 增加控件
4.1.1 增加到对话框
Resource View 里,鼠标左键双击某个对话框,如下图的IDD_TEST_DIALOG。这个对话框将被显示出来,此时鼠标右键单击该对话框,弹出菜单中单击【Insert ActiveX Control...】菜单项。
图4.1
VC++2010将显示如下界面
图4.2
选择要增加的控件,然后单击"OK"按钮,即可完成控件的增加。也可以直接鼠标左键双击某个控件完成增加工作。
这里笔者有个疑问:上图中,控件的Path为什么没有显示出来?
4.1.2 增加到Toolbox
如果需要频繁的增加某种控件,上一节的增加方法就显得效率比较低。这一节里,将把控件增加到Toolbox。这样,增加控件到对话框,就会比较高效一些。
鼠标右键单击Toolbox,弹出菜单中单击【Choose Items...】菜单项
图4.3
显示如下界面。请进入"COM Components"页面,列表里勾中要添加的控件(可以勾中多个)。单击"OK"按钮即可将控件添加到Toolbox。此时,可以通过Toolbox往对话框里增加控件,效率会提高很多。
图4.4
4.1.3 Toolbox里删除控件
鼠标右键单击某控件,弹出菜单中单击【Delete】菜单项,即可完成删除。
图4.5
上图中还有【Rename Item】菜单项,可用来更名。
4.2 生成包装类
增加了COM控件的接口包装类后,客户端程序才能访问控件。这里将说明生成包装类的两种方法。
4.2.1 直接生成
单击【Project】【Add Class...】菜单项
图4.6
选中"MFC Class From ActiveX Control",单击"Add"按钮。
图4.7
下拉列表框内选择控件,然后依次单击">>"和"Finish"按钮。
图4.8
上图中,单击"File"单选框,就可以通过一个ocx文件来生成包装类了。这种方式会多出一个事件接口,可以不用为事件接口添加包装类。
4.2.2 间接生成
可以给ActiveX控件添加变量。添加变量时如果没有发现包装类,VC++2010会自动生成包装类。操作如下:
鼠标右键单击控件,弹出菜单中单击【Add Variable...】菜单项
图4.9
显示如下界面:
图4.10
"Variable type"就是包装类的名称。如果ocxMFC这种控件已经生成了包装类,则此项将无法更改。
"Variable name"表示绑定该控件的变量名称。
单击"Finish"按钮,完成包装类的创建以及控件与变量的绑定。
4.2.3 包装类BUG
比较下面两张图,就能发现:使用VC++2005、2008、2010生成的包装类,竟然没有属性!
图4.11 VC++2005、2008、2010生成的包装类
图4.12 VC++6.0生成的包装类
第5章 VB6.0使用控件
VB6.0里增加ActiveX控件,如下图所示。
图5.1
列出的是控件的类型库信息。也就是说"ocxMFC ActiveX Control module"在注册表中的位置如下所示
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\TypeLib\{DAD6A33F-6756-43C6-BCE3-E48E361127CE}\1.0] @="ocxMFC ActiveX Control module" |
控件加入VB6.0,类型库也会被缓存至oca文件,这个缓存文件和ocx文件在同一目录下。
第6章 Office使用控件
在图1.4中,勾中"Available in "Insert Object" dialog"。创建出来的控件就可以被Word、Excel做为对象插入。如下图所示:
图6.1
上图列出的是HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID下可插入的COM类。显示的文本"OcxMFC Control"在注册表中的位置见下表。
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{091AEC6D-3B16-4F1A-95FA-94E8C48E3366}] @="OcxMFC Control" |
在Word 2003里插入一个控件后,该控件的类型库将被缓存至C:\Users\Administrator\AppData\Local\Temp\Word8.0目录,文件后缀名为exd。下次再插入同样的控件,会使用这个exd文件里的类型库,而不是控件嵌入的类型库。这就意味着:一旦控件COM接口有所更新,Word里并不会反应这些变化。此时,必须删除相应的exd文件。