摘要: 本文對COM組件中的ActiveX控件的MFC開發方法進行了介紹,講述了用戶自定義和庫存屬性、方法以及事件的添加方法和屬性頁的制作過程。使讀者能夠掌握基本的MFC ActiveX開發方法。
關鍵詞: MFC;ActiveX控件;COM
閱讀目錄: 一、前言 二、建立工程框架 三、屬性、方法以及事件的添加 四、實現屬性表 五、在包容程序中使用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編譯通過。
posted on 2007-02-28 15:33
SIMONE 閱讀(6063)
評論(3) 編輯 收藏 所屬分類:
C++ 、
ActiveX控件