MFC和Win32技術(shù)應(yīng)用分析
MFC Object和Windows Object的關(guān)系
MFC中最重要的封裝是對(duì)Win32 API的封裝,因此,理解Windows Object和MFC Object (C++對(duì)象,一個(gè)C++類的實(shí)例)之間的關(guān)系是理解MFC的關(guān)鍵之一。所謂Windows Object(Windows對(duì)象)是Win32下用句柄表示的Windows操作系統(tǒng)對(duì)象;所謂MFC Object (MFC對(duì)象)是C++對(duì)象,是一個(gè)C++類的實(shí)例,這里(本書(shū)范圍內(nèi))MFC Object是有特定含義的,指封裝Windows Object的C++ Object,并非指任意的C++ Object。
MFC Object 和Windows Object是不一樣的,但兩者緊密聯(lián)系。以窗口對(duì)象為例:
一個(gè)MFC窗口對(duì)象是一個(gè)C++ CWnd類(或派生類)的實(shí)例,是程序直接創(chuàng)建的。在程序執(zhí)行中它隨著窗口類構(gòu)造函數(shù)的調(diào)用而生成,隨著析構(gòu)函數(shù)的調(diào)用而消失。而Windows窗口則是Windows系統(tǒng)的一個(gè)內(nèi)部數(shù)據(jù)結(jié)構(gòu)的實(shí)例,由一個(gè)“窗口句柄”標(biāo)識(shí),Windows系統(tǒng)創(chuàng)建它并給它分配系統(tǒng)資源。Windows窗口在MFC窗口對(duì)象創(chuàng)建之后,由CWnd類的Create成員函數(shù)創(chuàng)建,“窗口句柄”保存在窗口對(duì)象的m_hWnd成員變量中。Windows窗口可以被一個(gè)程序銷毀,也可以被用戶的動(dòng)作銷毀。MFC窗口對(duì)象和Windows窗口對(duì)象的關(guān)系如圖2-1所示。其他的Windows Object和對(duì)應(yīng)的MFC Object也有類似的關(guān)系。
下面,對(duì)MFC Object和Windows Object作一個(gè)比較。有些論斷對(duì)設(shè)備描述表(MFC類是CDC,句柄是HDC)可能不適用,但具體涉及到時(shí)會(huì)指出。
從數(shù)據(jù)結(jié)構(gòu)上比較
MFC Object是相應(yīng)C++類的實(shí)例,這些類是MFC或者程序員定義的;
Windows Object是Windows系統(tǒng)的內(nèi)部結(jié)構(gòu),通過(guò)一個(gè)句柄來(lái)引用;
MFC給這些類定義了一個(gè)成員變量來(lái)保存MFC Object對(duì)應(yīng)的Windows Object的句柄。對(duì)于設(shè)備描述表CDC類,將保存兩個(gè)HDC句柄。
從層次上講比較
MFC Object是高層的,Windows Object是低層的;
MFC Object封裝了Windows Object的大部分或全部功能,MFC Object的使用者不需要直接應(yīng)用Windows Object的HANDLE(句柄)使用Win32 API,代替它的是引用相應(yīng)的MFC Object的成員函數(shù)。
從創(chuàng)建上比較
MFC Object通過(guò)構(gòu)造函數(shù)由程序直接創(chuàng)建;Windows Object由相應(yīng)的SDK函數(shù)創(chuàng)建。
MFC中,使用這些MFC Object,一般分兩步:
首先,創(chuàng)建一個(gè)MFC Object,或者在STACK中創(chuàng)建,或者在HEAP中創(chuàng)建,這時(shí),MFC Object的句柄實(shí)例變量為空,或者說(shuō)不是一個(gè)有效的句柄。
然后,調(diào)用MFC Object的成員函數(shù)創(chuàng)建相應(yīng)的Windows Object,MFC的句柄變量存儲(chǔ)一個(gè)有效句柄。
CDC(設(shè)備描述表類)的創(chuàng)建有所不同,在后面的2.3節(jié)會(huì)具體說(shuō)明CDC及其派生類的創(chuàng)建和使用。
當(dāng)然,可以在MFC Object的構(gòu)造函數(shù)中創(chuàng)建相應(yīng)的Windows對(duì)象,MFC的GDI類就是如此實(shí)現(xiàn)的,但從實(shí)質(zhì)上講,MFC Object的創(chuàng)建和Windows Object的創(chuàng)建是兩回事。
從轉(zhuǎn)換上比較
可以從一個(gè)MFC Object得到對(duì)應(yīng)的Windows Object的句柄;一般使用MFC Object的成員函數(shù)GetSafeHandle得到對(duì)應(yīng)的句柄。
可以從一個(gè)已存在的Windows Object創(chuàng)建一個(gè)對(duì)應(yīng)的MFC Object; 一般使用MFC Object的成員函數(shù)Attach或者FromHandle來(lái)創(chuàng)建,前者得到一個(gè)永久性對(duì)象,后者得到的可能是一個(gè)臨時(shí)對(duì)象。
從使用范圍上比較
MFC Object對(duì)系統(tǒng)的其他進(jìn)程來(lái)說(shuō)是不可見(jiàn)、不可用的;而Windows Object一旦創(chuàng)建,其句柄是整個(gè)Windows系統(tǒng)全局的。一些句柄可以被其他進(jìn)程使用。典型地,一個(gè)進(jìn)程可以獲得另一進(jìn)程的窗口句柄,并給該窗口發(fā)送消息。
對(duì)同一個(gè)進(jìn)程的線程來(lái)說(shuō),只可以使用本線程創(chuàng)建的MFC Object,不能使用其他線程的MFC Object。
從銷毀上比較
MFC Object隨著析構(gòu)函數(shù)的調(diào)用而消失;但Windows Object必須由相應(yīng)的Windows系統(tǒng)函數(shù)銷毀。
設(shè)備描述表CDC類的對(duì)象有所不同,它對(duì)應(yīng)的HDC句柄對(duì)象可能不是被銷毀,而是被釋放。
當(dāng)然,可以在MFC Object的析構(gòu)函數(shù)中完成Windows Object的銷毀,MFC Object的GDI類等就是如此實(shí)現(xiàn)的,但是,應(yīng)該看到:兩者的銷毀是不同的。
每類Windows Object都有對(duì)應(yīng)的MFC Object,下面用表格的形式列出它們之間的對(duì)應(yīng)關(guān)系,如表2-1所示:
表2-1 MFC Object和Windows Object的對(duì)應(yīng)關(guān)系
表2-1中的OBJECT分以下幾類:
描述Windows句柄MFC Object
窗口HWNDCWnd and CWnd-derived classes
設(shè)備上下文HDCCDC and CDC-derived classes
菜單HMENUCMenu
筆HPENCGdiObject類,CPen和CPen-derived classes
刷子HBRUSHCGdiObject類,CBrush和CBrush-derived classes
字體HFONTCGdiObject類,CFont和CFont-derived classes
位圖HBITMAPCGdiObject類,CBitmap和CBitmap-derived classes
調(diào)色板HPALETTECGdiObject類,CPalette和CPalette-derived classes
區(qū)域HRGNCGdiObject類,CRgn和CRgn-derived classes
圖像列表HimageLISTCimageList和CimageList-derived classes
套接字SOCKETCSocket,CAsynSocket及其派生類
Windows對(duì)象,
設(shè)備上下文對(duì)象,
GDI對(duì)象(BITMAP,BRUSH,F(xiàn)ONT,PALETTE,PEN,RGN),
菜單,
圖像列表,
網(wǎng)絡(luò)套接字接口。
從廣義上來(lái)看,文檔對(duì)象和文件可以看作一對(duì)MFC Object和Windows Object,分別用CDocument類和文件句柄描述。
后續(xù)幾節(jié)分別對(duì)前四類作一個(gè)簡(jiǎn)明扼要的論述。
Windows Object
用SDK的Win32 API編寫(xiě)各種Windows應(yīng)用程序,有其共同的規(guī)律:首先是編寫(xiě)WinMain函數(shù),編寫(xiě)處理消息和事件的窗口過(guò)程WndProc,在WinMain里頭注冊(cè)窗口(Register Window),創(chuàng)建窗口,然后開(kāi)始應(yīng)用程序的消息循環(huán)。
MFC應(yīng)用程序也不例外,因?yàn)镸FC是一個(gè)建立在SDK API基礎(chǔ)上的編程框架。對(duì)程序員來(lái)說(shuō)所不同的是:一般情況下,MFC框架自動(dòng)完成了Windows登記、創(chuàng)建等工作。
下面,簡(jiǎn)要介紹MFC Window對(duì)Windows Window的封裝。
Windows的注冊(cè)
一個(gè)應(yīng)用程序在創(chuàng)建某個(gè)類型的窗口前,必須首先注冊(cè)該“窗口類”(Windows Class)。注意,這里不是C++類的類。Register Window把窗口過(guò)程、窗口類型以及其他類型信息和要登記的窗口類關(guān)聯(lián)起來(lái)。[!--empirenews.page--]
“窗口類”的數(shù)據(jù)結(jié)構(gòu)
“窗口類”是Windows系統(tǒng)的數(shù)據(jù)結(jié)構(gòu),可以把它理解為Windows系統(tǒng)的類型定義,而Windows窗口則是相應(yīng)“窗口類”的實(shí)例。Windows使用一個(gè)結(jié)構(gòu)來(lái)描述“窗口類”,其定義如下:
typedef struct _WNDCLASSEX {
UINT cbSize; //該結(jié)構(gòu)的字節(jié)數(shù)
UINT style; //窗口類的風(fēng)格
WNDPROC lpfnWndProc; //窗口過(guò)程
int cbClsExtra;
int cbWndExtra;
HANDLE hInstance; //該窗口類的窗口過(guò)程所屬的應(yīng)用實(shí)例
HICON hIcon; //該窗口類所用的像標(biāo)
HCURSOR hCursor; //該窗口類所用的光標(biāo)
HBRUSH hbrBackground; //該窗口類所用的背景刷
LPCTSTR lpszMenuName; //該窗口類所用的菜單資源
LPCTSTR lpszClassName; //該窗口類的名稱
HICON hIconSm; //該窗口類所用的小像標(biāo)
} WNDCLASSEX;
從“窗口類”的定義可以看出,它包含了一個(gè)窗口的重要信息,如窗口風(fēng)格、窗口過(guò)程、顯示和繪制窗口所需要的信息,等等。關(guān)于窗口過(guò)程,將在后面消息映射等有關(guān)章節(jié)作詳細(xì)論述。
Windows系統(tǒng)在初始化時(shí),會(huì)注冊(cè)(Register)一些全局的“窗口類”,例如通用控制窗口類。應(yīng)用程序在創(chuàng)建自己的窗口時(shí),首先必須注冊(cè)自己的窗口類。在MFC環(huán)境下,有幾種方法可以用來(lái)注冊(cè)“窗口類”,下面分別予以討論。
調(diào)用AfxRegisterClass注冊(cè)
AfxRegisterClass函數(shù)是MFC全局函數(shù)。AfxRegisterClass的函數(shù)原型:
BOOL AFXAPI AfxRegisterClass(WNDCLASS *lpWndClass);
參數(shù)lpWndClass是指向WNDCLASS結(jié)構(gòu)的指針,表示一個(gè)“窗口類”。
首先,AfxRegisterClass檢查希望注冊(cè)的“窗口類”是否已經(jīng)注冊(cè),如果是則表示已注冊(cè),返回TRUE,否則,繼續(xù)處理。
接著,調(diào)用::RegisterClass(lpWndClass)注冊(cè)窗口類;
然后,如果當(dāng)前模塊是DLL模塊,則把注冊(cè)“窗口類”的名字加入到模塊狀態(tài)的域m_szUnregisterList中。該域是一個(gè)固定長(zhǎng)度的緩沖區(qū),依次存放模塊注冊(cè)的“窗口類”的名字(每個(gè)名字是以“\n\0”結(jié)尾的字符串)。之所以這樣做,是為了DLL退出時(shí)能自動(dòng)取消(Unregister)它注冊(cè)的窗口類。至于模塊狀態(tài)將在后面第9章詳細(xì)的討論。
最后,返回TRUE表示成功注冊(cè)。
調(diào)用AfxRegisterWndClass注冊(cè)
AfxRegisterWndClass函數(shù)也是MFC全局函數(shù)。AfxRegisterWndClass的函數(shù)原型:
LPCTSTR AFXAPI AfxRegisterWndClass(UINT nClassStyle,
HCURSOR hCursor, HBRUSH hbrBackground, HICON hIcon)
參數(shù)1指定窗口類風(fēng)格;
參數(shù)2、3、4分別指定該窗口類使用的光標(biāo)、背景刷、像標(biāo)的句柄,缺省值是0。
此函數(shù)根據(jù)窗口類屬性動(dòng)態(tài)地產(chǎn)生窗口類的名字,然后,判斷是否該類已經(jīng)注冊(cè),是則返回窗口類名;否則用指定窗口類的屬性(窗口過(guò)程指定為缺省窗口過(guò)程),調(diào)用AfxRegisterCalss注冊(cè)窗口類,返回類名。
動(dòng)態(tài)產(chǎn)生的窗口類名字由以下幾部分組成(包括冒號(hào)分隔符):
如果參數(shù)2、3、4全部為NULL,則由三部分組成。
“Afx”+“:”+模塊實(shí)例句柄”+“:”+“窗口類風(fēng)格”
否則,由六部分組成:
“Afx”+“:”+模塊實(shí)例句柄+“:”+“窗口類風(fēng)格”+“:”+光標(biāo)句柄+“:”+背景刷句柄+“:”+像標(biāo)句柄。比如:“Afx:400000:b:13de:6:32cf”。
該函數(shù)在MFC注冊(cè)主邊框或者文檔邊框“窗口類”時(shí)被調(diào)用。具體怎樣用在5.3.3.3節(jié)會(huì)指出。
隱含的使用MFC預(yù)定義的的窗口類
MFC4.0以前的版本提供了一些預(yù)定義的窗口類,4.0以后不再預(yù)定義這些窗口類。但是,MFC仍然沿用了這些窗口類,例如:
用于子窗口的“AfxWnd”;
用于邊框窗口(SDI主窗口或MDI子窗口)或視的“AfxFrameOrView”;
用于MDI主窗口的“AfxMDIFrame”;
用于標(biāo)準(zhǔn)控制條的“AfxControlBar”。
這些類的名字就 是“AfxWnd”、“AfxFrameOrView”、“AfxMdiFrame”、 “AfxControlBar”加上前綴和后綴(用來(lái)標(biāo)識(shí)版本號(hào)或是否調(diào)試版等)。它們使用標(biāo)準(zhǔn)應(yīng)用程序像標(biāo)、標(biāo)準(zhǔn)文檔像標(biāo)、標(biāo)準(zhǔn)光標(biāo)等標(biāo)準(zhǔn)資源。為了使用這些“窗口類”,MFC會(huì)在適當(dāng)?shù)臅r(shí)候注冊(cè)這些類:或者要?jiǎng)?chuàng)建該類的窗口時(shí),或者創(chuàng)建應(yīng)用程序的主窗口時(shí),等等。
MFC內(nèi)部使用了函數(shù)
BOOL AFXAPI AfxEndDeferRegisterClass(short fClass)
來(lái)幫助注冊(cè)上述原MFC版本的預(yù)定義“窗口類”。參數(shù)fClass區(qū)分了那些預(yù)定義窗口的類型。根據(jù)不同的類型,使用不同的窗口類風(fēng)格、窗口類名字等填充WndClass的域,然后調(diào)用AfxRegisterClass注冊(cè)窗口類。并且注冊(cè)成功之后,通過(guò)模塊狀態(tài)的m_fRegisteredClasses記錄該窗口類已經(jīng)注冊(cè),這樣該模塊在再次需要注冊(cè)這些窗口類之前可以查一下m_fRegisteredClasses,如果已經(jīng)注冊(cè)就不必浪費(fèi)時(shí)間了。為此,MFC內(nèi)部使用宏
AfxDeferRegisterClass(short fClass)
來(lái)注冊(cè)“窗口類”,如果m_fRegisteredClasses記錄了注冊(cè)的窗口類,返回TRUE,否則,調(diào)用AfxEndDeferRegisterClass注冊(cè)。
注冊(cè)這些窗口類的例子:
MFC在加載邊框窗口時(shí),會(huì)自動(dòng)地注冊(cè)“AfxFrameOrView”窗口類。在創(chuàng)建視時(shí),就會(huì)使用該“窗口類”創(chuàng)建視窗口。當(dāng)然,如果創(chuàng)建視窗口時(shí),該“窗口類”還沒(méi)有注冊(cè),MFC將先注冊(cè)它然后使用它創(chuàng)建視窗口。
不過(guò),MFC并不使用”AfxMDIFrame”來(lái)創(chuàng)建MDI主窗口,因?yàn)樵诩虞d主窗口時(shí)一般都指定了主窗口的資源,MFC使用指定的像標(biāo)注冊(cè)新的MDI主窗口類(通過(guò)函數(shù)AfxRegisterWndClass完成,因此“窗口類”的名字是動(dòng)態(tài)產(chǎn)生的)。[!--empirenews.page--]
MDI子窗口類似于上述MDI主窗口的處理。
在MFC創(chuàng)建控制窗口時(shí),如工具欄窗口,如果“AfxControlBar”類還沒(méi)有注冊(cè),則注冊(cè)它。注冊(cè)過(guò)程很簡(jiǎn)單,就是調(diào)用::InitCommonControl加載通用控制動(dòng)態(tài)連接庫(kù)。
調(diào)用::RegisterWndClass。
直接調(diào)用Win32的窗口注冊(cè)函數(shù)::RegisterWndClass注冊(cè)“窗口類”,這樣做有一個(gè)缺點(diǎn):如果是DLL模塊,這樣注冊(cè)的“窗口類”在程序退出時(shí)不會(huì)自動(dòng)的被取消注冊(cè)(Unregister)。所以必須記得在DLL模塊退出時(shí)取消它所注冊(cè)的窗口類。
子類化
子類化(Subclass)一個(gè)“窗口類”,可自動(dòng)地得到它的“窗口類”屬性。
MFC窗口類CWnd
在Windows系統(tǒng)里,一個(gè)窗口的屬性分兩個(gè)地方存放:一部分放在“窗口類”里頭,如上所述的在注冊(cè)窗口時(shí)指定;另一部分放在Windows Object本身,如:窗口的尺寸,窗口的位置(X,Y軸),窗口的Z軸順序,窗口的狀態(tài)(ACTIVE,MINIMIZED,MAXMIZED,RESTORED…),和其他窗口的關(guān)系(父窗口,子窗口…),窗口是否可以接收鍵盤(pán)或鼠標(biāo)消息,等等。
為了表達(dá)所有這些窗口的共性,MFC設(shè)計(jì)了一個(gè)窗口基類CWnd。有一點(diǎn)非常重要,那就是CWnd提供了一個(gè)標(biāo)準(zhǔn)而通用的MFC窗口過(guò)程,MFC下所有的窗口都使用這個(gè)窗口過(guò)程。至于通用的窗口過(guò)程卻能為各個(gè)窗口實(shí)現(xiàn)不同的操作,那就是MFC消息映射機(jī)制的奧秘和作用了。這些,將在后面有關(guān)章節(jié)詳細(xì)論述。
CWnd提供了一系列成員函數(shù),或者是對(duì)Win32相關(guān)函數(shù)的封裝,或者是CWnd新設(shè)計(jì)的一些函數(shù)。這些函數(shù)大致如下。
(1)窗口創(chuàng)建函數(shù)
這里主要討論函數(shù)Create和CreateEx。它們封裝了Win32窗口創(chuàng)建函數(shù)::CreateWindowEx。Create的原型如下:
BOOL CWnd::Create(LPCTSTR lpszClassName,
LPCTSTR lpszWindowName, DWORD dwStyle,
const RECT& rect,
CWnd* pParentWnd, UINT nID,
CCreateContext* pContext)
Create是一個(gè)虛擬函數(shù),用來(lái)創(chuàng)建子窗口(不能創(chuàng)建桌面窗口和POP UP窗口)。CWnd的基類可以覆蓋該函數(shù),例如邊框窗口類等覆蓋了該函數(shù)以實(shí)現(xiàn)邊框窗口的創(chuàng)建,視類則使用它來(lái)創(chuàng)建視窗口。
Create調(diào)用了成員函數(shù)CreateEx。CWnd::CreateEx的原型如下:
BOOL CWnd::CreateEx(DWORD dwExStyle, LPCTSTR lpszClassName,
LPCTSTR lpszWindowName, DWORD dwStyle,
int x, int y, int nWidth, int nHeight,
HWND hWndParent, HMENU nIDorHMenu, LPVOID lpParam)
CreateEx有11個(gè)參數(shù),它將調(diào)用::CreateWindowEx完成窗口的創(chuàng)建,這11個(gè)參數(shù)對(duì)應(yīng)地傳遞給::CreateWindowEx。參數(shù)指定了窗口擴(kuò)展風(fēng)格、“窗口類”、窗口名、窗口大小和位置、父窗口句柄、窗口菜單和窗口創(chuàng)建參數(shù)。
CreateEx的處理流程將在后面4.4.1節(jié)討論窗口過(guò)程時(shí)分析。
窗口創(chuàng)建時(shí)發(fā)送WM_CREATE消息,消息參數(shù)lParam指向一個(gè)CreateStruct結(jié)構(gòu)的變量,該結(jié)構(gòu)有11個(gè)域,其描述見(jiàn)后面4.4.1節(jié)對(duì)窗口過(guò)程的分析,Windows使用和CreateEx參數(shù)一樣的內(nèi)容填充該變量。
(2)窗口銷毀函數(shù)
例如:
DestroyWindow函數(shù) 銷毀窗口
PostNcDestroy( ),銷毀窗口后調(diào)用,虛擬函數(shù)
(3)用于設(shè)定、獲取、改變窗口屬性的函數(shù),例如:
SetWindowText(CString tiltle) 設(shè)置窗口標(biāo)題
GetWindowText() 得到窗口標(biāo)題
SetIcon(HICON hIcon, BOOL bBigIcon);設(shè)置窗口像標(biāo)
GetIcon( BOOL bBigIcon ) ;得到窗口像標(biāo)
GetDlgItem( int nID);得到窗口類指定ID的控制子窗口
GetDC(); 得到窗口的設(shè)備上下文
SetMenu(CMenu *pMenu); 設(shè)置窗口菜單
GetMenu();得到窗口菜單
…
(4)用于完成窗口動(dòng)作的函數(shù)
用于更新窗口,滾動(dòng)窗口,等等。一部分成員函數(shù)設(shè)計(jì)成或可重載(Overloaded)函數(shù),或虛擬(Overridden)函數(shù),或MFC消息處理函數(shù)。這些函數(shù)或者實(shí)現(xiàn)了一部分功能,或者僅僅是一個(gè)空函數(shù)。如:
有關(guān)消息發(fā)送的函數(shù):
SendMessage( UINT message,WPARAM wParam = 0, LPARAM lParam = 0 );
給窗口發(fā)送發(fā)送消息,立即調(diào)用方式
PostMessage(( UINT message,WPARAM wParam = 0, LPARAM lParam = 0 );
給窗口發(fā)送消息,放進(jìn)消息隊(duì)列
…
有關(guān)改變窗口狀態(tài)的函數(shù)
MoveWindow( LPCRECT lpRect, BOOL bRepaint = TRUE );
移動(dòng)窗口到指定位置
ShowWindow(BOOL );顯示窗口,使之可見(jiàn)或不可見(jiàn)
….
實(shí)現(xiàn)MFC消息處理機(jī)制的函數(shù):
virtual LRESULT WindowProc( UINT message, WPARAM wParam, LPARAM lParam ); 窗口過(guò)程,虛擬函數(shù)
virtual BOOL OnCommand( WPARAM wParam, LPARAM lParam );處理命令消息
…
消息處理函數(shù):
OnCreate( LPCREATESTRUCT lpCreateStruct );MFC窗口消息處理函數(shù),窗口創(chuàng)建時(shí)由MFC框架調(diào)用
OnClose();MFC窗口消息處理函數(shù),窗口創(chuàng)建時(shí)由MFC框架調(diào)用
…
其他功能的函數(shù)
CWnd的導(dǎo)出類是類型更具體、功能更完善的窗口類,它們繼承了CWnd的屬性和方法,并提供了新的成員函數(shù)(消息處理函數(shù)、虛擬函數(shù)、等等)。
常用的窗口類及其層次關(guān)系見(jiàn)圖1-1。
在MFC下創(chuàng)建一個(gè)窗口對(duì)象
MFC下創(chuàng)建一個(gè)窗口對(duì)象分兩步,首先創(chuàng)建MFC窗口對(duì)象,然后創(chuàng)建對(duì)應(yīng)的Windows窗口。在內(nèi)存使用上,MFC窗口對(duì)象可以在?;蛘叨?使用new創(chuàng)建)中創(chuàng)建。具體表述如下:
創(chuàng)建MFC窗口對(duì)象。通過(guò)定義一個(gè)CWnd或其派生類的實(shí)例變量或者動(dòng)態(tài)創(chuàng)建一個(gè)MFC窗口的實(shí)例,前者在??臻g創(chuàng)建一個(gè)MFC窗口對(duì)象,后者在堆空間創(chuàng)建一個(gè)MFC窗口對(duì)象。
調(diào)用相應(yīng)的窗口創(chuàng)建函數(shù),創(chuàng)建Windows窗口對(duì)象。
例如:在前面提到的AppWizard產(chǎn)生的源碼中,有CMainFrame(派生于CMDIFrame(SDI)或者CMDIFrameWnd(MDI))類。它有兩個(gè)成員變量定義如下:[!--empirenews.page--]
CToolBar m_wndToolBar;
CStatusBar m_wndStatusBar;
當(dāng)創(chuàng)建CMainFrame類對(duì)象時(shí),上面兩個(gè)MFC Object也被構(gòu)造。
CMainFrame還有一個(gè)成員函數(shù)
OnCreate(LPCREATESTRUCT lpCreateStruct),
它的實(shí)現(xiàn)包含如下一段代碼,調(diào)用CToolBar和CStatusBar的成員函數(shù)Create來(lái)創(chuàng)建上述兩個(gè)MFC對(duì)象對(duì)應(yīng)的工具欄HWND窗口和狀態(tài)欄HWND窗口:
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
…
if (!m_wndToolBar.Create(this) ||
!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
{
TRACE0("Failed to create toolbar\n");
return -1; // fail to create
}
if (!m_wndStatusBar.Create(this) ||
!m_wndStatusBar.SetIndicators(indicators,
sizeof(indicators)/sizeof(UINT)))
{
TRACE0("Failed to create status bar\n");
return -1; // fail to create
}
…
}
關(guān)于工具欄、狀態(tài)欄將在后續(xù)有關(guān)章節(jié)作詳細(xì)討論。
在MFC中,還提供了一種動(dòng)態(tài)創(chuàng)建技術(shù)。動(dòng)態(tài)創(chuàng)建的過(guò)程實(shí)際上也如上所述分兩步,只不過(guò)MFC使用這個(gè)技術(shù)是由框架自動(dòng)地完成整個(gè)過(guò)程的。通常框架窗口、文檔框架窗口、視使用了動(dòng)態(tài)創(chuàng)建。介于MFC的結(jié)構(gòu),CFrameWnd和CView及其派生類的實(shí)例即使不使用動(dòng)態(tài)創(chuàng)建,也要用new在堆中分配。理由見(jiàn)窗口的銷毀(2.2.5節(jié))。
至于動(dòng)態(tài)創(chuàng)建技術(shù),將在下一章具體討論。
在Windows窗口的創(chuàng)建過(guò)程中,將發(fā)送一些消息,如:
在創(chuàng)建了窗口的非客戶區(qū)(Nonclient area)之后,發(fā)送消息WM_NCCREATE;
在創(chuàng)建了窗口的客戶區(qū)(client area)之后,發(fā)送消息WM_CREATE;
窗口的窗口過(guò)程在窗口顯示之前收到這兩個(gè)消息。
如果是子窗口,在發(fā)送了上述兩個(gè)消息之后,還給父窗口發(fā)送WM_PARENATNOTIFY消息。其他類或風(fēng)格的窗口可能發(fā)送更多的消息,具體參見(jiàn)SDK開(kāi)發(fā)文檔。
MFC窗口的使用
MFC提供了大量的窗口類,其功能和用途各異。程序員應(yīng)該選擇哪些類來(lái)使用,以及怎么使用他們呢?
直接使用MFC提供的窗口類或者先從MFC窗口類派生一個(gè)新的C++類然后使用它,這些在通常情況下都不需要程序員提供窗口注冊(cè)的代碼。是否需要派生新的C++類,視MFC已有的窗口類是否能滿足使用要求而定。派生的C++類繼承了基類的特性并改變或擴(kuò)展了它的功能,例如增加或者改變對(duì)消息、事件的特殊處理等。
主要使用或繼承以下一些MFC窗口類(其層次關(guān)系圖見(jiàn)圖1-1):
框架類CFrameWnd,CMdiFrameWnd;
文檔框架CMdiChildWnd;
視圖CView和CView派生的有特殊功能的視圖如:列表CListView,編輯CEditView,樹(shù)形列表CTreeView,支持RTF的CRichEditView,基于對(duì)話框的視CFormView等等。
對(duì)話框CDialog。
通常,都要從這些類派生應(yīng)用程序的框架窗口和視窗口或者對(duì)話框。
工具條CToolBar
狀態(tài)條CStatusBar
其他各類控制窗口,如列表框CList,編輯框CEdit,組合框CComboBox,按鈕Cbutton等。
通常,直接使用這些類。
在MFC下窗口的銷毀
窗口對(duì)象使用完畢,應(yīng)該銷毀。在MFC下,一個(gè)窗口對(duì)象的銷毀包括HWND窗口對(duì)象的銷毀和MFC窗口對(duì)象的銷毀。一般情況下,MFC編程框架自動(dòng)地處理了這些。
(1)對(duì)CFrameWnd和CView的派生類
這些窗口的關(guān)閉導(dǎo)致銷毀窗口的函數(shù)DestroyWindow被調(diào)用。銷毀Windows窗口時(shí),MFC框架調(diào)用的最后一個(gè)成員函數(shù)是OnNcDestroy函數(shù),該函數(shù)負(fù)責(zé)Windows清理工作,并在最后調(diào)用虛擬成員函數(shù)PostNcDestroy。CFrameWnd和CView的PostNcDestroy調(diào)用delete this刪除自身這個(gè)MFC窗口對(duì)象。
所以,對(duì)這些窗口,如前所述,應(yīng)在堆(Heap)中分配,而且,不要對(duì)這些對(duì)象使用delete操作。
(2)對(duì)Windows Control窗口
在它們的析構(gòu)函數(shù)中,將調(diào)用DestroyWidnow來(lái)銷毀窗口。如果在棧中分配這樣的窗口對(duì)象,則在超出作用范圍的時(shí)候,隨著析構(gòu)函數(shù)的調(diào)用,MFC窗口對(duì)象和它的Windows window對(duì)象都被銷毀。如果在堆(Heap)中分配,則顯式調(diào)用delete操作符,導(dǎo)致析構(gòu)函數(shù)的調(diào)用和窗口的銷毀。
所以,這種類型的窗口應(yīng)盡可能在棧中分配,避免用額外的代碼來(lái)銷毀窗口。如前所述的CMainFrame的成員變量m_wndStatusBar和m_wndToolBar就是這樣的例子。
(3)對(duì)于程序員直接從CWnd派生的窗口
程序員可以在派生類中實(shí)現(xiàn)上述兩種機(jī)制之一,然后,在相應(yīng)的規(guī)范下使用。
后面章節(jié)將詳細(xì)的討論應(yīng)用程序退出時(shí)關(guān)閉、清理窗口的過(guò)程。
設(shè)備描述表
設(shè)備描述表概述
當(dāng)一個(gè)應(yīng)用程序使用GDI函數(shù)時(shí),必須先裝入特定的設(shè)備驅(qū)動(dòng)程序,然后為繪制窗口準(zhǔn)備設(shè)備描述表,比如指定線的寬度和顏色、刷子的樣式和顏色、字體、剪裁區(qū)域等等。不像其他Win32結(jié)構(gòu),設(shè)備描述表不能被直接訪問(wèn),只能通過(guò)系列Win32函數(shù)來(lái)間接地操作。
如同Windows“窗口類”一樣,設(shè)備描述表也是一種Windows數(shù)據(jù)結(jié)構(gòu),用來(lái)描述繪制窗口所需要的信息。它定義了一個(gè)坐標(biāo)映射模式、一組GDI圖形對(duì)象及其屬性。這些GDI對(duì)象包括用于畫(huà)線的筆,繪圖、填圖的刷子,位圖,調(diào)色板,剪裁區(qū)域,及路徑(Path)。
表2-2列出了設(shè)備描述表的結(jié)構(gòu)和各項(xiàng)缺省值,表2-3列出了設(shè)備描述表的類型,表2-4顯示設(shè)備描述表的類型。
表2-2 設(shè)備描述表的結(jié)構(gòu)
屬性缺省值
Background colorBackground color setting from Windows Control Panel (typically, white)
Background modeOPAQUE
BitmapNone
BrushWHITE_BRUSH
Brush origin(0,0)
Clipping regionEntire window or client area with the update region clipped, as appropriate. Child and pop-up windows in the client area may also be clipped
PaletteDEFAULT_PALETTE
Current pen position(0,0)
Device originUpper left corner of the window or the client area[!--empirenews.page--]
Drawing modeR2_COPYPEN
FontSYSTEM_FONT (SYSTEM_FIXED_FONT for applications written to run with Windows versions 3.0 and earlier)
Intercharacter spacing0
Mapping modeMM_TEXT
PenBLACK_PEN
Polygon-fill modeALTERNATE
Stretch modeBLACKONWHITE
Text colorText color setting from Control Panel (typically, black)
Viewport extent(1,1)
Viewport origin(0,0)
Window extent(1,1)
Window origin(0,0)
表2-3 設(shè)備描述表的分類
Display顯示設(shè)備描述表,提供對(duì)視頻顯示設(shè)備上的繪制操作的支持
Printer打印設(shè)備描述表,提供對(duì)打印機(jī)、繪圖儀設(shè)備上的繪制操作的支持
Memory內(nèi)存設(shè)備描述表,提供對(duì)位圖操作的支持
Information信息設(shè)備描述表,提供對(duì)操作設(shè)備信息獲取的支持
表2-3中的顯示設(shè)備描述表又分三種類型,如表2-4所示。
表2-4 顯示設(shè)備描述表的分類
名稱特點(diǎn)功能
Class Device
Contexts提供對(duì)Win16的向后兼容
Common
Device
Contexts在Windows系統(tǒng)的高速緩沖區(qū),數(shù)量有限Applicaion獲取設(shè)備描述表時(shí),Windows用缺省值初始化該設(shè)備描述表,Application使用它完成繪制操作,然后釋放
Private
Device
Contexts沒(méi)有數(shù)量限制,用完不需釋放一次獲取,多次使用多次使用過(guò)程中,每次設(shè)備描述表屬性的任何修改或變化都會(huì)被保存,以支持快速繪制
(1)使用設(shè)備描述表的步驟
要使用設(shè)備描述表,一般有如下步驟:
獲取或者創(chuàng)建設(shè)備描述表;
必要的話,改變?cè)O(shè)備描述表的屬性;
使用設(shè)備描述表完成繪制操作;
釋放或刪除設(shè)備描述表。
Common設(shè)備描述表通過(guò)::GetDC,::GetDCEx,::BeginPaint來(lái)獲得一個(gè)設(shè)備描述表,用畢,用::ReleaseDC或::EndPaint釋放設(shè)備描述表;
Printer設(shè)備描述表通過(guò)::CreateDC創(chuàng)建設(shè)備描述表,用::DeleteDC刪除設(shè)備描述表。
Memory設(shè)備描述表通過(guò)::CreateCompatibleDC創(chuàng)建設(shè)備描述表,用::DeleteDC刪除。
Information設(shè)備描述表通過(guò)::CreateIC創(chuàng)建設(shè)備描述表,用::DeleteDC刪除。
(2)改變?cè)O(shè)備描述表屬性的途徑
要改變?cè)O(shè)備描述表的屬性,可通過(guò)以下途徑:
用::SelectObject選入新的除調(diào)色板以外的GDI Object到設(shè)備描述表中;
對(duì)于調(diào)色板,使用::SelectPalette函數(shù)選入邏輯調(diào)色板,并使用::RealizePalette把邏輯調(diào)色板的入口映射到物理調(diào)色板中。
用其他API函數(shù)改變其他屬性,如::SetMapMode改變映射模式。
設(shè)備描述表在MFC中的實(shí)現(xiàn)
MFC提供了CDC類作為設(shè)備描述表類的基類,它封裝了Windows的HDC設(shè)備描述表對(duì)象和相關(guān)函數(shù)。
CDC類
CDC類包含了各種類型的Windows設(shè)備描述表的全部功能,封裝了所有的Win32 GDI 函數(shù)和設(shè)備描述表相關(guān)的SDK函數(shù)。在MFC下,使用CDC的成員函數(shù)來(lái)完成所有的窗口繪制工作。
CDC 類的結(jié)構(gòu)示意圖2-2所示。
CDC類有兩個(gè)成員變量:m_hDC,m_hAttribDC,它們都是Windows設(shè)備描述表句柄。CDC的成員函數(shù)作輸出操作時(shí),使用m_Hdc;要獲取設(shè)備描述表的屬性時(shí),使用m_hAttribDC。
在創(chuàng)建一個(gè)CDC類實(shí)例時(shí),缺省的m_hDC等于m_hAttribDC。如果需要的話,程序員可以分別指定它們。例如,MFC框架實(shí)現(xiàn)CMetaFileDC類時(shí),就是如此:CMetaFileDC從物理設(shè)備上讀取設(shè)備信息,輸出則送到元文件(metafile)上,所以m_hDC和m_hAttribDC是不同的,各司其責(zé)。還有一個(gè)類似的例子:打印預(yù)覽的實(shí)現(xiàn),一個(gè)代表打印機(jī)模擬輸出,一個(gè)代表屏幕顯示。
CDC封裝::SelectObject(HDC hdc,HGDIOBJECT hgdiobject)函數(shù)時(shí),采用了重載技術(shù),即它針對(duì)不同的GDI對(duì)象,提供了名同而參數(shù)不同的成員函數(shù):
SelectObject(CPen *pen)用于選入筆;
SelectObject(CBitmap* pBitmap)用于選入位圖;
SelectObject(CRgn *pRgn)用于選入剪裁區(qū)域;
SelectObject(CBrush *pBrush)用于選入刷子;
SelectObject(CFont *pFont)用于選入字體;
至于調(diào)色板,使用SelectPalette(CPalette *pPalette,BOOL bForceBackground )選入調(diào)色板到設(shè)備描述表,使用RealizePalletter()實(shí)現(xiàn)邏輯調(diào)色板到物理調(diào)色板的映射。
從CDC派生出功能更具體的設(shè)備描述表
從CDC 派生出四個(gè)功能更具體的設(shè)備描述表類。層次如圖2-3所示。
下面,分別討論派生出的四種設(shè)備描述表。
CCientDC
代表窗口客戶區(qū)的設(shè)備描述表。其構(gòu)造函數(shù)CClientDC(CWnd *pWin)通過(guò)::GetDC獲取指定窗口的客戶區(qū)的設(shè)備描述表HDC,并且使用成員函數(shù)Attach把它和CClientDC對(duì)象捆綁在一起;其析構(gòu)函數(shù)使用成員函數(shù)Detach把設(shè)備描述表句柄HDC分離出來(lái),并調(diào)用::ReleaseDC釋放設(shè)備描述表HDC。
CPaintDC
僅僅用于響應(yīng)WM_PAINT消息時(shí)繪制窗口,因?yàn)樗臉?gòu)造函數(shù)調(diào)用了::BeginPaint獲取設(shè)備描述表HDC,并且使用成員函數(shù)Attach把它和CPaintDC對(duì)象捆綁在一起;析構(gòu)函數(shù)使用成員函數(shù)Detach把設(shè)備描述表句柄HDC分離出來(lái),并調(diào)用::EndPaint釋放設(shè)備描述表HDC,而::BeginPaint和::EndPaint僅僅在響應(yīng)WM_PAINT時(shí)使用。
CMetaFileDC
用于生成元文件。
CWindowDC
代表整個(gè)窗口區(qū)(包括非客戶區(qū))的設(shè)備描述表。其構(gòu)造函數(shù)CWindowDC(CWnd *pWin)通過(guò)::GetWindowDC獲取指定窗口的客戶區(qū)的設(shè)備描述表HDC,并使用Attach把它和CWindowDC對(duì)象捆綁在一起;其析構(gòu)函數(shù)使用Detach把設(shè)備描述表HDC分離出來(lái),調(diào)用::ReleaseDC釋放設(shè)備描述表HDC。
MFC設(shè)備描述表類的使用
使用CPaintDC、CClientDC、CWindowDC的方法
首先,定義一個(gè)這些類的實(shí)例變量,通常在棧中定義。然后,使用它。
例如,MFC中CView對(duì)WM_PAINT消息的實(shí)現(xiàn)方法如下:
void CView::OnPaint()
{
// standard paint routine
CPaintDC dc(this);
OnPrepareDC(&dc);[!--empirenews.page--]
OnDraw(&dc);
}
在棧中定義了CPaintDC類型的變量dc,隨著構(gòu)造函數(shù)的調(diào)用獲取了設(shè)備描述表;設(shè)備描述表使用完畢,超出其有效范圍就被自動(dòng)地清除,隨著析構(gòu)函數(shù)的調(diào)用,其獲取的設(shè)備描述表被釋放。
如果希望在堆中創(chuàng)建,例如
CPaintDC *pDC;
pDC = new CPaintDC(this)
則在使用完畢時(shí),用delete刪除pDC:
delete pDC;
直接使用CDC
需要注意的是:在生成CDC對(duì)象的時(shí)候,并不像它的派生類那樣,在構(gòu)造函數(shù)里獲取相應(yīng)的Windows設(shè)備描述表。最好不要使用::GetDC等函數(shù)來(lái)獲取一個(gè)設(shè)備描述表,而是創(chuàng)建一個(gè)設(shè)備描述表。其構(gòu)造函數(shù)如下:
CDC::CDC()
{
m_hDC = NULL;
m_hAttribDC = NULL;
m_bPrinting = FALSE;
}
其析構(gòu)函數(shù)如下:
CDC::~CDC()
{
if (m_hDC != NULL)
::DeleteDC(Detach());
}
在CDC析構(gòu)函數(shù)中,如果設(shè)備描述表句柄不空,則調(diào)用DeleteDC刪除它。這是直接使用CDC時(shí)最好創(chuàng)建Windows設(shè)備描述表的理由。如果設(shè)備描述表不是創(chuàng)建的,則應(yīng)該在析構(gòu)函數(shù)被調(diào)用前分離出設(shè)備描述表句柄并用::RealeaseDC釋放它,釋放后m_hDC為空,則在析構(gòu)函數(shù)調(diào)用時(shí)不會(huì)執(zhí)行::DeleteDC。當(dāng)然,不用擔(dān)心CDC的派生類的析構(gòu)函數(shù)調(diào)用CDC的析構(gòu)函數(shù),因?yàn)镃DC::~CDC()不是虛擬析構(gòu)函數(shù)。
直接使用CDC的例子是內(nèi)存設(shè)備上下文,例如:
CDC dcMem; //聲明一個(gè)CDC對(duì)象
dcMem.CreateCompatibleDC(&dc); //創(chuàng)建設(shè)備描述表
pbmOld = dcMem.SelectObject(&m_bmBall);//更改設(shè)備描述表屬性
…//作一些繪制操作
dcMem.SelectObject(pbmOld);//恢復(fù)設(shè)備描述表的屬性
dcMem.DeleteDC(); //可以不調(diào)用,而讓析構(gòu)函數(shù)去刪除設(shè)備描述表
GDI對(duì)象
在討論設(shè)備描述表時(shí),已經(jīng)多次涉及到GDI對(duì)象。這里,需強(qiáng)調(diào)一下:GDI對(duì)象要選入Windows 設(shè)備描述表后才能使用;用畢,要恢復(fù)設(shè)備描述表的原GDI對(duì)象,并刪除該GDI對(duì)象。
一般按如下步驟使用GDI對(duì)象:
Create or get a GDI OBJECT hNewGdi;
hOldGdi = ::SelectObject(hdc, hNewGdi)
……
::SelectObject(hdc, hOldGdi)
::DeleteObject(hNewGdi)
先創(chuàng)建或得到一個(gè)GDI對(duì)象,然后把它選入設(shè)備描述表并保存它原來(lái)的GDI對(duì)象;用畢恢復(fù)設(shè)備描述表原來(lái)的GDI對(duì)象并刪除新創(chuàng)建的GDI對(duì)象。
需要指出的是,如果hNewGdi是一個(gè)Stock GDI對(duì)象,可以不刪除(刪除也可以)。通過(guò)
HGDIOBJ GetStockObject(
int fnObject // type of stock object
);
來(lái)獲取Stock GDI對(duì)象。
MFC GDI對(duì)象
MFC用一些類封裝了Windows GDI對(duì)象和相關(guān)函數(shù),層次結(jié)構(gòu)如圖2-4所示:
CGdiObject封裝了Windows GDI Object共有的特性。其派生類在繼承的基礎(chǔ)上,主要封裝了各類GDI的創(chuàng)建函數(shù)以及和具體GDI對(duì)象相關(guān)的操作。
CGdiObject的構(gòu)造函數(shù)僅僅讓m_hObject為空。如果m_hObject不空,其析構(gòu)函數(shù)將刪除對(duì)應(yīng)的Windows GDI對(duì)象。MFC GDI對(duì)象和Windows GDI對(duì)象的關(guān)系如圖2-5所示。
使用MFC GDI類的使用
首先創(chuàng)建GDI對(duì)象,可分一步或兩步創(chuàng)建。一步創(chuàng)建就是構(gòu)造MFC對(duì)象和Windows GDI對(duì)象一步完成;兩步創(chuàng)建則先構(gòu)造MFC對(duì)象,接著創(chuàng)建Windows GDI對(duì)象。然后,把新創(chuàng)建的GDI對(duì)象選進(jìn)設(shè)備描述表,取代原GDI對(duì)象并保存。最后,恢復(fù)原GDI對(duì)象。例如:
void CMyView::OnDraw(CDC *pDC)
{
CPen penBlack; //構(gòu)造MFC CPen對(duì)象
if (penBlack.CreatePen(PS_SOLID, RGB(0, 0, 0)))
{
CPen *pOldPen = pDC->SelectObject(&penBlack)); //選進(jìn)設(shè)備表,保存原筆
…
pDC->SelectObject(pOldPen); //恢復(fù)原筆
}else
{
…
}
}
和在SDK下有一點(diǎn)不同的是:這里沒(méi)有DeleteObject。因?yàn)閳?zhí)行完OnDraw后,棧中的penBlack被銷毀,它的析構(gòu)函數(shù)被調(diào)用,導(dǎo)致DeleteObject的調(diào)用。
還有一點(diǎn)要說(shuō)明:
pDC->SelectObject(&penBlack)返回了一個(gè)CPen *指針,也就是說(shuō),它根據(jù)原來(lái)PEN的句柄創(chuàng)建了一個(gè)MFC CPen對(duì)象。這個(gè)對(duì)象是否需要?jiǎng)h除呢?不必要,因?yàn)樗且粋€(gè)臨時(shí)對(duì)象,MFC框架會(huì)自動(dòng)地刪除它。當(dāng)然,在本函數(shù)執(zhí)行完畢把控制權(quán)返回給主消息循環(huán)之前,該對(duì)象是有效的。
關(guān)于臨時(shí)對(duì)象及MFC處理它們的內(nèi)部機(jī)制,將在后續(xù)章節(jié)詳細(xì)討論。
至此,Windows編程的核心概念:窗口、GDI界面(設(shè)備描述表、GDI對(duì)象等)已經(jīng)陳述清楚,特別揭示了MFC對(duì)這些概念的封裝機(jī)制,并簡(jiǎn)明講述了與這些Windows Object對(duì)應(yīng)的MFC類的使用方法。還有其他Windows概念,可以參見(jiàn)SDK開(kāi)發(fā)文檔。在MFC的實(shí)現(xiàn)上,基本上僅僅是對(duì)和這些概念相關(guān)的Win32函數(shù)的封裝。如果明白了MFC的窗口、GDI界面的封裝機(jī)制,其他就不難了。