VC++技術內幕(第四版)筆記(第8章)

/************************************/
第八章:使用ActiveX控件

1,ActiveX控件是一個直接插入到C++程序中的軟件模塊,以前常稱OLE控件(OCX),是基於MS-COM技術。

2,ActiveX控件與普通Windows控件比較:
相同點:ActiveX控件也可看成是一個子窗口(可以看成這樣的)。
如果想在對話框上加入ActiveX控件,則只要在對話框編輯器中,把ActiveX控件放在適當的位置上,並在資源模板中標識該控件。如果要在運行的過程中建立ActiveX控件,則可以調用響應控件類的Create成員函數,而且通常在父窗口的WM_CREATE消息控制函數中調用。
不同點:屬性和方法。
ActiveX控件不像普通控件那樣發送以WM_打頭的通知消息給它的包容器窗口,而是激發事件。事件實際上是由控件調用包容器函數。像普通的控件通知消息一樣,事件並沒有返回值傳給ActiveX控件。事件如lick,KeyDown。但對於客戶來說時間與控件的通知消息是一樣的。

3,在MFC庫中,ActiveX控件就像子窗口一樣,但在控件窗口和包容器窗口之間有一層重要代碼。實際上,ActiveX控件可能沒有窗口。當調用Create函數時,並不是直接建立控件窗口,而是把控件代碼載入進來,並激發一個“實地激活”(in-place activation)命令。然後ActiveX控件再建立它自己的窗口,通過MFC的CWnd類指針我們可以訪問該窗口。不過客戶程序最好不要使用ActiveX控件的hWnd句柄。

4,通常ActiveX控件會保存在擴展名爲OCX的動態連接庫中。包容器程序回根據Windows註冊表利用COM技術在需要的時候裝入動態連接庫。
說明:
1)暫時可以這樣認爲,如果使用了ActiveX控件,那麼在運行時候要裝入該ActiveX控件代碼。顯然在發佈含有ActiveX控件的程序時候,必須要包含相應的OCX文件,而且還得提供一個合適的安裝程序。

5,安裝ActiveX控件:
1)把找到的ActiveX控件動態連接庫拷到硬盤上。
2)在WINDOWS註冊表中登記註冊。(可使用Regsvr32命令行命令)
3)在使用該控件的項目中安裝該控件。(選擇Project菜單,再選擇Add To Project,再選擇Components And Controls,再選擇Registered ActiveX Controls,這時列表框列出系統已經註冊所有的ActiveX控件,選擇需要的控件INSERT即可。)

6,ActiveX控件包容器編程:
1)不管ActiveX控件是作爲對話框控件,還是做爲“子窗口”,MFC和ClassWizard都支持。
2)ActiveX控件編寫者設計了ActiveX控件屬性供使用者在設計時訪問。所有的ActiveX控件屬性(包括設計時屬性),在運行時都是可以訪問的,不過有些屬性可能被設計成只讀的。
3)當在項目中插入ActiveX控件時,ClassWizard就會產生相應的CWnd的派生類C++類,來滿足對空間的方法和屬性進行訪問要求。控件的屬性和方法都有相應的成員函數,同時生成的類還有一個構造函數可用以動態創建ActiveX控件的事例。
4)當在項目中插入ActiveX控件ClassWizard生成的CWnd的派生類C++類中,可以看到其成員函數的代碼中都有對InvokeHelper函數的調用,InvokeHelper函數的第一個參數都和對應的屬性或方法在ActiveX控件中的分發(dispatch)ID(標識ActiveX控件的方法或屬性的)相對應。通過查看ActiveX控件hlp文件可以發現,ActiveX控件的方法在生存的C++類中都有同名的成員函數與之對應,ActiveX控件的屬性都有一組Get和Set函數對其操作,其中ActiveX控件的方法和屬性操作與生成的C++類成員函數相關聯都是通過InvokeHelper函數的調用來完成的,InvokeHelper函數的第一個參數是由Component Gallery(控件提供者)提供的。因爲經過這樣的處理,所以我們如果要調用ActiveX控件的方法或對其屬性進行取和設置操作,只需調用生成的C++類對應的成員函數便可。
下面對InvokeHelper單獨說明:
CWnd::InvokeHelper
void InvokeHelper( DISPID dwDispID, WORD wFlags, VARTYPE vtRet, void* pvRet, const BYTE* pbParamInfo, ... );
說明:
Call this member function to invoke the OLE control method or property specified by dwDispID, in the context specified by wFlags.
其中參數:
dwDispID:
//Identifies the method or property to be invoked. This value is usually supplied by Component Gallery.

wFlags:可以爲下面些值,指明調用InvokeHelper的目的。
//[ DISPATCH_METHOD ]   The member is invoked as a method. If a property has the same name, both this and the DISPATCH_PROPERTYGET flag may be set.
[ DISPATCH_PROPERTYGET ] The member is retrieved as a property or data member.
[ DISPATCH_PROPERTYPUT ] The member is changed as a property or data member.
[ DISPATCH_PROPERTYPUTREF ] The member is changed by a reference assignment, rather than a value assignment. This flag is valid only when the property accepts a reference to an object.

