MFC(微軟基礎類庫)c++
微軟基礎類庫(英語:Microsoft Foundation Classes,簡稱MFC)是一個微軟公司提供的類庫(class libraries),以C++類的形式封裝了Windows API,而且包含一個應用程序框架,以減小應用程序開發人員的工做量。其中包含的類包含大量Windows句柄封裝類和不少Windows的內建控件和組件的封裝類。程序員
中文名編程
微軟基礎類庫windows
外文名網絡
MicrosoftFoundationClasses數據結構
簡 稱app
MFC框架
類 型ide
C/C++的圖形化界面語言函數
開發者
微軟
特 點
效率損失低
使用者
大衆
MFC(MicrosoftFoundationClasses)是微軟基礎類庫的簡稱,是微軟公司實現的一個c++類庫,主要封裝了大部分的windows API函數,vc++是微軟公司開發的c/c++的集成開發環境,所謂集成開發環境,就是說利用它能夠編輯,編譯,調試,而不是使用多種工具輪換操做,靈活性較大。vc也指它的內部編譯器,集成開發環境必須有一個編譯器內核,例如DevC++其中一個編譯器內核就是gcc。 MFC除了是一個類庫之外,仍是一個框架,在vc++裏新建一個MFC的工程,開發環境會自動幫你產生許多文件,同時它使用了mfcxx.dll。xx是版本,它封裝了mfc內核,因此你在你的代碼看不到本來的SDK編程中的消息循環等等東西,由於MFC框架幫你封裝好了,這樣你就能夠專心的考慮你程序的邏輯,而不是這些每次編程都要重複的東西,可是因爲是通用框架,沒有最好的針對性,固然也就喪失了一些靈活性和效率。可是MFC的封裝很淺,因此效率上損失不大。
MFC Object和Windows Object的關係
MFC中最重要的封裝是對Win32 API的封裝,所以,理解Windows Object和MFC Object (C++對象,一個C++類的實例)之間的關係是理解MFC的關鍵之一。所謂Windows Object(Windows對象)是Win32下用句柄表示的Windows操做系統對象;所謂MFC Object (MFC對象)是C++對象,是一個C++類的實例,這裏(本書範圍內)MFC Object是有特定含義的,指封裝Windows Object的C++ Object,並不是指任意的C++ Object。
MFC Object 和Windows Object是不同的,但二者緊密聯繫。以窗口對象爲例:
一個MFC窗口對象是一個C++ CWnd類(或派生類)的實例,是程序直接建立的。在程序執行中它隨着窗口類構造函數的調用而生成,隨着析構函數的調用而消失。而Windows窗口則是Windows系統的一個內部數據結構的實例,由一個「窗口句柄」標識,Windows系統建立它並給它分配系統資源。Windows窗口在MFC窗口對象建立以後,由CWnd類的Create成員函數建立,「窗口句柄」保存在窗口對象的m_hWnd成員變量中。Windows窗口能夠被一個程序銷燬,也能夠被用戶的動做銷燬。MFC窗口對象和Windows窗口對象的關係如圖2-1所示。其餘的Windows Object和對應的MFC Object也有相似的關係。
下面,對MFC Object和Windows Object做一個比較。有些論斷對設備描述表(MFC類是CDC,句柄是HDC)可能不適用,但具體涉及到時會指出。
從數據結構上比較
MFC Object是相應C++類的實例,這些類是MFC或者程序員定義的;
Windows Object是Windows系統的內部結構,經過一個句柄來引用;
MFC給這些類定義了一個成員變量來保存MFC Object對應的Windows Object的句柄。對於設備描述表CDC類,將保存兩個HDC句柄。
從層次上講比較
MFC Object是高層的,Windows Object是低層的;
MFC Object封裝了Windows Object的大部分或所有功能,MFC Object的使用者不須要直接應用Windows Object的HANDLE(句柄)使用Win32 API,代替它的是引用相應的MFC Object的成員函數。
從建立上比較
MFC Object經過構造函數由程序直接建立;Windows Object由相應的SDK函數建立。
MFC中,使用這些MFC Object,通常分兩步:
首先,建立一個MFC Object,或者在STACK中建立,或者在HEAP中建立,這時,MFC Object的句柄實例變量爲空,或者說不是一個有效的句柄。
而後,調用MFC Object的成員函數建立相應的Windows Object,MFC的句柄變量存儲一個有效句柄。
CDC(設備描述表類)的建立有所不一樣,在後面的2.3節會具體說明CDC及其派生類的建立和使用。
固然,能夠在MFC Object的構造函數中建立相應的Windows對象,MFC的GDI類就是如此實現的,但從實質上講,MFC Object的建立和Windows Object的建立是兩回事。
從轉換上比較
能夠從一個MFC Object獲得對應的Windows Object的句柄;通常使用MFC Object的成員函數GetSafeHandle獲得對應的句柄。
能夠從一個已存在的Windows Object建立一個對應的MFC Object; 通常使用MFC Object的成員函數Attach或者FromHandle來建立,前者獲得一個永久性對象,後者獲得的多是一個臨時對象。
從使用範圍上比較
MFC Object對系統的其餘進程來講是不可見、不可用的;而Windows Object一旦建立,其句柄是整個Windows系統全局的。一些句柄能夠被其餘進程使用。典型地,一個進程能夠得到另外一進程的窗口句柄,並給該窗口發送消息。
對同一個進程的線程來講,只可使用本線程建立的MFC Object,不能使用其餘線程的MFC Object。
從銷燬上比較
MFC Object隨着析構函數的調用而消失;但Windows Object必須由相應的Windows系統函數銷燬。
設備描述表CDC類的對象有所不一樣,它對應的HDC句柄對象可能不是被銷燬,而是被釋放。
固然,能夠在MFC Object的析構函數中完成Windows Object的銷燬,MFC Object的GDI類等就是如此實現的,可是,應該看到:二者的銷燬是不一樣的。
每類Windows Object都有對應的MFC Object,下面用表格的形式列出它們之間的對應關係,如表2-1所示:
表2-1 MFC Object和Windows Object的對應關係
描述
Windows句柄 |
MFC Object |
|
窗口 |
HWND |
CWnd and CWnd-derived classes |
設備上下文 |
HDC |
CDC and CDC-derived classes |
菜單 |
HMENU |
CMenu |
筆 |
HPEN |
CGdiObject類,CPen和CPen-derived classes |
刷子 |
HBRUSH |
CGdiObject類,CBrush和CBrush-derived classes |
字體 |
HFONT |
CGdiObject類,CFont和CFont-derived classes |
位圖 |
HBITMAP |
CGdiObject類,CBitmap和CBitmap-derived classes |
調色板 |
HPALETTE |
CGdiObject類,CPalette和CPalette-derived classes |
區域 |
HRGN |
CGdiObject類,CRgn和CRgn-derived classes |
圖像列表 |
Hp_w_picpathLIST |
Cp_w_picpathList和Cp_w_picpathList-derived classes |
套接字 |
SOCKET |
CSocket,CAsynSocket及其派生類 |
表2-1中的OBJECT分如下幾類:
Windows對象,
設備上下文對象,
GDI對象(BITMAP,BRUSH,FONT,PALETTE,PEN,RGN),
菜單,
圖像列表,
網絡套接字接口。
從廣義上來看,文檔對象和文件能夠看做一對MFC Object和Windows Object,分別用CDocument類和文件句柄描述。
後續幾節分別對前四類做一個簡明扼要的論述。
Windows Object
用SDK的Win32 API編寫各類Windows應用程序,有其共同的規律:首先是編寫WinMain函數,編寫處理消息和事件的窗口過程WndProc,在WinMain裏頭註冊窗口(Register Window),建立窗口,而後開始應用程序的消息循環。
MFC應用程序也不例外,由於MFC是一個創建在SDK API基礎上的編程框架。對程序員來講所不一樣的是:通常狀況下,MFC框架自動完成了Windows登記、建立等工做。
下面,簡要介紹MFC Window對Windows Window的封裝。
Windows的註冊
一個應用程序在建立某個類型的窗口前,必須首先註冊該「窗口類」(Windows Class)。注意,這裏不是C++類的類。Register Window把窗口過程、窗口類型以及其餘類型信息和要登記的窗口類關聯起來。
「窗口類」的數據結構
「窗口類」是Windows系統的數據結構,能夠把它理解爲Windows系統的類型定義,而Windows窗口則是相應「窗口類」的實例。Windows使用一個結構來描述「窗口類」,其定義以下:
typedef struct _WNDCLASSEX {
UINT cbSize; //該結構的字節數
UINT style; //窗口類的風格
WNDPROC lpfnWndProc; //窗口過程
int cbClsExtra;
int cbWndExtra;
HANDLE hInstance; //該窗口類的窗口過程所屬的應用實例
HICON hIcon; //該窗口類所用的像標
HCURSOR hCursor; //該窗口類所用的光標
HBRUSH hbrBackground; //該窗口類所用的背景刷
LPCTSTR lpszMenuName; //該窗口類所用的菜單資源
LPCTSTR lpszClassName; //該窗口類的名稱
HICON hIconSm; //該窗口類所用的小像標
} WNDCLASSEX;
從「窗口類」的定義能夠看出,它包含了一個窗口的重要信息,如窗口風格、窗口過程、顯示和繪製窗口所須要的信息,等等。關於窗口過程,將在後面消息映射等有關章節做詳細論述。
Windows系統在初始化時,會註冊(Register)一些全局的「窗口類」,例如通用控制窗口類。應用程序在建立本身的窗口時,首先必須註冊本身的窗口類。在MFC環境下,有幾種方法能夠用來註冊「窗口類」,下面分別予以討論。
調用AfxRegisterClass註冊
AfxRegisterClass函數是MFC全局函數。AfxRegisterClass的函數原型:
BOOL AFXAPI AfxRegisterClass(WNDCLASS *lpWndClass);
參數lpWndClass是指向WNDCLASS結構的指針,表示一個「窗口類」。
首先,AfxRegisterClass檢查但願註冊的「窗口類」是否已經註冊,若是是則表示已註冊,返回TRUE,不然,繼續處理。
接着,調用::RegisterClass(lpWndClass)註冊窗口類;
而後,若是當前模塊是DLL模塊,則把註冊「窗口類」的名字加入到模塊狀態的域m_szUnregisterList中。該域是一個固定長度的緩衝區,依次存放模塊註冊的「窗口類」的名字(每一個名字是以「\n\0」結尾的字符串)。之因此這樣作,是爲了DLL退出時能自動取消(Unregister)它註冊的窗口類。至於模塊狀態將在後面第9章詳細的討論。
最後,返回TRUE表示成功註冊。
調用AfxRegisterWndClass註冊
AfxRegisterWndClass函數也是MFC全局函數。AfxRegisterWndClass的函數原型:
LPCTSTR AFXAPI AfxRegisterWndClass(UINT nClassStyle,
HCURSOR hCursor, HBRUSH hbrBackground, HICON hIcon)
參數1指定窗口類風格;
參數二、三、4分別指定該窗口類使用的光標、背景刷、像標的句柄,缺省值是0。
此函數根據窗口類屬性動態地產生窗口類的名字,而後,判斷是否該類已經註冊,是則返回窗口類名;不然用指定窗口類的屬性(窗口過程指定爲缺省窗口過程),調用AfxRegisterCalss註冊窗口類,返回類名。
動態產生的窗口類名字由如下幾部分組成(包括冒號分隔符):
若是參數二、三、4所有爲NULL,則由三部分組成。
「Afx」+「:」+模塊實例句柄」+「:」+「窗口類風格」
不然,由六部分組成:
「Afx」+「:」+模塊實例句柄+「:」+「窗口類風格」+「:」+光標句柄+「:」+背景刷句柄+「:」+像標句柄。好比:「Afx:400000:b:13de:6:32cf」。
該函數在MFC註冊主邊框或者文檔邊框「窗口類」時被調用。具體怎樣用在5.3.3.3節會指出。
隱含的使用MFC預約義的的窗口類
MFC4.0之前的版本提供了一些預約義的窗口類,4.0之後再也不預約義這些窗口類。可是,MFC仍然沿用了這些窗口類,例如:
用於子窗口的「AfxWnd」;
用於邊框窗口(SDI主窗口或MDI子窗口)或視的「AfxFrameOrView」;
用於MDI主窗口的「AfxMDIFrame」;
用於標準控制條的「AfxControlBar」。
這些類的名字就 是「AfxWnd」、「AfxFrameOrView」、「AfxMdiFrame」、 「AfxControlBar」加上前綴和後綴(用來標識版本號或是否調試版等)。它們使用標準應用程序像標、標準文檔像標、標準光標等標準資源。爲了使用這些「窗口類」,MFC會在適當的時候註冊這些類:或者要建立該類的窗口時,或者建立應用程序的主窗口時,等等。
MFC內部使用了函數
BOOL AFXAPI AfxEndDeferRegisterClass(short fClass)
來幫助註冊上述原MFC版本的預約義「窗口類」。參數fClass區分了那些預約義窗口的類型。根據不一樣的類型,使用不一樣的窗口類風格、窗口類名字等填充WndClass的域,而後調用AfxRegisterClass註冊窗口類。而且註冊成功以後,經過模塊狀態的m_fRegisteredClasses記錄該窗口類已經註冊,這樣該模塊在再次須要註冊這些窗口類以前能夠查一下m_fRegisteredClasses,若是已經註冊就沒必要浪費時間了。爲此,MFC內部使用宏
AfxDeferRegisterClass(short fClass)
來註冊「窗口類」,若是m_fRegisteredClasses記錄了註冊的窗口類,返回TRUE,不然,調用AfxEndDeferRegisterClass註冊。
註冊這些窗口類的例子:
MFC在加載邊框窗口時,會自動地註冊「AfxFrameOrView」窗口類。在建立視時,就會使用該「窗口類」建立視窗口。固然,若是建立視窗口時,該「窗口類」尚未註冊,MFC將先註冊它而後使用它建立視窗口。
不過,MFC並不使用」AfxMDIFrame」來建立MDI主窗口,由於在加載主窗口時通常都指定了主窗口的資源,MFC使用指定的像標註冊新的MDI主窗口類(經過函數AfxRegisterWndClass完成,所以「窗口類」的名字是動態產生的)。
MDI子窗口相似於上述MDI主窗口的處理。
在MFC建立控制窗口時,如工具欄窗口,若是「AfxControlBar」類尚未註冊,則註冊它。註冊過程很簡單,就是調用::InitCommonControl加載通用控制動態鏈接庫。
調用::RegisterWndClass。
直接調用Win32的窗口註冊函數::RegisterWndClass註冊「窗口類」,這樣作有一個缺點:若是是DLL模塊,這樣註冊的「窗口類」在程序退出時不會自動的被取消註冊(Unregister)。因此必須記得在DLL模塊退出時取消它所註冊的窗口類。
子類化
子類化(Subclass)一個「窗口類」,可自動地獲得它的「窗口類」屬性。
MFC窗口類CWnd
在Windows系統裏,一個窗口的屬性分兩個地方存放:一部分放在「窗口類」裏頭,如上所述的在註冊窗口時指定;另外一部分放在Windows Object自己,如:窗口的尺寸,窗口的位置(X,Y軸),窗口的Z軸順序,窗口的狀態(ACTIVE,MINIMIZED,MAXMIZED,RESTORED…),和其餘窗口的關係(父窗口,子窗口…),窗口是否能夠接收鍵盤或鼠標消息,等等。
爲了表達全部這些窗口的共性,MFC設計了一個窗口基類CWnd。有一點很是重要,那就是CWnd提供了一個標準而通用的MFC窗口過程,MFC下全部的窗口都使用這個窗口過程。至於通用的窗口過程卻能爲各個窗口實現不一樣的操做,那就是MFC消息映射機制的奧祕和做用了。這些,將在後面有關章節詳細論述。
CWnd提供了一系列成員函數,或者是對Win32相關函數的封裝,或者是CWnd新設計的一些函數。這些函數大體以下。
(1)窗口建立函數
這裏主要討論函數Create和CreateEx。它們封裝了Win32窗口建立函數::CreateWindowEx。Create的原型以下:
BOOL CWnd::Create(LPCTSTR lpszClassName,
LPCTSTR lpszWindowName, DWORD dwStyle,
const RECT& rect,
CWnd* pParentWnd, UINT nID,
CCreateContext* pContext)
Create是一個虛擬函數,用來建立子窗口(不能建立桌面窗口和POP UP窗口)。CWnd的基類能夠覆蓋該函數,例如邊框窗口類等覆蓋了該函數以實現邊框窗口的建立,視類則使用它來建立視窗口。
Create調用了成員函數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個參數,它將調用::CreateWindowEx完成窗口的建立,這11個參數對應地傳遞給::CreateWindowEx。參數指定了窗口擴展風格、「窗口類」、窗口名、窗口大小和位置、父窗口句柄、窗口菜單和窗口建立參數。
CreateEx的處理流程將在後面4.4.1節討論窗口過程時分析。
窗口建立時發送WM_CREATE消息,消息參數lParam指向一個CreateStruct結構的變量,該結構有11個域,其描述見後面4.4.1節對窗口過程的分析,Windows使用和CreateEx參數同樣的內容填充該變量。
(2)窗口銷燬函數
例如:
DestroyWindow函數 銷燬窗口
PostNcDestroy( ),銷燬窗口後調用,虛擬函數
(3)用於設定、獲取、改變窗口屬性的函數,例如:
SetWindowText(CString tiltle) 設置窗口標題
GetWindowText() 獲得窗口標題
SetIcon(HICON hIcon, BOOL bBigIcon);設置窗口像標
GetIcon( BOOL bBigIcon ) ;獲得窗口像標
GetDlgItem( int nID);獲得窗口類指定ID的控制子窗口
GetDC(); 獲得窗口的設備上下文
SetMenu(CMenu *pMenu); 設置窗口菜單
GetMenu();獲得窗口菜單
…
(4)用於完成窗口動做的函數
用於更新窗口,滾動窗口,等等。一部分紅員函數設計成或可重載(Overloaded)函數,或虛擬(Overridden)函數,或MFC消息處理函數。這些函數或者實現了一部分功能,或者僅僅是一個空函數。如:
有關消息發送的函數:
SendMessage( UINT message,WPARAM wParam = 0, LPARAM lParam = 0 );
給窗口發送發送消息,當即調用方式
PostMessage(( UINT message,WPARAM wParam = 0, LPARAM lParam = 0 );
給窗口發送消息,放進消息隊列
…
有關改變窗口狀態的函數
MoveWindow( LPCRECT lpRect, BOOL bRepaint = TRUE );
移動窗口到指定位置
ShowWindow(BOOL );顯示窗口,使之可見或不可見
….
實現MFC消息處理機制的函數:
virtual LRESULT WindowProc( UINT message, WPARAM wParam, LPARAM lParam ); 窗口過程,虛擬函數
virtual BOOL OnCommand( WPARAM wParam, LPARAM lParam );處理命令消息
…
消息處理函數:
OnCreate( LPCREATESTRUCT lpCreateStruct );MFC窗口消息處理函數,窗口建立時由MFC框架調用
OnClose();MFC窗口消息處理函數,窗口建立時由MFC框架調用
…
其餘功能的函數
CWnd的導出類是類型更具體、功能更完善的窗口類,它們繼承了CWnd的屬性和方法,並提供了新的成員函數(消息處理函數、虛擬函數、等等)。
經常使用的窗口類及其層次關係見圖1-1。
在MFC下建立一個窗口對象
MFC下建立一個窗口對象分兩步,首先建立MFC窗口對象,而後建立對應的Windows窗口。在內存使用上,MFC窗口對象能夠在棧或者堆(使用new建立)中建立。具體表述以下:
建立MFC窗口對象。經過定義一個CWnd或其派生類的實例變量或者動態建立一個MFC窗口的實例,前者在棧空間建立一個MFC窗口對象,後者在堆空間建立一個MFC窗口對象。
調用相應的窗口建立函數,建立Windows窗口對象。
例如:在前面提到的AppWizard產生的源碼中,有CMainFrame(派生於CMDIFrame(SDI)或者CMDIFrameWnd(MDI))類。它有兩個成員變量定義以下:
CToolBar m_wndToolBar;
CStatusBar m_wndStatusBar;
當建立CMainFrame類對象時,上面兩個MFC Object也被構造。
CMainFrame還有一個成員函數
OnCreate(LPCREATESTRUCT lpCreateStruct),
它的實現包含以下一段代碼,調用CToolBar和CStatusBar的成員函數Create來建立上述兩個MFC對象對應的工具欄HWND窗口和狀態欄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
}
…
}
關於工具欄、狀態欄將在後續有關章節做詳細討論。
在MFC中,還提供了一種動態建立技術。動態建立的過程實際上也如上所述分兩步,只不過MFC使用這個技術是由框架自動地完成整個過程的。一般框架窗口、文檔框架窗口、視使用了動態建立。介於MFC的結構,CFrameWnd和CView及其派生類的實例即便不使用動態建立,也要用new在堆中分配。理由見窗口的銷燬(2.2.5節)。
至於動態建立技術,將在下一章具體討論。
在Windows窗口的建立過程當中,將發送一些消息,如:
在建立了窗口的非客戶區(Nonclient area)以後,發送消息WM_NCCREATE;
在建立了窗口的客戶區(client area)以後,發送消息WM_CREATE;
窗口的窗口過程在窗口顯示以前收到這兩個消息。
若是是子窗口,在發送了上述兩個消息以後,還給父窗口發送WM_PARENATNOTIFY消息。其餘類或風格的窗口可能發送更多的消息,具體參見SDK開發文檔。
MFC窗口的使用
MFC提供了大量的窗口類,其功能和用途各異。程序員應該選擇哪些類來使用,以及怎麼使用他們呢?
直接使用MFC提供的窗口類或者先從MFC窗口類派生一個新的C++類而後使用它,這些在一般狀況下都不須要程序員提供窗口註冊的代碼。是否須要派生新的C++類,視MFC已有的窗口類是否能知足使用要求而定。派生的C++類繼承了基類的特性並改變或擴展了它的功能,例如增長或者改變對消息、事件的特殊處理等。
主要使用或繼承如下一些MFC窗口類(其層次關係圖見圖1-1):
框架類CFrameWnd,CMdiFrameWnd;
文檔框架CMdiChildWnd;
視圖CView和CView派生的有特殊功能的視圖如:列表CListView,編輯CEditView,樹形列表CTreeView,支持RTF的CRichEditView,基於對話框的視CFormView等等。
對話框CDialog。
一般,都要從這些類派生應用程序的框架窗口和視窗口或者對話框。
工具條CToolBar
狀態條CStatusBar
其餘各種控制窗口,如列表框CList,編輯框CEdit,組合框CComboBox,按鈕Cbutton等。
一般,直接使用這些類。
在MFC下窗口的銷燬
窗口對象使用完畢,應該銷燬。在MFC下,一個窗口對象的銷燬包括HWND窗口對象的銷燬和MFC窗口對象的銷燬。通常狀況下,MFC編程框架自動地處理了這些。
(1)對CFrameWnd和CView的派生類
這些窗口的關閉致使銷燬窗口的函數DestroyWindow被調用。銷燬Windows窗口時,MFC框架調用的最後一個成員函數是OnNcDestroy函數,該函數負責Windows清理工做,並在最後調用虛擬成員函數PostNcDestroy。CFrameWnd和CView的PostNcDestroy調用delete this刪除自身這個MFC窗口對象。
因此,對這些窗口,如前所述,應在堆(Heap)中分配,並且,不要對這些對象使用delete操做。
(2)對Windows Control窗口
在它們的析構函數中,將調用DestroyWidnow來銷燬窗口。若是在棧中分配這樣的窗口對象,則在超出做用範圍的時候,隨着析構函數的調用,MFC窗口對象和它的Windows window對象都被銷燬。若是在堆(Heap)中分配,則顯式調用delete操做符,致使析構函數的調用和窗口的銷燬。
因此,這種類型的窗口應儘量在棧中分配,避免用額外的代碼來銷燬窗口。如前所述的CMainFrame的成員變量m_wndStatusBar和m_wndToolBar就是這樣的例子。
(3)對於程序員直接從CWnd派生的窗口
程序員能夠在派生類中實現上述兩種機制之一,而後,在相應的規範下使用。
後面章節將詳細的討論應用程序退出時關閉、清理窗口的過程。
設備描述表
設備描述表概述
當一個應用程序使用GDI函數時,必須先裝入特定的設備驅動程序,而後爲繪製窗口準備設備描述表,好比指定線的寬度和顏色、刷子的樣式和顏色、字體、剪裁區域等等。不像其餘Win32結構,設備描述表不能被直接訪問,只能經過系列Win32函數來間接地操做。
如同Windows「窗口類」同樣,設備描述表也是一種Windows數據結構,用來描述繪製窗口所須要的信息。它定義了一個座標映射模式、一組GDI圖形對象及其屬性。這些GDI對象包括用於畫線的筆,繪圖、填圖的刷子,位圖,調色板,剪裁區域,及路徑(Path)。
表2-2列出了設備描述表的結構和各項缺省值,表2-3列出了設備描述表的類型,表2-4顯示設備描述表的類型。
表2-2 設備描述表的結構
屬性 |
缺省值 |
|
Background color |
Background color setting from Windows Control Panel (typically, white) |
|
Background mode |
OPAQUE |
|
Bitmap |
None |
|
Brush |
WHITE_BRUSH |
|
Brush origin |
(0,0) |
|
Clipping region |
Entire window or client area with the update region clipped, as appropriate. Child and pop-up windows in the client area may also be clipped |
|
Palette |
DEFAULT_PALETTE |
|
Current pen position |
(0,0) |
|
Device origin |
Upper left corner of the window or the client area |
|
Drawing mode |
R2_COPYPEN |
|
Font |
SYSTEM_FONT (SYSTEM_FIXED_FONT for applications written to run with Windows versions 3.0 and earlier) |
|
Intercharacter spacing |
0 |
|
Mapping mode |
MM_TEXT |
|
Pen |
BLACK_PEN |
|
Polygon-fill mode |
ALTERNATE |
|
Stretch mode |
BLACKONWHITE |
|
Text color |
Text 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 設備描述表的分類
Display |
顯示設備描述表,提供對視頻顯示設備上的繪製操做的支持 |
Printer |
打印設備描述表,提供對打印機、繪圖儀設備上的繪製操做的支持 |
Memory |
內存設備描述表,提供對位圖操做的支持 |
Information |
信息設備描述表,提供對操做設備信息獲取的支持 |
表2-3中的顯示設備描述表又分三種類型,如表2-4所示。
表2-4 顯示設備描述表的分類
名稱 |
特色 |
功能 |
Class DeviceContexts |
提供對Win16的向後兼容 |
|
CommonDeviceContexts |
在Windows系統的高速緩衝區,數量有限 |
Applicaion獲取設備描述表時,Windows用缺省值初始化該設備描述表,Application使用它完成繪製操做,而後釋放 |
PrivateDeviceContexts |
沒有數量限制,用完不需釋放一次獲取,屢次使用 |
屢次使用過程當中,每次設備描述表屬性的任何修改或變化都會被保存,以支持快速繪製 |
(1)使用設備描述表的步驟
要使用設備描述表,通常有以下步驟:
獲取或者建立設備描述表;
必要的話,改變設備描述表的屬性;
使用設備描述表完成繪製操做;
釋放或刪除設備描述表。
Common設備描述表經過::GetDC,::GetDCEx,::BeginPaint來得到一個設備描述表,用畢,用::ReleaseDC或::EndPaint釋放設備描述表;
Printer設備描述表經過::CreateDC建立設備描述表,用::DeleteDC刪除設備描述表。
Memory設備描述表經過::CreateCompatibleDC建立設備描述表,用::DeleteDC刪除。
Information設備描述表經過::CreateIC建立設備描述表,用::DeleteDC刪除。
(2)改變設備描述表屬性的途徑
要改變設備描述表的屬性,可經過如下途徑:
用::SelectObject選入新的除調色板之外的GDI Object到設備描述表中;
對於調色板,使用::SelectPalette函數選入邏輯調色板,並使用::RealizePalette把邏輯調色板的入口映射到物理調色板中。
用其餘API函數改變其餘屬性,如::SetMapMode改變映射模式。
設備描述表在MFC中的實現
MFC提供了CDC類做爲設備描述表類的基類,它封裝了Windows的HDC設備描述表對象和相關函數。
CDC類
CDC類包含了各類類型的Windows設備描述表的所有功能,封裝了全部的Win32 GDI 函數和設備描述表相關的SDK函數。在MFC下,使用CDC的成員函數來完成全部的窗口繪製工做。
CDC類有兩個成員變量:m_hDC,m_hAttribDC,它們都是Windows設備描述表句柄。CDC的成員函數做輸出操做時,使用m_Hdc;要獲取設備描述表的屬性時,使用m_hAttribDC。
在建立一個CDC類實例時,缺省的m_hDC等於m_hAttribDC。若是須要的話,程序員能夠分別指定它們。例如,MFC框架實現CMetaFileDC類時,就是如此:CMetaFileDC從物理設備上讀取設備信息,輸出則送到元文件(metafile)上,因此m_hDC和m_hAttribDC是不一樣的,各司其責。還有一個相似的例子:打印預覽的實現,一個表明打印機模擬輸出,一個表明屏幕顯示。
CDC封裝::SelectObject(HDC hdc,HGDIOBJECT hgdiobject)函數時,採用了重載技術,即它針對不一樣的GDI對象,提供了名同而參數不一樣的成員函數:
SelectObject(CPen *pen)用於選入筆;
SelectObject(CBitmap* pBitmap)用於選入位圖;
SelectObject(CRgn *pRgn)用於選入剪裁區域;
SelectObject(CBrush *pBrush)用於選入刷子;
SelectObject(CFont *pFont)用於選入字體;
至於調色板,使用SelectPalette(CPalette *pPalette,BOOL bForceBackground )選入調色板到設備描述表,使用RealizePalletter()實現邏輯調色板到物理調色板的映射。
從CDC派生出功能更具體的設備描述表
下面,分別討論派生出的四種設備描述表。
CClientDC
表明窗口客戶區的設備描述表。其構造函數CClientDC(CWnd *pWin)經過::GetDC獲取指定窗口的客戶區的設備描述表HDC,而且使用成員函數Attach把它和CClientDC對象捆綁在一塊兒;其析構函數使用成員函數Detach把設備描述表句柄HDC分離出來,並調用::ReleaseDC釋放設備描述表HDC。
CPaintDC
僅僅用於響應WM_PAINT消息時繪製窗口,由於它的構造函數調用了::BeginPaint獲取設備描述表HDC,而且使用成員函數Attach把它和CPaintDC對象捆綁在一塊兒;析構函數使用成員函數Detach把設備描述表句柄HDC分離出來,並調用::EndPaint釋放設備描述表HDC,而::BeginPaint和::EndPaint僅僅在響應WM_PAINT時使用。
CMetaFileDC
用於生成元文件。
CWindowDC
表明整個窗口區(包括非客戶區)的設備描述表。其構造函數CWindowDC(CWnd *pWin)經過::GetWindowDC獲取指定窗口的客戶區的設備描述表HDC,並使用Attach把它和CWindowDC對象捆綁在一塊兒;其析構函數使用Detach把設備描述表HDC分離出來,調用::ReleaseDC釋放設備描述表HDC。
MFC設備描述表類的使用
使用CPaintDC、CClientDC、CWindowDC的方法
首先,定義一個這些類的實例變量,一般在棧中定義。而後,使用它。
例如,MFC中CView對WM_PAINT消息的實現方法以下:
void CView::OnPaint()
{
// standard paint routine
CPaintDC dc(this);
OnPrepareDC(&dc);
OnDraw(&dc);
}
在棧中定義了CPaintDC類型的變量dc,隨着構造函數的調用獲取了設備描述表;設備描述表使用完畢,超出其有效範圍就被自動地清除,隨着析構函數的調用,其獲取的設備描述表被釋放。
若是但願在堆中建立,例如
CPaintDC *pDC;
pDC = new CPaintDC(this)
則在使用完畢時,用delete刪除pDC:
delete pDC;
直接使用CDC
須要注意的是:在生成CDC對象的時候,並不像它的派生類那樣,在構造函數裏獲取相應的Windows設備描述表。最好不要使用::GetDC等函數來獲取一個設備描述表,而是建立一個設備描述表。其構造函數以下:
CDC::CDC()
{
m_hDC = NULL;
m_hAttribDC = NULL;
m_bPrinting = FALSE;
}
其析構函數以下:
CDC::~CDC()
{
if (m_hDC != NULL)
::DeleteDC(Detach());
}
在CDC析構函數中,若是設備描述表句柄不空,則調用DeleteDC刪除它。這是直接使用CDC時最好建立Windows設備描述表的理由。若是設備描述表不是建立的,則應該在析構函數被調用前分離出設備描述表句柄並用::RealeaseDC釋放它,釋放後m_hDC爲空,則在析構函數調用時不會執行::DeleteDC。固然,不用擔憂CDC的派生類的析構函數調用CDC的析構函數,由於CDC::~CDC()不是虛擬析構函數。
直接使用CDC的例子是內存設備上下文,例如:
CDC dcMem; //聲明一個CDC對象
dcMem.CreateCompatibleDC(&dc); //建立設備描述表
pbmOld = dcMem.SelectObject(&m_bmBall);//更改設備描述表屬性
…//做一些繪製操做
dcMem.SelectObject(pbmOld);//恢復設備描述表的屬性
dcMem.DeleteDC(); //能夠不調用,而讓析構函數去刪除設備描述表
GDI對象
在討論設備描述表時,已經屢次涉及到GDI對象。這裏,需強調一下:GDI對象要選入Windows 設備描述表後才能使用;用畢,要恢復設備描述表的原GDI對象,並刪除該GDI對象。
通常按以下步驟使用GDI對象:
Create or get a GDI OBJECT hNewGdi;
hOldGdi = ::SelectObject(hdc, hNewGdi)
……
::SelectObject(hdc, hOldGdi)
::DeleteObject(hNewGdi)
先建立或獲得一個GDI對象,而後把它選入設備描述表並保存它原來的GDI對象;用畢恢復設備描述表原來的GDI對象並刪除新建立的GDI對象。
須要指出的是,若是hNewGdi是一個Stock GDI對象,能夠不刪除(刪除也能夠)。經過
HGDIOBJ GetStockObject(
int fnObject // type of stock object
);
來獲取Stock GDI對象。
MFC GDI對象
MFC用一些類封裝了Windows GDI對象和相關函數,
CGdiObject封裝了Windows GDI Object共有的特性。其派生類在繼承的基礎上,主要封裝了各種GDI的建立函數以及和具體GDI對象相關的操做。
CGdiObject的構造函數僅僅讓m_hObject爲空。若是m_hObject不空,其析構函數將刪除對應的Windows GDI對象。MFC GDI對象和Windows GDI對象的關係如圖2-5所示。
使用MFC GDI類的使用
首先建立GDI對象,可分一步或兩步建立。一步建立就是構造MFC對象和Windows GDI對象一步完成;兩步建立則先構造MFC對象,接着建立Windows GDI對象。而後,把新建立的GDI對象選進設備描述表,取代原GDI對象並保存。最後,恢復原GDI對象。例如:
void CMyView::OnDraw(CDC *pDC)
{
CPen penBlack; //構造MFC CPen對象
if (penBlack.CreatePen(PS_SOLID, RGB(0, 0, 0)))
{
CPen *pOldPen = pDC->SelectObject(&penBlack)); //選進設備表,保存原筆
…
pDC->SelectObject(pOldPen); //恢復原筆
}else
{
…
}
}
和在SDK下有一點不一樣的是:這裏沒有DeleteObject。由於執行完OnDraw後,棧中的penBlack被銷燬,它的析構函數被調用,致使DeleteObject的調用。
還有一點要說明:
pDC->SelectObject(&penBlack)返回了一個CPen *指針,也就是說,它根據原來PEN的句柄建立了一個MFC CPen對象。這個對象是否須要刪除呢?沒必要要,由於它是一個臨時對象,MFC框架會自動地刪除它。固然,在本函數執行完畢把控制權返回給主消息循環以前,該對象是有效的。
關於臨時對象及MFC處理它們的內部機制,將在後續章節詳細討論。
至此,Windows編程的核心概念:窗口、GDI界面(設備描述表、GDI對象等)已經陳述清楚,特別揭示了MFC對這些概念的封裝機制,並簡明講述了與這些Windows Object對應的MFC類的使用方法。還有其餘Windows概念,能夠參見SDK開發文檔。在MFC的實現上,基本上僅僅是對和這些概念相關的Win32函數的封裝。若是明白了MFC的窗口、GDI界面的封裝機制,其餘就不難了。