<a href="http://www.ctdisk.com/file/6533040" target="_blank">本文源碼與文檔下載下傳</a>
本章目标:
完成本章學習後,您将能夠:
Ø 了解什麼是ActiveX控件
Ø 掌握如何編寫基于MFC的ActiveX控件
Ø 掌握如何測試ActiveX控件
Ø 了解ActiveX控件如何注冊
重點:ActiveX控件的實作、測試及注冊。
本章将介紹ActiveX控件的應用與工作原理。我們可以把ActiveX控件看做是一個極小的伺服器應用程式,它不能獨立運作,必須嵌入到某個容器程式中,與該容器一起運作。那麼,該容器就相當于客戶程式,它使用ActiveX提供的服務。
注:本文改編自孫鑫教程,在此基礎上加入了Active控制與網頁的互動。
Activex控件是微軟提供的功能強大的程式設計和開發技術。ActiveX是基于OLE和COM的一門開發技術,它既是一個自動化對象,也是一個COM對象。根據微軟權威的軟體開發指南MSDN(Microsoft Developer
Network)的定義,ActiveX控件以前也叫做OLE控件或OCX控件,它是一些軟體元件或對象,可以将其插入到WEB網頁或其它應用程式中。在形态上ActiveX控件是字尾名為ocx的控件,但是,讀者應該注意的是,Activex控件對應的檔案也可以是其他字尾名,如.dll。
一個典型的ActiveX控件,它具有方法、屬性、事件這三種特性。
在實際程式設計中,我們可以将常用的功能封裝在一個ActiveX控件中,然後将該控件提供給VB或Delphi的開發人員使用。例如,我們開發了一個中國地圖控件,正好有一個公司有許多分支機構,如麥當勞,它會不斷地在全國各地增加它的加盟店,而麥當勞公司總部需要實時地觀測它每月新增的這些加盟店的地理位置。于是麥當勞公司就可以直接購買我們開發的這個地圖控件,在地圖上顯示它們各分支機構的位置,而不需要再自行開發這種控件了。現在很多公司都在做Activex控件的開發,将一些常用功能封裝到一個ActiveX控件中,然後提供給其他公司或最終使用者直接使用。
下面,我們利用VC++編寫一個ActiveX控件,這可以利用MFC ActiveX
ControlWizard為我
生成一個ActiveX控件程式的架構。MFC為ActiveX控件的開發提供了很好的支援,對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)
//}}AFX_DISPATCH
DECLARE_DISPATCH_MAP()
afx_msg void AboutBox();
Event maps
//{{AFX_EVENT(CClockCtrl)
//}}AFX_EVENT
DECLARE_EVENT_MAP()
我們可以看到,在該檔案中不僅提供了一個消息映射,它還提供了一個排程映身和事件映射。其中排程映射是MFC提供的一種映射機制,主要是為了讓外部應用程式可以友善地通路控件的屬性和方法,而事件映射也是MFC提供的一種映射機制,讓控件可以向包含它的容器發送事件通知。稍後我們為Clock控件添加方法和屬性時就會用到這兩個映射。
CClockPropPage類派生于COlePropertyPage類,後者的派生層次結構如下圖所示:
可以看到,COlePropertyPage類派生于CDialog類,它以一種類似于對話框的圖形界面顯示一個自定義控件的屬性。也就是說,CClockPropPage類是用來顯示Clock控件的屬性頁的。
這裡,我們利用Build(F7)指令生成Clock控件程式,然後在該工程所在目錄的Debug目錄下,可以看到生成了一個Clock.ocx檔案,這就是程式生成的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控件并選中,如下所示:
當通過這種方法插入ActiveX控件時,會在工程中為該控件生成一個類,這裡就為Clock控件生成了一個類,類名為CClock,其基類是CWnd。該類是控件的封裝類。它封裝了對這個ActiveX控件進行通路的一些操作。單擊【OK】按鈕關閉該對話框,這時,在ClockTest工程的ClassView頁籤上,可以看到增加了一個類:CClock,該類提供了一些函數,我們隻需要調用這些函數就可以通路Clock這個ActiveX控件的方法和屬性。同時,在工具箱上也增加了Clock控件的圖示,如下圖所示:
我們隻需要單擊該圖示,就可以在對話框資源上拖放一塊合适的區域放置一個Clock控件。如下圖:
所有的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控件了。
下面繼續完成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控件,将會看到這時這個時鐘控件顯示的時間随系統目前時間變化而變化了。
讀者可以發現,在VB中提供了一個如下圖所示的屬性面闆,通過此面闆,可以修改控件屬性的值。該面闆的左邊列出了控件的一些屬性,對Clock控件來說,目前我們沒有為它添加任一屬性,都是MFC ActiveX
ControlWizard自動生成的屬性;面闆的右邊就是屬性對應的值,例如,控件的Name(名稱)屬性是Clock1。如果我們想要改變Clock控件的前景色和背景色,卻發現Clock控件的前景色和背景色,卻發現在Clock控件的屬性面闆中沒有看到前景色和背景色這兩種屬性。但是如果在VB程式的窗體上放置一個清單框控件,然後在屬性面闆上就可以看到該控件有BackColor(背景色)和ForeColor(前景色)屬性,可以用來設定該控件的背景色和前景色。如果希望為Clock控件也提供這樣的屬性,讓使用者可以設定該控件的前景色和背景色,那麼就需要在VC++開發環境中繼續完善Clock控件,為它添加這樣的屬性。
在VC++開發環境中,如果想要為控件添加屬性,可以通過ClassWizard來完成。首先打開ClassWizard對話框,然後選擇Automation頁籤,接着單擊該頁籤上的【Add
Property…】按鈕,将出現如下圖所示的添加屬性對話框:
在此對話框上,單擊External
name(外部名稱)下拉清單框,将會看到在出現的清單框中有許多屬性,這些都是MFC為ActiveX控件提供的标準屬性,其中就有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接口下,可以看到添加了兩個屬性:BackColor和ForeColor。
再次利用Build指令生成Clock控件。
目前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會為該屬性自動生成兩個函數:SetInterval和GetInterval。在程式中,如果想要設定Interval屬性的值,可以調用SetInterval函數;如果想要得到該屬性的值,可以調用GetInterval函數。但在控件内部,如果想要儲存Interval這個屬性的值,需要我們自已定義一個成員變量來實作。剛才我們已經看到,如果選擇Member variable選項,ClassWizard會自動生成一個這樣的成員變量,本例保持預設設定,即選擇Member variable選項。
然後,單擊添加屬性對話框上的【OK】按鈕完成Interval屬性的添加,并單擊ClassWizaard對話框上的【OK】按鈕關閉ClassWizard對話框。這時,在VC開發環境中,在ClassView頁籤上,可以看到_DClock接口中又增加了一個屬性:Interval,并且在CClockCtrl類中增加了一個函數:OnIntervalChanged。當Interval這一外部屬性被修改時,就會調用這個OnIntervalChanged函數。該函數的預設實作代碼如下所示:
CClockCtrl::OnIntervalChanged()
// TODO: Add notification handler
code
SetModifiedFlag();
可以看到,此函數中調用了一個名為SetModifiedFlag函數,根據字面的意思,可以猜測到該函數是用來設定屬性被修改的标記。
另外,可以發現,ClassWizard還為CClockCtrl類提供了一個成員變量:m_interval,其定義代碼如下所示:
// Dispatch maps
short m_interval;
afx_msg void
OnIntervalChanged();
可以看到,增加的m_interval和OnIntervalChanged函數的定義都位于CClockCtrl類的排程映射中。前面已經介紹過,排程映射主要是為了讓外部應用程式可以友善地通路控件的屬性和方法。
接下來,我們就在OnIntervalChanged函數中根據使用者輸入的時間間隔值控制Clock控件的顯示更新。具體代碼如下所示:
if
(m_interval<0||m_interval>6000)
{
m_interval=1000;
}
else
m_interval=m_interval/1000*1000;
KillTimer(1);
SetTimer(1,m_interval,NULL);
因為時間間隔不能為負數,也不能太大。是以在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屬性的值,是以應該選擇Interval(PropPut)項,并在随後出現的對話框的Parameter編輯框中輸入數值:2000,單擊【Set
Value】按鈕,這時就把Interval屬性的值設定為2000了,如下圖所示
但是,這時這個屬性值仍未生效,需要單擊【Invoke】按鈕才行。之後就會發現Clock控件顯示的時間每隔2秒跳動一次,說明設定生效了。
下面,為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
MessageBox(“Hello world!”);
利用Build指令生成最新的Clock控件,再次利用ActiveX Control Test
Container容器測試該控件。在該容器中調用控件方法的步驟是:選中Clock控件,選擇【Control\Invoke
Methods…】菜單項,這時将打開Invoke Methods對話框,在此對話框的Method
Name下拉清單框中選擇“Hello”方法,然後單擊【Invoke】按鈕,就會調用Clock控件的Hello方法,将出現如下圖所示的消息框:
ActiveX控件有兩種事件:标準事件和自定義事件。
在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】按鈕,添加消息響應代碼:
CClockTestDlg::OnClickClockctrl1()
// TODO: Add your control notification handler
code here
MessageBox(“Clock Clicked”);
編譯運作該程式,我們在Clock控件上單擊滑鼠左鍵,彈出如下消息框:
這是因為當在Clock控件上單擊滑鼠左鍵時,該控件接收到該單擊消息,于是它就利用_DClockEvents接口中的方法(即Click方法)向容器(即ClockTest對話框)發出事件通知,因為_DClockEvents這個源接口是容器實作的,相當于控件調用了容器的Click方法,實際上就是調用了OnClickClockctrl1這個消息響應函數中的代碼。
在VC++中,為了給ActiveX控件增加自定義事件,同樣可以利用ClassWizard來完成,與上面添加标準事件的過程是一樣的。另外,也可以在工程的ClassView頁籤上,用滑鼠右鍵單擊_DClockEvents接口,并從彈出的快捷菜單中選擇【Add
Event…】菜單項,進而也可以打開添加事件對話框。利用該對話框,我們為Clock控件添加一個自定義的事件,新添加的這個事件的外部名稱設定為:NewMinute,系統将自動将該事件的内部名稱設定為:FireNewMinute,結果如下所示:
單擊【OK】按鈕對話框。這時,在ClassView頁籤中,可以看到_DClockEvents接口下又增加了一個方法:NewMinute,并且在CClockCtrl類中增加了一個FireNewMinute方法。這樣,在控件内部,就可以調用FireNewMinute方法向容器發出事件通知,而在此方法内部,它會調用_DClockEvents接口中的NewMinute方法向容器發出事件通知。我們發現,在ClockCtrl.h中,自動生成的FireNewMinute方法的代碼如下:
// Event maps
void FireNewMinute()
{FireEvent(eventidNewMinute,EVENT_PARAM(VTS_NONE));}
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】,為其添加消息響應代碼:
CClockTestDlg::OnNewMinuteClockctrl1()
MessageBox(“New Minute”);
編譯運作程式,我們發現新的一分鐘到來的時候會彈出如下消息框:
這是因為當新的一分鐘到來時,Clock控件就會調用FireNewMinute方法,向容器(即ClockTest對話框)發出NewMinute事件,而容器接收到這一事件後,會調用OnNewMinuteClockctrl1來響應。
同樣,也可以用ActiveX Control Test
Container容器測試該控件。當插入該控件後,可以看到當該控件上顯示的時間一旦到達新的一分鐘時,該容器下面的視窗中就會顯示這樣一句話:Clock Control:NewMinute,即觸發了一個NewMinute事件。如下圖所示:
到此為止,我們為Clock控件添加了一個标準事件:Click,和一個自定義事件:NewMinute。讀者一定要注意,對标準事件來說,其觸發過程由MFC底層實作。但對自定義事件來說,必須要在某個條件到來時,在代碼中顯式地調用某個函數發出該事件通知。
當我們在網際網路上暢遊的時候,經常會碰到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檔案,在其最下面就可以找到,如下所示:
helpstring("Clock Control"),
control ]
[default, source]
dispinterface _DClockEvents;
<SCRIPT LANGUAGE=”JScript” EVENT=”NewMinute()”
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控件暴露出來的方法。