vtRet:
//Specifies the type of the return value.
VT_EMPTY  void
VT_I2  short
VT_I4  long
VT_R4  float
VT_R8  double
VT_CY  CY
VT_DATE  DATE
VT_BSTR  BSTR
VT_DISPATCH  LPDISPATCH
VT_ERROR  SCODE
VT_BOOL  BOOL
VT_VARIANT VARIANT
VT_UNKNOWN  LPUNKNOWN

pvRet:
//Address of the variable that will that will receive the property value or return value. It must match the type specified by vtRet.

pbParamInfo:一般都設置爲NULL
//Pointer to a null-terminated string of bytes specifying the types of the parameters following pbParamInfo.
specifies the types of the parameters passed to the method or property.
...:
//Variable List of parameters, of types specified in pbParamInfo.

5)AppWizard對ActiveX控件的支持是通過在生成的應用程序類的成員函數InitInstance中插入(AfxEnableControlContainer();),同時在響應項目文件的StdAfx.h文件中插入(#include<afxdisp.h>)(原因可參考書P38一些說明)。
如果項目中不包含這兩行,而又要加入ActiveX控件,則只要手工加入上面兩行代碼即可。

6)可以對話框編輯器來生成對話框的模板中加入一個或多個ActiveX控件,這樣我們可以在對話框模板生成的類中添加數據成員或事件控制函數來獲取ActiveX控件的屬性或對其控制。
注意:(詳細見書P159頁的[致WIN32程序員])
實際上,資源模板並不是在對話框編輯器中所看的那樣。函數CDialog::DoModal在把對話框模板交給WINDOWS內部的對話框過程之前,要先對模板進行預處理,即:它先會去掉所有的ActiveX控件,有剩下的控件建立對話框窗口,然後再裝入ActiveX控件,激活它們並在正確的位置上創建它的窗口。
當模式對話框運行時候,MFC處理所有送給對話框消息時,是不管是有普通控件發送的,還是ActiveX控件發送的。故ActiveX控件雖然不是對話框模板一部分,但用戶仍然可以用TAB鍵在所有的控件間切換。

7)調用UpdateData(FALSE)將會從所有的對話框控件中讀取所有屬性值。如果只需要得到ActiveX控件屬性的話,可以調用ActiveX控件生成的C++類中Get函數(同樣設置調用Set函數),這樣就提高了效率。

8)事例部分代碼對比說明:
代碼一:
   CDataExchange dx(this,TRUE);
   DDX_Text(&dx,IDC_DAY,m_sDay);
   DDX_Text(&dx,IDC_MONTH,m_sMonth);
   DDX_Text(&dx,IDC_YEAR,m_sYear);
說明一:
CDataExchange類構造函數:(注,在MFC|SRC|AFXWIN.H中可以看到其構造函數聲明,在MFC|SRC|WINCORE.CPP文件中可以看到其構造函數的定義。)
原型:CDataExchange::CDataExchange(CWnd* pDlgWnd, BOOL bSaveAndValidate);
定義:
CDataExchange::CDataExchange(CWnd* pDlgWnd, BOOL bSaveAndValidate)
{
 ASSERT_VALID(pDlgWnd);
 m_bSaveAndValidate = bSaveAndValidate;
 m_pDlgWnd = pDlgWnd;
 m_hWndLastControl = NULL;
}

//其中m_pDlgWnd和m_bSaveAndValidate是CDataExchange數據成員,可以通過這中方式給它們賦值。
m_pDlgWnd:The dialog box or window where the data exchange takes place.
m_bSaveAndValidate Flag for the direction of DDX and DDV. 詳見說明二。

說明二:
//CDataExchange does not have a base class.
//The CDataExchange class supports the dialog data exchange (DDX) and dialog data validation (DDV) routines used by the Microsoft Foundation classes. Use this class if you are writing data exchange routines for custom data types or controls, or if you are writing your own data validation routines.
//A CDataExchange object provides the context information needed for DDX and DDV to take place. The flag m_bSaveAndValidate is FALSE when DDX is used to fill the initial values of dialog controls from data members. The flag m_bSaveAndValidate is TRUE when DDX is used to set the current values of dialog controls into data members and when DDV is used to validate the data values. If the DDV validation fails, the DDV procedure will display a message box explaining the input error. The DDV procedure will then call Fail to reset the focus to the offending control and throw an exception to stop the validation process.


