• ActiveX控件的实现


     
    • Ø         了解什么是ActiveX控件
    • Ø         掌握如何编写基于MFCActiveX控件
    • Ø         掌握如何测试ActiveX控件
    • Ø         掌握如何测试ActiveX控件
    • Ø         了解ActiveX控件如何注册

    重点:ActiveX控件的实现、测试及注册。

    本章将介绍ActiveX控件的应用与工作原理。我们可以把ActiveX控件看做是一个极小的服务器应用程序,它不能独立运行,必须嵌入到某个容器程序中,与该容器一起运行。那么,该容器就相当于客户程序,它使用ActiveX提供的服务。

    注:本文改编自孙鑫教程,在此基础上加入了Active控制与网页的交互。

    1.1 什么是ActiveX控件

    Activex控件是微软提供的功能强大的程序设计和开发技术。ActiveX是基于OLECOM的一门开发技术,它既是一个自动化对象,也是一个COM对象。根据微软权威的软件开发指南MSDNMicrosoft Developer Network)的定义,ActiveX控件以前也叫做OLE控件或OCX控件,它是一些软件组件或对象,可以将其插入到WEB网页或其它应用程序中。在形态上ActiveX控件是后缀名为ocx的控件,但是,读者应该注意的是,Activex控件对应的文件也可以是其他后缀名,如.dll

    一个典型的ActiveX控件,它具有方法、属性、事件这三种特性。

    1.2 ActiveX控件的好处

    在实际编程中,我们可以将常用的功能封装在一个ActiveX控件中,然后将该控件提供给VBDelphi的开发人员使用。例如,我们开发了一个中国地图控件,正好有一个公司有许多分支机构,如麦当劳,它会不断地在全国各地增加它的加盟店,而麦当劳公司总部需要实时地观测它每月新增的这些加盟店的地理位置。于是麦当劳公司就可以直接购买我们开发的这个地图控件,在地图上显示它们各分支机构的位置,而不需要再自行开发这种控件了。现在很多公司都在做Activex控件的开发,将一些常用功能封装到一个ActiveX控件中,然后提供给其他公司或最终用户直接使用。

    1.3 MFC编写基本的ActiveX控件

    下面,我们利用VC++编写一个ActiveX控件,这可以利用MFC ActiveX ControlWizard为我

    生成一个ActiveX控件程序的框架。MFCActiveX控件的开发提供了很好的支持,对ActiveX来说,它的底层实际上是采用COM技术实现的,但是利用MFC ActiveX ControlWizard,即使对COM不了解,我们也可以开发出一个功能完善的ActiveX控件。

    本例将开发一个时钟控件。在VC++开发环境中,选择【File\New】菜单项,在打开的对话框上选择Projects选项卡,并在列表框中选择MFC ActiveX ControlWizard,工程名设置为:Clock。单击【OK】按钮,进入MFC ActiveX ControlWizard向导的第一步,如下图所示:

    这里,第一个选项的作用是询问用户该工程中将要提供的控件数目。注:一个文件中可以包含多个ActiveX控件。本例中,我们对以上选项都选择默认。

    单击【Next】按钮,进入MFC ActiveX ControlWizard向导的第二步,如下图所示。

    单击【Finish】,就创建了一个MFC ActiveX控件工程。我们可以看到,MFC ActiveX ControlWizard向导创建的工程自动生成了三个类,如下图所示:

    其中CClockApp类派生于COleControlModule类,而后者的派生层次见下图

    可以看到,COleControlModule类是从CWinApp类派生的,所以可以把该类看作是一个应用程序类,它的实例表示了控件程序本身。也就是说,CClockApp类相当于单文档应用程序的应用程序类。

    CClockCtrl类派生于COleControl类,后者的派生层次结构如下图所示:

    可以看到,COleControl类是从CWnd类派生的,因此,它也是一个窗口类,相当于单文档应用程序中的主窗口类,或者视类,那么对控件窗口进行的操作都将在CClockCtrl类中完成。在该类中,可以看到它提供了一个OnDraw函数,当控件窗口发生重绘时就会调用这个函数。如果控件需要输出图形,就可以在这个函数中编写相应的实代码。

    我们先来看看CClockCtrl类头文件中的部分内容:

    // Message maps

           //{{AFX_MSG(CClockCtrl)

                  // NOTE – ClassWizard will add and remove member functions here.

                  //    DO NOT EDIT what you see in these blocks of generated code !

           //}}AFX_MSG

           DECLARE_MESSAGE_MAP()

    // Dispatch maps

           //{{AFX_DISPATCH(CClockCtrl)

                  // NOTE – ClassWizard will add and remove member functions here.

                  //    DO NOT EDIT what you see in these blocks of generated code !

           //}}AFX_DISPATCH

           DECLARE_DISPATCH_MAP()

           afx_msg void AboutBox();

    // Event maps

           //{{AFX_EVENT(CClockCtrl)

                  // NOTE – ClassWizard will add and remove member functions here.

                  //    DO NOT EDIT what you see in these blocks of generated code !

           //}}AFX_EVENT

           DECLARE_EVENT_MAP()

    我们可以看到,在该文件中不仅提供了一个消息映射,它还提供了一个调度映身和事件映射。其中调度映射是MFC提供的一种映射机制,主要是为了让外部应用程序可以方便地访问控件的属性和方法,而事件映射也是MFC提供的一种映射机制,让控件可以向包含它的容器发送事件通知。稍后我们为Clock控件添加方法和属性时就会用到这两个映射。

    CClockPropPage类派生于COlePropertyPage类,后者的派生层次结构如下图所示:

    可以看到,COlePropertyPage类派生于CDialog类,它以一种类似于对话框的图形界面显示一个自定义控件的属性。也就是说,CClockPropPage类是用来显示Clock控件的属性页的。

    另外,读者可以看到在该工程中还有两项内容:_DClock_DClockEvents,之母们的前面都有一个像平放着小勺一样的图标(),该图标表示对应的项是接口,接口是控件与外部程序进行通信的协议。可以把接口看作是函数的集合,外部程序通过这个接口所暴露出来的方法去访问控件的属性和方法。实际上,可以把接口看作是一个抽象基类,在此接口中定义的所有函数都是纯虚函数,这些函数的实现是在CClockCtrl类中完成的。MFC通过底层的封装,让CClockCtrl类继承自接口:_DClock,所以通过该接口调用的函数实际上是调用CClockCtrl类中真正实现的函数。ActiveX控件中的接口与计算机机硬件的接口是类似的,例如,在计算机硬件中,主板与显卡间的通信是通过主板上的插槽完成的,这个插槽就是主板与显卡进行通信的接口,一旦我们制定了这个接口,就可以任意地选择一块主板与一块显卡进行通信。因为该接口是标准的,所以选择任一厂商生产的主板,任一厂商生产的显示都是可以的,只要它们的接口遵从共同的标准。主板通过该接口所暴露出来的方法去调用显卡的显示功能,而显卡需要实现该接口所暴露出来的方法。显卡就相当于这里的ActiveX控件,而主板就相当于与控件通信的外部容器。如果两个通信实体要通过接口进行通信,那么肯定是其中的一个实体实现该接口所暴露出来的方法,而另一个实体通过接口调用这些方法。这里,就是ActiveX控件实现接口所暴露出来的方法,而容器调用这些方法。关于接口的底层实现,需要了解一些COM的基本知识,读者如感兴趣的话,可自行查看相关资料。本例中,因为MFC提供的封装,所以底层的细节是看不到的。

    这里,我们利用BuildF7)命令生成Clock控件程序,然后在该工程所在目录的Debug目录下,可以看到生成了一个Clock.ocx文件,这就是程序生成的ActiveX控件文件。在使用时,只需要将这个文件传递给使用方,经过注册后就可以使用该控件了。

    1.4 ActiveX控件的测试

    我们在VC++开发环境中运行Clock程序,将出现如下对话框,让用户选择一个可执行程序。

    前面已经提到,ActiveX控件不能独立运行,它必须嵌入到一个容中运行。因此,我们可以点击该对话框上标示了一个向右箭头的按钮,将弹出如下的快捷菜单。

    可以选择【ActiveX Control Test Container】菜单项,也就是说,我们选择ActiveX Control Test Container这个应用程序作为Clock控件的容器,该应用程序位于Microsoft Visual Studio安装目录下的Commaon\Tools子目录下,程序名称为:TSTCON32.EXE。如果没有出现这个应用程序所对应的菜单项,那么可以选择【Browse】菜单项,然后找到TSTCON32.EXE程序并选中即可。

    最后,单击【OK】按钮,这时将打开ActiveX Control Test Container应用程序,如下图

    于是我们就可以加载特定的ActiveX控件,方法是选择【Edit\Insert New Control…】菜单项,这时将弹出如下对话框

    然后在该对话框左边的列任意选中一个控件,接着快速连续地按下键盘上的【C】、【L】、【O】键,就可以直接定位到我们刚刚生成的Clock控件。

    然后单击对话框上的【OK】按钮关闭该对话框,这时,在ActiveX Control Test Container应用程序中就加载了Clock控件,这个ActiveX控件当前的功能就是绘制一个椭圆,如下图所示:

    当然,我们也可以新建一个VC++对话框工程来进行测试,该工程取名为ClockTest。如果想要在对话框资源上添加一个ActiveX控件,方法是:在对话框资源上单击鼠标右键,从弹出的快捷菜单中选择【Insert ActiveX Control…】菜单项,这时将显示如下对话框,在此对话框中找到Clock控件并选中,然后单击【OK】按钮关闭该对话框即可。

    这时,在对话框资源上就插入了Clock控件。

    VC++中,另一种插入ActiveX控件的方法是,选择【Project\Add to Project\Componets and Controls…】菜单项,将显示如下对话框:

    在此对话框中,双击“Registered ActiveX Controls”目录,并在此目录下找到Clock控件并选中,如下所示:

     

    然后单击【Insert】按钮,并单击随后显示的确认对话框上的的【确定】按钮,这时将弹出如下对话框:

    当通过这种方法插入ActiveX控件时,会在工程中为该控件生成一个类,这里就为Clock控件生成了一个类,类名为CClock,其基类是CWnd。该类是控件的封装类。它封装了对这个ActiveX控件进行访问的一些操作。单击【OK】按钮关闭该对话框,这时,在ClockTest工程的ClassView选项卡上,可以看到增加了一个类:CClock,该类提供了一些函数,我们只需要调用这些函数就可以访问Clock这个ActiveX控件的方法和属性。同时,在工具箱上也增加了Clock控件的图标,如下图所示:

    我们只需要单击该图标,就可以在对话框资源上拖放一块合适的区域放置一个Clock控件。如下图:

    1.5 ActiveX控件的注册

    所有的ActiveX控件必须注册才能使用。实际上,当在VC++开发环境中生成Clock控件程序时,输出窗口如下图所示:

    我们看到Registering ActiveX Control…,表明在生成Clock控件程序时,VC++环境已经帮我们注册了该控件。实际上,VC++是编译器是通过调用regsvr32程序去完成这个操作的。

    如果想要删除ActiveX控件的注册信息,可以利用Regsvr32程序的/u选项来实现。我们可以选择系统的【开始\运行】命令,然后在对话框上的打开编辑框控件中输入regsvr32 /u,再在其后输入想要删除的ActiveX控件的完整路径,如下图所示。

    单击【确定】按钮,这时会弹出如下所示的信息对话框:

    该信息框中提示“DllUnregisterServer in D:\Code\Clock\Debug\Clock.ocx succeeded”。这里DllUnregisteredServer是一个函数,并且是ActiveX控件提供的一个函数。“regsvr32 /u”这一命令执行是实际上调用的是指定控件的DllUnregisterServer函数来删除控件的注册信息,因为对于regsvr32程序来说,它并不知道需要删除哪些信息,所以它只是调用控件的DllUnregisterServer函数,由后者来删除该控件在注册表中的注册信息。

    当删除了Clock控件在注册表中的信息之后,如果在ActiveX Control Test Container程序中再想加载Clock控件时,在控件列表中就找不到这个控件了。

    如果想再次注册Clock控件,仍可以选择regsvr32程序,但不需要使用/u选项,其他同上。这时将显示如下所示对话框

    在该信息框中提示:“DllRegisterServer in D:\Code\Clock\Debug\Clock.ocx succeeded”。同样的,DllRegisterServer也是ActiveX控件提供的一个函数。当执行regsvr32这一命令时,它实际上是调用指定控件的DllRegisterServer函数,将该控件的信息写入注册表。因此,实际上,ActiveX控件的注册和取消注册都是利用该控件自身提供的两个函数来完成的,regsvr32程序只是调用这两个函数而已。当注册完成后,在ActiveX Control Test Container程序的控件列表中就可以找到Clock控件了。

    1.6 时间控件的实现

    下面继续完成Clock控件的实现,让该控件显示系统当前时间,这可以在CClockCtrl类的OnDraw函数中完成。这时,该函数中已经自动生成了两行代码,分别用来填充控件的背景和绘制椭圆,我们先将这两代码注释起来,然后添加如下代码:

    void CClockCtrl::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);

           CTime time=CTime::GetCurrentTime();

           CString str=time.Format(“%H:%M:%S”);

           pdc->TextOut(0,0,str);

    }

    如果想要获得当前系统时间,可以使用CTime类的静态方法:GetCurrentTime,该函数将返回表示系统当前时间的CTime对象,之后就可以利用CTime对象的Format方法对得到的CTime类型的时间进行格式化,返回一个CString对象,然后将显示时间的字符串显示在控件窗口中。

    VC++开发环境中,利用Build命令生成Clock控件程序,并运行,如下图所示:

    可是,这时控件显示的时间是静止的,为了让该时间“动起来”,我们需要设置一个定时器,让它每隔一秒钟发送一个WM_TIMER消息,在响应该定时器的消息处理函数中,让该控件刷新,重新办理出当前系统时间。这里,我们需要在控件窗口创建完成之后设置定时器,为此我们需要为CClockCtrl类增加WM_CREATE消息的处理函数,然后在些函数中,在控件窗口创建完成之后,调用SetTimer函数创建定时器。具体代码如下所示:

    int CClockCtrl::OnCreate(LPCREATESTRUCT lpCreateStruct)

    {

           if (COleControl::OnCreate(lpCreateStruct) == -1)

                  return -1;

          

           // TODO: Add your specialized creation code here

          

           SetTimer(1,1000,NULL);

           return 0;

    }

    接下来,再为CClockCtrl类增加Windows消息:WM_TIMER的处理,在其响应函数OnTimer中调用Invalidate函数,使窗口无效,这样就可以使窗口重绘。具体实现代码如下所示:

    void CClockCtrl::OnTimer(UINT nIDEvent) {

           // TODO: Add your message handler code here and/or call default

           Invalidate();

           COleControl::OnTimer(nIDEvent);

    }

    Build并运行Clock控件,将会看到这时这个时钟控件显示的时间随系统当前时间变化而变化了。

    1.7 属性

    读者可以发现,在VB中提供了一个如下图所示的属性面板,通过此面板,可以修改控件属性的值。该面板的左边列出了控件的一些属性,对Clock控件来说,当前我们没有为它添加任一属性,都是MFC ActiveX ControlWizard自动生成的属性;面板的右边就是属性对应的值,例如,控件的Name(名称)属性是Clock1。如果我们想要改变Clock控件的前景色和背景色,却发现Clock控件的前景色和背景色,却发现在Clock控件的属性面板中没有看到前景色和背景色这两种属性。但是如果在VB程序的窗体上放置一个列表框控件,然后在属性面板上就可以看到该控件有BackColor(背景色)和ForeColor(前景色)属性,可以用来设置该控件的背景色和前景色。如果希望为Clock控件也提供这样的属性,让用户可以设置该控件的前景色和背景色,那么就需要在VC++开发环境中继续完善Clock控件,为它添加这样的属性。

    1.7.1   标准属性

    VC++开发环境中,如果想要为控件添加属性,可以通过ClassWizard来完成。首先打开ClassWizard对话框,然后选择Automation选项卡,接着单击该选项卡上的【Add Property…】按钮,将出现如下图所示的添加属性对话框:

    在此对话框上,单击External name(外部名称)下拉列表框,将会看到在出现的列表框中有许多属性,这些都是MFCActiveX控件提供的标准属性,其中就有BackColor(背景色)和ForeColor(前景色)属性,如果想要为控件添加某种标准属性,只要从该列表中选择该属性,例如选择BackColor,并保持默认的Stock选项选中状态,单击【OK】按钮即为控件添加了背景色属性。然后按照同样的方法为Clock控件添加ForeColor属性。这时,Clock控件的Automation选项卡内容如下图所示:

    可以看到,新添加的这两个属前面都有一个“S”标志,而且下面的提示说明它们是一个“Stock Property”,即常规的,或储备的属性。在ActiveX控件中有四种属性。

    l         Stock为每个控件提供的标准属性,如字体或颜色。

    l         Ambient围绕控件的环境属性——已被置入容器的属性。这些属性不能被更改,但控件可以使用它们调整自己的属性。

    l         Extended这些是由容器处理的属性,一般包括大小和在屏幕上的位置

    l         Custom由控件开发者添加的属性。

    单击上图所示对话框上的【OK】按钮关闭ClassWizard对话框,然后,在VC++开发环境中的Class View选项上的_DClock接口下,可以看到添加了两个属性:BackColorForeColor

    再次利用Build命令生成Clock控件。

    1.7.2   自定义属性

    当前Clock控件是每隔1秒更新一次时间的显示,接下,我们给Clock控件增加一个自定义的属性:时间间隔,在用户设置了该属性的值以后,Clock控件就按照用户指定的时间间隔值来更新显示的时间。

    这时同样需要利用ClassWizard来了Clock控件添加属性,并且也是选择ClassWizard对话框上的Automation选项卡,然后单击【Add Property】按钮,将弹出增加属性对话框。在该对话框中有几项内容,其中External name(外部名称)是在像VB这样的集成开发环境中所看到的控件属性名称,而Variable name(变量名称)是在VC++集成开发环境中开发这个控件时使用的该控件类的成员变量。也就是说,在开发程序中使用Variable name访问控件属性,而在外部使用该控件时,使用的是External name访问控件的属性。这里,我们将新添加的时间间隔属性的外部名称设置为Inverval,类型选择为short类型,变量名称自动被设置为m_interval,ClassWizard为该控件自动增加了一个通知函数:OnIntervalChanged(如下图所示),当在外部修改该属性时,这个函数将被调用。

    可以看到,在添加属性对话框上为我们提供了三个单选按钮,但这时只有两个选项可供选择,默认选择的是Member variable,当选择该选项后,ClassWizard会为该属性生成一个成员变量,并生成一个通知函数,正如上图所示的那样;如果选择Get/Set methods选项,这时添加属性对话框就变成了下图所示的样子。可以看到,这时在添加属性对话框中就没有成员变量和通知函数这两个选项了,ClassWizard会为该属性自动生成两个函数:SetIntervalGetInterval。在程序中,如果想要设置Interval属性的值,可以调用SetInterval函数;如果想要得到该属性的值,可以调用GetInterval函数。但在控件内部,如果想要保存Interval这个属性的值,需要我们自已定义一个成员变量来实现。刚才我们已经看到,如果选择Member variable选项,ClassWizard会自动生成一个这样的成员变量,本例保持默认设置,即选择Member variable选项。

    然后,单击添加属性对话框上的【OK】按钮完成Interval属性的添加,并单击ClassWizaard对话框上的【OK】按钮关闭ClassWizard对话框。这时,在VC开发环境中,在ClassView选项卡上,可以看到_DClock接口中又增加了一个属性:Interval,并且在CClockCtrl类中增加了一个函数:OnIntervalChanged。当Interval这一外部属性被修改时,就会调用这个OnIntervalChanged函数。该函数的默认实现代码如下所示:

    void CClockCtrl::OnIntervalChanged()

    {

           // TODO: Add notification handler code

           SetModifiedFlag();

    }

    可以看到,此函数中调用了一个名为SetModifiedFlag函数,根据字面的意思,可以猜测到该函数是用来设置属性被修改的标记。

    另外,可以发现,ClassWizard还为CClockCtrl类提供了一个成员变量:m_interval,其定义代码如下所示:

    // Dispatch maps

           //{{AFX_DISPATCH(CClockCtrl)

           short m_interval;

           afx_msg void OnIntervalChanged();

           //}}AFX_DISPATCH

           DECLARE_DISPATCH_MAP()

    可以看到,增加的m_intervalOnIntervalChanged函数的定义都位于CClockCtrl类的调度映射中。前面已经介绍过,调度映射主要是为了让外部应用程序可以方便地访问控件的属性和方法。

    接下来,我们就在OnIntervalChanged函数中根据用户输入的时间间隔值控制Clock控件的显示更新。具体代码如下所示:

    void CClockCtrl::OnIntervalChanged()

    {

           // TODO: Add notification handler code

           if (m_interval<0||m_interval>6000)

           {

                  m_interval=1000;

           }

           else

           {

                  m_interval=m_interval/1000*1000;

           }

           KillTimer(1);

           SetTimer(1,m_interval,NULL);

           SetModifiedFlag();

    }

    因为时间间隔不能为负数,也不能太大。所以在OnIntervalChanged函数中,首先对m_interval变量的值进行判断,如果用户设置的时间间隔属性值小于0,或者大于6000,则就将这个间隔值设置为1000。否则,进行调整,即对用户输入的值取整,得到一个整数的秒数。接下来,调用KillTimer函数销毁先前设置的定时器(其标识是1),时间间隔用Clock控件的m_interval属性值来设置。

    利用Build命令生成最新的Clock控件,然后利用ActiveX Control Test Container容器测试该控件。在利用【Edit\Insert New Control…】命令插入该控件后,为了测试控件的属性,需要选中该控件,然后单击【Control\Invoke Methods…】菜单项,这时将显示如下的对话框:

    在此对话框中有一个方法名称(Method Name)下拉列表,在此列表中列出了当前控件提供的方法,如下图所示:

    如果想要得到某个属性值,应该选择PropGet类型的方法:如果想要设置某个属性的值,则应该选择PropPut类型的方法。这里我们想要设置Clock控件的Interval属性的值,因此应该选择IntervalPropPut)项,并在随后出现的对话框的Parameter编辑框中输入数值:2000,单击【Set Value】按钮,这时就把Interval属性的值设置为2000了,如下图所示

    但是,这时这个属性值仍未生效,需要单击【Invoke】按钮才行。之后就会发现Clock控件显示的时间每隔2秒跳动一次,说明设置生效了。

    1.8 方法

    下面,为Clock控件添加一个自定义的方法。同样,这也是利用ClassWizard来完成的。首先打开ClassWizard对话框,选择Automation选项卡,注意:在此属性页上,class name这一选项一定要选择CClockCtrl。然后,【Add Method】按钮,这时将出现如下对话框:

    该对话框中提供了几个选项,其中外部名称(External name)是给外部程序使用控件的方法时使用的,这里,我们可以将其设置为Hello。读者可以看到,系统自动为该方法提供了一个内部名称(Internal name):Hello,这个内部名称是在控件内部使用的方法名称,它可以与外部名称不一样。然后将返回类型(Return type)设置为void,不用给这个方法设置参数。如下所示:

    然后单击【OK】按钮关闭添加方法对话框,并单击ClassWizard对话框上的【OK】按钮关闭该对话框。这时,在ClassView选项中,可以看到在_DClock接口下增加了一个方法:Hello,该方法前面是用一个绿色的小方块表示的。同时,在CClockCtrl类中提供了该方法的实现,这时该方法的实现代码是空的。在此方法中,我们可以使用MessageBox函数显示一个消息框,其中显示字符串:“Hello world!”。具体代码如下:

    void CClockCtrl::Hello()

    {

           // TODO: Add your dispatch handler code here

           MessageBox(“Hello world!”);

    }

    利用Build命令生成最新的Clock控件,再次利用ActiveX Control Test Container容器测试该控件。在该容器中调用控件方法的步骤是:选中Clock控件,选择【Control\Invoke Methods…】菜单项,这时将打开Invoke Methods对话框,在此对话框的Method Name下拉列表框中选择“Hello”方法,然后单击【Invoke】按钮,就会调用Clock控件的Hello方法,将出现如下图所示的消息框:

    1.9 事件

    ActiveX控件有两种事件:标准事件和自定义事件。

    1.9.1   标准事件

    VC++中,如果想要为Clock控件添加一个事件,可以利用ClassWizard来完成。首先打开ClassWizard对话框,并打开它的ActiveX Events选项卡,在此选项卡上,确保Class name组合框中选择的是CClockCtrl。然后单击【Add Event】按钮,将显示添加事件对话框,在此对话框上有一个名称为External name的组合框,当单击其右边向下的箭头时,将会看到该列表框中列出了一些预先准备好的事件(如下图所示),即MFC提供的一些标准的事件,例如Click事件。这里,我们先为Clock控件增加一个标准事件,也就是一个Stock事件。在External name下拉列表中选择Click,保持默认的Stock选项不变,然后单击【OK】按钮关闭Add Event对话框,并单击ClassWizard对话框上的【OK】按钮,关闭该对话框。

    这时,在ClassView选项卡中可以看到,在_DClockEvents接口下面增加了一个方法:Click,该方法就是刚刚添加的Click事件。为什么添加的事件增加到_DClockEvents接口中,而没有放到_DClock接口中呢?读者可以在Clock.odl文件的最后看到如下代码段:

    //  Class information for CClockCtrl

           [ uuid(8377E215-598D-4F31-8BDE-0E16AFF83A9A),

             helpstring("Clock Control"), control ]

           coclass Clock

           {

                  [default] dispinterface _DClock;

                  [default, source] dispinterface _DClockEvents;

           };

    在上述所示的代码中,可以看到在说明_DClockEvents接口时,其前面有一个“source”标识,而_DClock接口前面并没有此标识。“source”标识表明_DClockEvents接口是一个源接口。源接口表示控件将使用这个接口来发送通知事件,这个接口不是控件本身实现的接口。前面已经提过,作为利用接口进行通信的双方,肯定是一方调用接口所暴露出来的方法,另一方实现该接口所提供的方法。我们现在所实现的Clock控件正是调用_DClockEvents接口提供的方法,向容器发出事件通知。既然是控件使用_DClockEvents接口提供的方法,那么谁负责实现这个方法呢?实际上,_DClockEvents接口中的方法是由容器实现的。容器通过一种机制知道控件中定义了一个源接口,于是它就实现该接口。这里,读者可能会有这样的疑问,为什么容器实现的接口由控件定义呢。一方面,对于每个控件来说,它可以有自己的事件接口,而容器是无法预先知道控件将使用哪一个事件接口发出通知,因此我们在编写控件的同时指定事件接口,并将其标识为源接口。另一方面,接口由谁来定义是无所谓的,例如,主板与显卡进行通信,那么是主板厂商去定义接口,还是由显卡产商去定义接口,或者它们一起来定义接口,这都是一样的,关键是通信的双方能够遵照一个接口进行通信就可以了。

    现在,我们已经为Clock控件增加了一个标准事件:Click,再次利用ActiveX Control Test Container容器测试该控件。当插入Clock控件后,在此控件上单击鼠标左键,这时,在该容器下面的窗口中可以看到这样一句话:Clock Control:Click,即触发了Clock控件的Click事件,如下图所示:

    我们也可以用前面新建的VC++工程ClockTest来测试,打开【ClassWizard】,选择【Message Maps】选项卡,选中IDC_CLOCKCTRL1,我们发现它对应一个Click消息,就是我们刚才为Clock控件添加的Click事件。如下图所示:

    点击【Add Function…】,为其添加一个消息处理,如下所示:

    点击【OK】关闭对话框,再点击ClassWizard上的【Edit Code】按钮,添加消息响应代码:

    void CClockTestDlg::OnClickClockctrl1()

    {

           // TODO: Add your control notification handler code here

           MessageBox(“Clock Clicked”);

    }

    编译运行该程序,我们在Clock控件上单击鼠标左键,弹出如下消息框:

    这是因为当在Clock控件上单击鼠标左键时,该控件接收到该单击消息,于是它就利用_DClockEvents接口中的方法(即Click方法)向容器(即ClockTest对话框)发出事件通知,因为_DClockEvents这个源接口是容器实现的,相当于控件调用了容器的Click方法,实际上就是调用了OnClickClockctrl1这个消息响应函数中的代码。

    1.9.2   自定义事件

    VC++中,为了给ActiveX控件增加自定义事件,同样可以利用ClassWizard来完成,与上面添加标准事件的过程是一样的。另外,也可以在工程的ClassView选项卡上,用鼠标右键单击_DClockEvents接口,并从弹出的快捷菜单中选择【Add Event…】菜单项,从而也可以打开添加事件对话框。利用该对话框,我们为Clock控件添加一个自定义的事件,新添加的这个事件的外部名称设置为:NewMinute,系统将自动将该事件的内部名称设置为:FireNewMinute,结果如下所示:

    单击【OK】按钮对话框。这时,在ClassView选项卡中,可以看到_DClockEvents接口下又增加了一个方法:NewMinute,并且在CClockCtrl类中增加了一个FireNewMinute方法。这样,在控件内部,就可以调用FireNewMinute方法向容器发出事件通知,而在此方法内部,它会调用_DClockEvents接口中的NewMinute方法向容器发出事件通知。我们发现,在ClockCtrl.h中,自动生成的FireNewMinute方法的代码如下:

    // Event maps

           //{{AFX_EVENT(CClockCtrl)

           void FireNewMinute()

                  {FireEvent(eventidNewMinute,EVENT_PARAM(VTS_NONE));}

           //}}AFX_EVENT

           DECLARE_EVENT_MAP()

    对于上面添加的Click事件来说,因为它是MFC提供的一个标准事件,它的触发过程被底层屏蔽了,所以我们没有看到。而对于自定义的事件来说,必须在某个条件到来时,显式地调用某个函数发出该事件通知。本例中,我们可以在新的一分钟到达时,发出NewMinute事件通知。因此,在CClockCtrl类的OnDraw函数中,在调用GetCurrentTime函数得到系统时间之后,添加下述代码:

    if (0==time.GetSecond())       {

                  FireNewMinute();

           }

    也就是说,在得到当前系统时间之后,首先应对秒数进行判断,如果秒数为0,即到达了新的一分钟,就调用FireNewMinute方法,向容器发出NewMinute事件通知。而NewMinute事件是由容器实现的。

    我们通过前面新面的ClockTest程序来测试,打开【ClassWizard】,选择【Message Maps】选项卡,选中IDC_CLOCKCTRL1,我们发现它对应一个NewMinute消息,就是我们刚才为Clock控件添加的NewMinute事件。如下图所示:

    单击【Add Function】,弹出如下对话框:

    单击【OK】关闭对话框,并在ClassWizard上单击【EditCode】,为其添加消息响应代码:

    void CClockTestDlg::OnNewMinuteClockctrl1()

    {

           // TODO: Add your control notification handler code here

           MessageBox(“New Minute”);

    }

    编译运行程序,我们发现新的一分钟到来的时候会弹出如下消息框:

    这是因为当新的一分钟到来时,Clock控件就会调用FireNewMinute方法,向容器(即ClockTest对话框)发出NewMinute事件,而容器接收到这一事件后,会调用OnNewMinuteClockctrl1来响应。

    同样,也可以用ActiveX Control Test Container容器测试该控件。当插入该控件后,可以看到当该控件上显示的时间一旦到达新的一分钟时,该容器下面的窗口中就会显示这样一句话:Clock Control:NewMinute,即触发了一个NewMinute事件。如下图所示:

    到此为止,我们为Clock控件添加了一个标准事件:Click,和一个自定义事件:NewMinute。读者一定要注意,对标准事件来说,其触发过程由MFC底层实现。但对自定义事件来说,必须要在某个条件到来时,在代码中显式地调用某个函数发出该事件通知。

    1.10    ActiveX控件与网页的交互

    当我们在互联网上畅游的时候,经常会碰到IE浏览器提示我们下载安装某些插件,这些就是所谓的ActiveX插件,那么ActiveX控件是如何嵌入到到网页中的,以及如何与网页通信的呢,这就是本节我们要讲的内容。

    我们用记事本编辑一个html文件,代码如下:

    <html>

        <head>

            <title>时钟控件测试</title>

            <meta http-equiv=”Content-Type” content=”text/html; charset=gb2312″>

            <meta name=”GENERATOR” content=”Microsoft FrontPage 4.0″>

            <meta name=”ProgId” content=”FrontPage.Editor.Document”>

        </head>

      

        <script type=”text/javascript” language=”javascript”>

        <!–

            function On_PageLoad()

            {

            }

     

            function Hello_onclick()

            {

                ClockCtrl.Hello();

            }

          

        </script>

     

        <body onLoad=”return On_PageLoad()”>

      

            <SCRIPT LANGUAGE=”JScript” EVENT=”NewMinute()” FOR=”ClockCtrl”>

               alert(“new minute”);

            </SCRIPT>

            <OBJECT name=”Clock” id=”ClockCtrl” height=”80″ width=”180″ classid=”clsid:8377E215-598D-4F31-8BDE-0E16AFF83A9A”>

            </OBJECT>

            <hr/>

                  <INPUT id=”hello” type=”button” value=”执行Hello” name=”ButtonStop” onClick=”return Hello_onclick()”/>

        </body>

    </html>

     

    下面解释一下这段代码:

    <OBJECT name=”Clock” id=”ClockCtrl” height=”80″ width=”180″ classid=”clsid:8377E215-598D-4F31-8BDE-0E16AFF83A9A”>       </OBJECT>

    这段代码表示初始一个ActiveX控件对象,前面我们已经讲过ActiveX控件实际上是一个COM组件,他是需要在注册表中注册之后才能使用的,其中clsid:8377E215-598D-4F31-8BDE-0E16AFF83A9A代表在注册表中注册的classid,我们可以打开注册表编辑器,搜索“Clock”,找到Clock在注册表的注册信息,如下图所示:

    实际上,这个classid值也可以在我们的Clock控件程序代码中找到,我们打开Clock.odl文件,在其最下面就可以找到,如下所示:

    //  Class information for CClockCtrl

           [ uuid(8377E215-598D-4F31-8BDE-0E16AFF83A9A),

             helpstring("Clock Control"), control ]

           coclass Clock

           {

                  [default] dispinterface _DClock;

                  [default, source] dispinterface _DClockEvents;

           };

    <SCRIPT LANGUAGE=”JScript” EVENT=”NewMinute()” FOR=”ClockCtrl”>

    alert(“new minute”);

    </SCRIPT>

    这段代码表示网页测试程序去订阅Clock时钟控件暴露出来的事件NewMinute,一旦新的一分钟到来的时候就会弹出如下对话框:

    这样就完成了Clock控件与网页程序的通信。

    <INPUT id=”hello” type=”button” value=”执行Hello” name=”ButtonStop” onClick=”return Hello_onclick()”/>

    这段代码代表单击标题为“执行Hello”的按钮,将执行Hello_onclick()函数:

    function Hello_onclick()

    {

        ClockCtrl.Hello();

    }

    这段代码执行时钟控件暴露出来的方法Hello(),弹出如下对话框:

     

    这样就完成了网页程序与时钟控件的通信。

    总结:ActiveX控件与网页程序的通信是通过暴露事件,网页程序去订阅该事件,一旦事件发生的条件满足,ActiveX控件就会通知给网页程序,从而实现了ActiveX控件与网页程序的通信。网页程序与ActiveX控件的通信是通过网页程序调用ActiveX控件暴露出来的方法。

     
    •  
     
     
  • 相关阅读:
    【leetcode】1630. Arithmetic Subarrays
    【leetcode】1629. Slowest Key
    【leetcode】1624. Largest Substring Between Two Equal Characters
    【leetcode】1620. Coordinate With Maximum Network Quality
    【leetcode】1619. Mean of Array After Removing Some Elements
    【leetcode】1609. Even Odd Tree
    【leetcode】1608. Special Array With X Elements Greater Than or Equal X
    【leetcode】1603. Design Parking System
    【leetcode】1598. Crawler Log Folder
    Java基础加强总结(三)——代理(Proxy)Java实现Ip代理池
  • 原文地址:https://www.cnblogs.com/qmfsun/p/3132004.html
Copyright © 2020-2023  润新知