基本概念:
容器和伺服器程式
容器應用程式時可以嵌入或連結對象的應用程式。Word 就是容器應用程式。伺服器應用程式是建立對象并且當對象被輕按兩下時,可以被啟動的應用程式。Excel 就是伺服器應用程式。ActiveX 控件不能獨立運作,它必須被嵌入容器應用程式中,和容器應用程式一起運作。
---------------------------------------------------------------------------------
編寫一個 ActiveX 時鐘控件
1.利用 MFC ActiveX ContrlWizard 建立一個 Clock 工程
2.在建立的工程中有三個類,其中 CClockApp 從 COleControlModule 中派生出來的,
可以将其看作是應用程式類,它的一個執行個體表示控件程式本身。
COleControl 從 CWnd 派生得到,也是一個視窗類,
CClockCtrl 相當于單文檔程式的主視窗類。
這個類包含重繪用的 OnDraw 函數,也包含了一些消息映射,包括排程映射。
Dispatch maps 排程映射,主要是 MFC 提供讓外部應用程式可以通路控件的屬性和方法。
Event maps 事件映射,控件向包含它的容器發送事件通知。
CClockPropPage 類由 COlePropertyPage 派生而來:
COlePropertyPage
The COlePropertyPage class is used to display the properties of a custom control in a graphical interface, similar to a dialog box. 被用來顯示一個自定義控件的屬性,類似于一個對話框。它是對話框類,于是 enum { IDD = IDD_PROPPAGE_CLOCK }給它關聯了一個對話框資源。
在工程中,還有兩個全局函數:STDAPI DllRegisterServer(void);将控件資訊寫入系統資料庫中 STDAPI DllUnregisterServer(void)解除安裝注冊資訊。
3.在三個類之上,還有類似小勺的圖示 Dclock,_DClockEvents,它們表示接口。
接口是外部程式和控件進行通信的協定,可以把接口看作是函數的集合,外部程式通過接口提供的方法,去通路控件的屬性和方法。也可以将接口看作抽象基類,接口中所定義的所有函數都是純虛函數, 這些函數的實作都是在 CClockCtrl 類中實作,MFC 通過底層的封裝使 CClockCtrl 類繼承 Dclock 接口。是以調用接口,事實上調用的是 CClockCtrl 類中的函數。由于封裝,底層的通信細節我們看不到,如果對這個感興趣,可以看一些 COM 程式設計的資料。
4.如果些時編譯一下工程,會生成一個 Clock.ocx 檔案,它就是 ActiveX 控件的檔案。
要用控件的時候,隻需要把這個檔案傳遞給對方。注意:ActiveX 控件不能單獨使用,必須嵌入到一個視窗當中一直運作。當我們用 VB 添加控件時,發現了我們剛才編譯的控件,但是 VB 怎麼知道我們建立的控件的位置呢? 我們發現編譯時,輸出視窗的最後一行 “Registering ActiveX Control...”說明編譯時會注冊控件。事實上編譯之後,VC 會調用 regsvr32 注冊控件,并将資訊寫入到系統資料庫中,VB 在加載 ActiveX 控件時,會從系統資料庫中搜尋相關的 ActiveX 控件資訊。注意:ActiveX 控件在使用之前都需要注冊。
如果想解除安裝控件,可以在“運作”中輸入指令:
“regsvr32 /u 控件檔案完整路徑名”,事實上是調用工程中全局函數 DllUnregisterServer 來完成解除安裝的。
如果想再次注冊控件,可以在“運作”中輸入下面指令:
“regsvr32 控件檔案完整路徑名”事實上是調用工程中全局函數 DllRegisterServer 來完成注冊的。
5.下面實作在控件上輸出當時系統時間。
可以在 OnDraw 函數中完成這個功能。
這樣就能做出一個靜态的時間控件,如果我們想使控件實時顯示時間,需要添加兩個消息響應函數 WM_CREATE,WM_TIMER.要使時間可以每秒更新,我們先在 CClockCtrl 類中添加 WM_CREATE 消息處理,在其響應函數 OnCreate()中設定一個計時器:SetTimer(1,1000,NULL);接着增加一個 WM_TIMER 消息響應處理,在 OnTimer 中寫上 Invalidate(); 使視窗發生重繪。也可以調用 InvalidateControl()強制控件重繪自身。
6.在 VB 測試中發現,其他很多控件可以修改控件的背景色、前景色和字型顔色,而我們還不行。
ActiveX 控件有四種屬性:
Stock:為每個控件提供的标準屬性,如字型或顔色。
Ambient:圍繞控件的環境屬性——已被置入容器的屬性。
這些屬性不能被修改,但控件可以使用它們調整自己的屬性。
Extended:這些是由容器處理的屬性,一般包括大小和在螢幕上的位置。
Custom:由控件開發者添加的屬性。
7.這時在 VB 測試中,我們的控件也可以修改控件的背景色和前景色了,但是設定完以後沒有效果,因為還要 OnDraw 函數中自己編寫代碼來完成這些改變。要得到控件标準屬性,要通過一些函數來完成,如COleControl::GetForeColor 得到前景色,COleControl::GetBackColor 得到背景色,不過要注意的是它們得到的是 OLE_COLOR 的顔色,還需要通過 TranslateColor 方法轉換成 COLORREF。
8.控件一般都會有屬性頁,當我們右鍵點選控件時會彈出這個屬性頁友善對控件屬性的設定,我們的控件已經有一個屬性頁,通過 CClockPropPage 類來實作的,顯示的面容是對話框資源的内容,下面修改控件屬性頁:
屬性頁之是以可以在視窗中看得到,是因為在 ClockCtl.cpp 中的 BEGIN_PROPPAGEIDS(CClockCtrl, 1)與 END_PROPPAGEIDS(CClockCtrl)之間調用了 PROPPAGEID(CClockPropPage::guid),其中的 guid 表示全局唯一辨別符,它是一個128位的整數,用來唯一地辨別一個元件或者一個接口。我們可以用 PROPPAGEID 宏增加屬性頁。
增加顔色屬性頁
首先在 BEGIN_PROPPAGEIDS(CClockCtrl, 1)與END_PROPPAGEIDS(CClockCtrl)之間添加代碼 PROPPAGEID(CLSID_CColorPropPage)來添加屬性頁。
CLSID_CColorPropPage 屬性表單是控件自身提供的, 我們添加之後不用作任何處理,就可以使用,效果如下:
注意:
BEGIN_PROPPAGEIDS(CClockCtrl, 2)中的數字2表示有多少個屬性頁代碼要顯示,
如果增加了屬性頁數字一定要加1,不然如果沒有修改或修改錯誤,會産生不可預料錯誤。
9.增加一個自定義屬性也是通過 MFC ClassWizard 來完成,與第6步增加前景色與背景色的步驟相同。
1)下面增加設定時間間隔的屬性,用這個屬性來控件時間重新整理頻率:
屬性添加成功以後,在_DClock 接口中增加了 Interval 屬性,同時在 CClockCtrl 類中增加了一個
成員變量 m_interval 和 OnIntervalChanged()方法(當外部屬性修改之後就會調用這個方法)。
而且它們都在排程映射當中:
2)在 CClockCtrl::OnIntervalChanged()中添加屬性處理程式代碼
10.使增加的自定義屬性在屬性表單中設定
在對話框資源中添加一個編輯框,再為這個編輯框關聯一個變量, 注意,我們在為編輯框關聯一個變量 m_updateInterval 的同時也關聯了一個屬性是,這樣我們不需要增加代碼就能把控件和自定義屬性相關聯。在 void CClockPropPage::DoDataExchange(CDataExchange* pDX)中會生成下面代碼:
這樣,我們就可以在屬性頁裡面設定時間間隔了。
11.為控件添加一個方法:
為控件增加函數,MFC ClassWizard-->Automation-->Add Method Class Name 要選擇CClockCtrl 輸入函數名,之後就可以在 CClockCtrl 類中找到了。方法添加成功以後,在_DClock 接口中增加了 Hello 方法,同時在 CClockCtrl 類中增加了 Hello 方法。接下來,我們可以在 CClockCtrl 類中增加了 Hello 方法添加自己的代碼就可以了。
12.為控件添加一個标準事件
我們選擇 MFC ClassWizard-->ActiveX Events--->Add Event。事件添加成功以後,會在_DClockEvents 中增加一個事件 Click,DClockEvents 接口是源接口,控件将用這個接口發送通知事件,它不是控件本身實作的接口,這個接口是通過容器來實作的
13.增加一個自定義事件:當秒數為0時,發出一個 NewMinute 事件。
1)增加一個自定義事件的過程與增加一個标準事件的步驟相同,也可以這樣添加在事件接口_DClockEvents 上點選右鍵,選擇增加事件,效果一樣,都會彈出 Add Event 對話框。事件添加成功以後,會在_DClockEvents 中增加一個事件NewMinute事件,同時在在 CClockCtrl 類中增加了void FireNewMinute(),也就是在控件内部可以調用 FireNewMinute()向容器發送事件通知,而這個函數内部會調用接口的 void NewMinute()向容器發出事件通知。
2)在 OnDraw()方法添加代碼 if(0 == time.GetSecond())FireNewMinute();使當秒數為0時,向容器發出一個 NewMinute 事件通知。标準消息不需要另外寫代碼向容器發出事件通知,VC在底層代碼内部實作了這個過程。
14.将我們自定義的控件屬性在修改之後永久儲存下來,使用者打開程式之後,控件的屬性都是使用原先儲存的值。需要在 void CClockCtrl::DoPropExchange(CPropExchange* pPX)加入如下代碼 PX_Short(pPX,"Interval",m_interval,1000);之後再在程式中 OnCreate()方法中将 SetTimer(1,1000,NULL); 修改代碼為 SetTimer(1,m_interval,NULL);
15.在屬性頁中修改自定義控件屬性的時候,通知容器做相應的調整,進而使容器屬性清單中實時地顯示我們對屬性所作的修改。在 void CClockCtrl::OnIntervalChanged() 中加入如下代碼:BoundPropertyChanged(0x1); //通知容器排程 ID 為1的屬性發生了改變
16.前面都是讓控件在容器設計時改變控件屬性,如果希望使用者在設計模式時時鐘控件停止運作,而在使用者模式下時間會跳動,在控件内部可以通過 AmbientUserMode()方法得到目前控件是處于設計狀态還是運作狀态。在 void CClockCtrl::OnTimer(UINT nIDEvent)下修改代碼如下:
if(AmbientUserMode()) InvalidateControl();
17.編寫完控件以後,我們可以選擇 Win32 Release 方式進行編譯,生成發行版 ActiveX 控件。在開發的時候通常是以 Win32 Debug 模式下編譯的,這種模式下開發有助于我們開發過程的産生的錯誤,例如非法記憶體通路錯誤;還可以幫助我們調試程式,跟蹤程式進而排查錯誤。但是在調試模式下生成的檔案比較大,因為在檔案中包含了調試的資訊。
而當我們開發完成後,在 Release 模式下進行編譯時,VC 編譯器會在代碼生成上、執行速度上做一些優化,同時生成的可執行程式或控件檔案會比較小。
-------------------------------------------------------------------
在 VC 中編寫一個用戶端調用 ActiveX 控件:
1.建立一個基于 MFC 對話框的 ClockTest 工程項目
2.點選右鍵,選擇“插入 ActiveX 控件”,然後在彈出的對話框中選擇剛才我們建立的控件。
也可以通過菜單的方式增加控件:
“工程”->增加到工程->Componets and Controls
選擇已注冊控件"Registered ActiveX Contrlos",找到我們自己控件,再按下插入。
通過這種方式插入 ActiveX 控件時,會在工程中自動生成一個類 CCock,其基類為 CWnd。這個類是一個封裝類,封裝了對 ActiveX 控件進行通路的一些操作。同時在 VC 的工具箱上面也增加一個時鐘控件,可以直接将一個時鐘控件拖放到窗體上。
3.用第二種方法插入的控件,除了将控件手動插入到窗體以外,我們也可以通過代碼動态生成一個控件。
1)CClockTestDlg 增加一個成員變量:CClock m_clock;
2)在 CClockTestDlg 的頭檔案中包含一個 clock.h
3)接下來就要以在一個按鈕的單擊事件中增加建立控件的代碼:
4)在設計時,可以點選右鍵為控件添加事件響應。