代碼二:(是爲了比較代碼一做些說明的)
void CActiveXDialog::DoDataExchange(CDataExchange* pDX)
{
 CDialog::DoDataExchange(pDX);//調用基類的DoDataExchange
 //{{AFX_DATA_MAP(CActiveXDialog)
 DDX_Control(pDX, IDC_CALENDAR1, m_calendar);
 DDX_Text(pDX, IDC_DAY, m_sDay);
 DDX_Text(pDX, IDC_MONTH, m_sMonth);
 DDX_Text(pDX, IDC_YEAR, m_sYear);
 //}}AFX_DATA_MAP
}
說明一:
//DoDataExchange Called by the framework to exchange and validate dialog data.
//DoDataExchange Never call this function directly. It is called by the UpdateData member function. Call UpdateData to initialize a dialog box’s controls or retrieve data from a dialog box.
說明二:
//在MFC|SRC|WINCORE.CPP文件中可以看到UpdateData函數的定義
BOOL CWnd::UpdateData(BOOL bSaveAndValidate)
{ ... 
 CDataExchange dx(this, bSaveAndValidate);//創建了一個CDataExchange對象,與當前窗口相關聯
 ...
 DoDataExchange(&dx); //注意:DoDataExchange是個虛函數。子類中如果有重寫了,則調用子類的。
 ...
}
說明三:
//在MFC|Include|AFXWIN2.INL文件中可有看到CWnd::DoDataExchange的如下定義(內聯):
// CWnd dialog data support
_AFXWIN_INLINE void CWnd::DoDataExchange(CDataExchange*)
 { } // default does nothing
由此可見代碼二中CDialog::DoDataExchange(pDX)調用好象是個‘擺設’,不起做任何事情。框架設置DoDataExchange函數目的是在我們子窗口類(這裏是對話筐)中重寫它,添加代碼完成子窗口類(這裏是對話筐)中數據成員與對話筐上控件的交互。
說明四:
如果結合UpdateData和DoDataExchange兩函數整體來看,應該體會到這裏代碼一與代碼二實質上是一會事情。代碼二隻是藉助了框架兜了些圈子。

說明四:
摘錄MSDN中Dialog Data Exchange一些E文段落對上討論做個總結:
Dialog Data Exchange

If you use the DDX mechanism, you set the initial values of the dialog object’s member variables, typically in your OnInitDialog handler or the dialog constructor. Immediately before the dialog is displayed, the framework’s DDX mechanism transfers the values of the member variables to the controls in the dialog box, where they appear when the dialog box itself appears in response to DoModal or Create. The default implementation of OnInitDialog in CDialog calls the UpdateData member function of class CWnd to initialize the controls in the dialog box.

The same mechanism transfers values from the controls to the member variables when the user clicks the OK button (or whenever you call the UpdateData member function with the argument TRUE). The dialog data validation mechanism validates any data items for which you specified validation rules.

UpdateData works in both directions, as specified by the BOOL parameter passed to it. To carry out the exchange, UpdateData sets up a CDataExchange object and calls your dialog class’s override of CDialog’s DoDataExchange member function. DoDataExchange takes an argument of type CDataExchange. The CDataExchange object passed to UpdateData represents the context of the exchange, defining such information as the direction of the exchange.

When you (or ClassWizard) override DoDataExchange, you specify a call to one DDX function per data member (control). Each DDX function knows how to exchange data in both directions based on the context supplied by the CDataExchange argument passed to your DoDataExchange by UpdateData.

MFC provides many DDX functions for different kinds of exchange.

If the user cancels a modal dialog box, the OnCancel member function terminates the dialog box and DoModal returns the value IDCANCEL. In that case, no data is exchanged between the dialog box and the dialog object.

9)當輸入焦點在某ActiveX控件上時,按下F1鍵引起OnHelpInfo函數調用,可在OnHelpInfo函數中設置幫助信息。
說明:
ClassWizard不能修改生成的ActiveX控件類,因而必須手工加入消息映射代碼。事例代碼如下:
//在ActiveX控件類頭文件中加入函數原型並聲明消息映射表:
 protected:
  afx_msg BOOL OnHelpInfo(HELPINFO* pHelpInfo);
  DECLARE_MESSAGE_MAP()//在ActiveX控件類代碼文件中添加消息映射及OnHelpInfo函數定義:
 BEGIN_MESSAGE_MAP(CCalendar,CWnd)
  ON_WM_HELPINFO()
 END_MESSAGE_MAP()
/**
 BOOL CCalendar::OnHelpInfo(HELPINFO *pHelpInfo)
{
 ::WinHelp(GetSafeHwnd(),"C:/WINDOWS/system32/MSCAL.hlp",
    HELP_FINDER,0);
 return FALSE;
}

/////////////////
//////////////
///////////

7,在運行時創建ActiveX控件:
1)在項目中插入ActiveX控件。ClassWizard會生存相應的ActiveX控件類的文件。
2)在使用ActiveX控件的對話框或窗口類中添加ActiveX控件類數據成員。
3)重寫CDialog::OnInitDialog(或其它窗口中響應WM_CREAT消息),在新的函數中調用ActiveX控件類Create函數。
4)在父窗口類中,手工添加必要的與新控件有關的事件消息處理函數及原型,和相應的消息映射。

/////////
8,更多的ActiveX控件編程參見P160-167頁(ActiveX控件在HTML文件中使用 和 在運行時創建ActiveX控件)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章