MFC[C語言的圖形化界面語言]

MFC[C語言的圖形化界面語言]
MFC[C語言的圖形化界面語言]
更多義項 ▼ 收起列表 ▲

微軟基礎類庫(英語:Microsoft Foundation Classes,簡稱MFC)是一個微軟公司提供的類庫(class libraries),以C++類的形式封裝了Windows API,並且包含一個應用程式框架,以減少應用程式開發人員的工作量。其中包含的類包含大量Windows句柄封裝類和很多Windows的內建控制項和組件的封裝類。

基本信息

定義

過程過程
MFC(MicrosoftFoundationClasses)是微軟基礎類庫的簡稱,是微軟公司實現的一個c++類庫,主要封裝了大部分的windowsAPI函式,vc++是微軟公司開發的c/c++的集成開發環境,所謂集成開發環境,就是說利用它你可以編輯,編譯,調試,而不是使用多種工具輪換操作,靈活性較大。有時人們說vc呢也指它的內部編譯器,集成開發環境必須有一個編譯器核心,要不有什麼用,例如DevC++其中一個編譯器核心就是gcc。MFC除了是一個類庫以外,還是一個框架,你應該試過,在vc++里新建一個MFC的工程,開發環境會自動幫你產生許多檔案,同時它使用了mfcxx.dll。xx是版本,它封裝了mfc核心,所以你在你的代碼看不到原本的SDK編程中的訊息循環等等東西,因為MFC框架幫你封裝好了,這樣你就可以專心的考慮你程式的邏輯,而不是這些每次編程都要重複的東西,但是由於是通用框架,沒有最好的針對性,當然也就喪失了一些靈活性和效率但是MFC的封裝很淺,所以效率上損失不大。

MFC和Win32

關係

圖形化編程圖形化編程
MFC中最重要的封裝是對Win32API的封裝,因此,理解WindowsObject和MFCObject(C++對象,一個C++類的實例)之間的關係是理解MFC的關鍵之一。所謂WindowsObject(Windows對象)是Win32下用句柄表示的Windows作業系統對象;所謂MFCObject(MFC對象)是C++對象,是一個C++類的實例,這裡(本書範圍內)MFCObject是有特定含義的,指封裝WindowsObject的C++Object,並非指任意的C++Object。
MFCObject和WindowsObject是不一樣的,但兩者緊密聯繫。以視窗對象為例:
一個MFC視窗對象是一個C++CWnd類(或派生類)的實例,是程式直接創建的。在程式執行中它隨著視窗類構造函式的調用而生成,隨著析構函式的調用而消失。而Windows視窗則是Windows系統的一個內部數據結構的實例,由一個“視窗句柄”標識,Windows系統創建它並給它分配系統資源。Windows視窗在MFC視窗對象創建之後,由CWnd類的Create成員函式創建,“視窗句柄”保存在視窗對象的m_hWnd成員變數中。Windows視窗可以被一個程式銷毀,也可以被用戶的動作銷毀。MFC視窗對象和Windows視窗對象的關係如圖2-1所示。其他的WindowsObject和對應的MFCObject也有類似的關係。
下面,對MFCObject和WindowsObject作一個比較。有些論斷對設備描述表(MFC類是CDC,句柄是HDC)可能不適用,但具體涉及到時會指出。
從數據結構上比較
MFCObject是相應C++類的實例,這些類是MFC或者程式設計師定義的;
WindowsObject是Windows系統的內部結構,通過一個句柄來引用;
MFC給這些類定義了一個成員變數來保存MFCObject對應的WindowsObject的句柄。對於設備描述表CDC類,將保存兩個HDC句柄。
從層次上講比較
MFCObject是高層的,WindowsObject是低層的;
MFCObject封裝了WindowsObject的大部分或全部功能,MFCObject的使用者不需要直接套用WindowsObject的HANDLE(句柄)使用Win32API,代替它的是引用相應的MFCObject的成員函式。
從創建上比較
MFCObject通過構造函式由程式直接創建;WindowsObject由相應的SDK函式創建。
MFC中,使用這些MFCObject,一般分兩步:
首先,創建一個MFCObject,或者在STACK中創建,或者在HEAP中創建,這時,MFCObject的句柄實例變數為空,或者說不是一個有效的句柄。
然後,調用MFCObject的成員函式創建相應的WindowsObject,MFC的句柄變數存儲一個有效句柄。
CDC(設備描述表類)的創建有所不同,在後面的2.3節會具體說明CDC及其派生類的創建和使用。
當然,可以在MFCObject的構造函式中創建相應的Windows對象,MFC的GDI類就是如此實現的,但從實質上講,MFCObject的創建和WindowsObject的創建是兩回事。
從轉換上比較
可以從一個MFCObject得到對應的WindowsObject的句柄;一般使用MFCObject的成員函式GetSafeHandle得到對應的句柄。
可以從一個已存在的WindowsObject創建一個對應的MFCObject;一般使用MFCObject的成員函式Attach或者FromHandle來創建,前者得到一個永久性對象,後者得到的可能是一個臨時對象。
從使用範圍上比較
MFCObject對系統的其他進程來說是不可見、不可用的;而WindowsObject一旦創建,其句柄是整個Windows系統全局的。一些句柄可以被其他進程使用。典型地,一個進程可以獲得另一進程的視窗句柄,並給該視窗傳送訊息。
對同一個進程的執行緒來說,只可以使用本執行緒創建的MFCObject,不能使用其他執行緒的MFCObject。
從銷毀上比較
MFCObject隨著析構函式的調用而消失;但WindowsObject必須由相應的Windows系統函式銷毀。
設備描述表CDC類的對象有所不同,它對應的HDC句柄對象可能不是被銷毀,而是被釋放。
當然,可以在MFCObject的析構函式中完成WindowsObject的銷毀,MFCObject的GDI類等就是如此實現的,但是,應該看到:兩者的銷毀是不同的。

每類WindowsObject都有對應的MFCObject,下面用表格的形式列出它們之間的對應關係,如表2-1所示:
表2-1MFCObject和WindowsObject的對應關係

描述 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
圖像列表 HimageLIST CimageList和CimageList-derived classes
套接字 SOCKET CSocket,CAsynSocket及其派生類

表2-1中的OBJECT分以下幾類:
Windows對象,設備上下文對象,GDI對象(BITMAP,BRUSH,FONT,PALETTE,PEN,RGN),選單,圖像列表,網路套接字接口。
從廣義上來看,文檔對象和檔案可以看作一對MFCObject和WindowsObject,分別用CDocument類和檔案句柄描述。
後續幾節分別對前四類作一個簡明扼要的論述

WindowsObject

用SDK的Win32API編寫各種Windows應用程式,有其共同的規律:首先是編寫WinMain函式,編寫處理訊息和事件的視窗過程WndProc,在WinMain裡頭註冊視窗(RegisterWindow),創建視窗,然後開始應用程式的訊息循環。
MFC應用程式也不例外,因為MFC是一個建立在SDKAPI基礎上的編程框架。對程式設計師來說所不同的是:一般情況下,MFC框架自動完成了Windows登記、創建等工作。
下面,簡要介紹MFCWindow對WindowsWindow的封裝。

Windows的註冊

一個應用程式在創建某個類型的視窗前,必須首先註冊該“視窗類”(WindowsClass)。注意,這裡不是C++類的類。RegisterWindow把視窗過程、視窗類型以及其他類型信息和要登記的視窗類關聯起來。

“視窗類”的數據結構

“視窗類”是Windows系統的數據結構,可以把它理解為Windows系統的類型定義,而Windows視窗則是相應“視窗類”的實例。Windows使用一個結構來描述“視窗類”,其定義如下:
typedefstruct_WNDCLASSEX{
UINTcbSize;//該結構的位元組數
UINTstyle;//視窗類的風格
WNDPROClpfnWndProc;//視窗過程
intcbClsExtra;
intcbWndExtra;
HANDLEhInstance;//該視窗類的視窗過程所屬的套用實例
HICONhIcon;//該視窗類所用的像標
HCURSORhCursor;//該視窗類所用的游標
HBRUSHhbrBackground;//該視窗類所用的背景刷
LPCTSTRlpszMenuName;//該視窗類所用的選單資源
LPCTSTRlpszClassName;//該視窗類的名稱
HICONhIconSm;//該視窗類所用的小像標
}WNDCLASSEX;
從“視窗類”的定義可以看出,它包含了一個視窗的重要信息,如視窗風格、視窗過程、顯示和繪製視窗所需要的信息,等等。關於視窗過程,將在後面訊息映射等有關章節作詳細論述。
Windows系統在初始化時,會註冊(Register)一些全局的“視窗類”,例如通用控制視窗類。應用程式在創建自己的視窗時,首先必須註冊自己的視窗類。在MFC環境下,有幾種方法可以用來註冊“視窗類”,下面分別予以討論。
調用AfxRegisterClass註冊
AfxRegisterClass函式是MFC全局函式。AfxRegisterClass的函式原型:
BOOLAFXAPIAfxRegisterClass(WNDCLASS*lpWndClass);
參數lpWndClass是指向WNDCLASS結構的指針,表示一個“視窗類”。
首先,AfxRegisterClass檢查希望註冊的“視窗類”是否已經註冊,如果是則表示已註冊,返回TRUE,否則,繼續處理。
接著,調用::RegisterClass(lpWndClass)註冊視窗類;
然後,如果當前模組是DLL模組,則把註冊“視窗類”的名字加入到模組狀態的域m_szUnregisterList中。該域是一個固定長度的緩衝區,依次存放模組註冊的“視窗類”的名字(每個名字是以“\n\0”結尾的字元串)。之所以這樣做,是為了DLL退出時能自動取消(Unregister)它註冊的視窗類。至於模組狀態將在後面第9章詳細的討論。
最後,返回TRUE表示成功註冊。
調用AfxRegisterWndClass註冊
AfxRegisterWndClass函式也是MFC全局函式。AfxRegisterWndClass的函式原型:
LPCTSTRAFXAPIAfxRegisterWndClass(UINTnClassStyle,
HCURSORhCursor,HBRUSHhbrBackground,HICONhIcon)
參數1指定視窗類風格;
參數2、3、4分別指定該視窗類使用的游標、背景刷、像標的句柄,預設值是0。
此函式根據視窗類屬性動態地產生視窗類的名字,然後,判斷是否該類已經註冊,是則返回視窗類名;否則用指定視窗類的屬性(視窗過程指定為預設視窗過程),調用AfxRegisterCalss註冊視窗類,返回類名。
動態產生的視窗類名字由以下幾部分組成(包括冒號分隔設定):
如果參數2、3、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內部使用了函式
BOOLAFXAPIAfxEndDeferRegisterClass(shortfClass)
來幫助註冊上述原MFC版本的預定義“視窗類”。參數fClass區分了那些預定義視窗的類型。根據不同的類型,使用不同的視窗類風格、視窗類名字等填充WndClass的域,然後調用AfxRegisterClass註冊視窗類。並且註冊成功之後,通過模組狀態的m_fRegisteredClasses記錄該視窗類已經註冊,這樣該模組在再次需要註冊這些視窗類之前可以查一下m_fRegisteredClasses,如果已經註冊就不必浪費時間了。為此,MFC內部使用宏
AfxDeferRegisterClass(shortfClass)
來註冊“視窗類”,如果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系統里,一個視窗的屬性分兩個地方存放:一部分放在“視窗類”裡頭,如上所述的在註冊視窗時指定;另一部分放在WindowsObject本身,如:視窗的尺寸,視窗的位置(X,Y軸),視窗的Z軸順序,視窗的狀態(ACTIVE,MINIMIZED,MAXMIZED,RESTORED…),和其他視窗的關係(父視窗,子視窗…),視窗是否可以接收鍵盤或滑鼠訊息,等等。
為了表達所有這些視窗的共性,MFC設計了一個視窗基類CWnd。有一點非常重要,那就是CWnd提供了一個標準而通用的MFC視窗過程,MFC下所有的視窗都使用這個視窗過程。至於通用的視窗過程卻能為各個視窗實現不同的操作,那就是MFC訊息映射機制的奧秘和作用了。這些,將在後面有關章節詳細論述。
CWnd提供了一系列成員函式,或者是對Win32相關函式的封裝,或者是CWnd新設計的一些函式。這些函式大致如下。
(1)視窗創建函式
這裡主要討論函式Create和CreateEx。它們封裝了Win32視窗創建函式::CreateWindowEx。Create的原型如下:
BOOLCWnd::Create(LPCTSTRlpszClassName,
LPCTSTRlpszWindowName,DWORDdwStyle,
constRECT&rect,
CWnd*pParentWnd,UINTnID,
CCreateContext*pContext)
Create是一個虛擬函式,用來創建子視窗(不能創建桌面視窗和POPUP視窗)。CWnd的基類可以覆蓋該函式,例如框線視窗類等覆蓋了該函式以實現框線視窗的創建,視類則使用它來創建視視窗。
Create調用了成員函式CreateEx。CWnd::CreateEx的原型如下:
BOOLCWnd::CreateEx(DWORDdwExStyle,LPCTSTRlpszClassName,
LPCTSTRlpszWindowName,DWORDdwStyle,
intx,inty,intnWidth,intnHeight,
HWNDhWndParent,HMENUnIDorHMenu,LPVOIDlpParam)
CreateEx有11個參數,它將調用::CreateWindowEx完成視窗的創建,這11個參數對應地傳遞給::CreateWindowEx。參數指定了視窗擴展風格、“視窗類”、視窗名、視窗大小和位置、父視窗句柄、視窗選單和視窗創建參數。
CreateEx的處理流程將在後面4.4.1節討論視窗過程時分析。
視窗創建時傳送WM_CREATE訊息,訊息參數lParam指向一個CreateStruct結構的變數,該結構有11個域,其描述見後面4.4.1節對視窗過程的分析,Windows使用和CreateEx參數一樣的內容填充該變數。
(2)視窗銷毀函式
例如:
DestroyWindow函式銷毀視窗
PostNcDestroy(),銷毀視窗後調用,虛擬函式
(3)用於設定、獲取、改變視窗屬性的函式,例如:
SetWindowText(CStringtiltle)設定視窗標題
GetWindowText()得到視窗標題
SetIcon(HICONhIcon,BOOLbBigIcon);設定視窗像標
GetIcon(BOOLbBigIcon);得到視窗像標
GetDlgItem(intnID);得到視窗類指定ID的控制子視窗
GetDC();得到視窗的設備上下文
SetMenu(CMenu*pMenu);設定視窗選單
GetMenu();得到視窗選單

(4)用於完成視窗動作的函式
用於更新視窗,滾動視窗,等等。一部分成員函式設計成或可重載(Overloaded)函式,或虛擬(Overridden)函式,或MFC訊息處理函式。這些函式或者實現了一部分功能,或者僅僅是一個空函式。如:
有關訊息傳送的函式:
SendMessage(UINTmessage,WPARAMwParam=0,LPARAMlParam=0);
給視窗傳送傳送訊息,立即調用方式
PostMessage((UINTmessage,WPARAMwParam=0,LPARAMlParam=0);
給視窗傳送訊息,放進訊息佇列

有關改變視窗狀態的函式
MoveWindow(LPCRECTlpRect,BOOLbRepaint=TRUE);
移動視窗到指定位置
ShowWindow(BOOL);顯示視窗,使之可見或不可見
….
實現MFC訊息處理機制的函式:
virtualLRESULTWindowProc(UINTmessage,WPARAMwParam,LPARAMlParam);視窗過程,虛擬函式
virtualBOOLOnCommand(WPARAMwParam,LPARAMlParam);處理命令訊息

訊息處理函式:
OnCreate(LPCREATESTRUCTlpCreateStruct);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))類。它有兩個成員變數定義如下:
CToolBarm_wndToolBar;
CStatusBarm_wndStatusBar;
當創建CMainFrame類對象時,上面兩個MFCObject也被構造。
CMainFrame還有一個成員函式
OnCreate(LPCREATESTRUCTlpCreateStruct),
它的實現包含如下一段代碼,調用CToolBar和CStatusBar的成員函式Create來創建上述兩個MFC對象對應的工具列HWND視窗和狀態欄HWND視窗:
intCMainFrame::OnCreate(LPCREATESTRUCTlpCreateStruct)
{

if(!m_wndToolBar.Create(this)||
!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
{
TRACE0("Failedtocreatetoolbar\n");
return-1;//failtocreate
}
if(!m_wndStatusBar.Create(this)||
!m_wndStatusBar.SetIndicators(indicators,
sizeof(indicators)/sizeof(UINT)))
{
TRACE0("Failedtocreatestatusbar\n");
return-1;//failtocreate
}

}
關於工具列、狀態欄將在後續有關章節作詳細討論。
在MFC中,還提供了一種動態創建技術。動態創建的過程實際上也如上所述分兩步,只不過MFC使用這個技術是由框架自動地完成整個過程的。通常框架視窗、文檔框架視窗、視使用了動態創建。介於MFC的結構,CFrameWnd和CView及其派生類的實例即使不使用動態創建,也要用new在堆中分配。理由見視窗的銷毀(2.2.5節)。
至於動態創建技術,將在下一章具體討論。
在Windows視窗的創建過程中,將傳送一些訊息,如:
在創建了視窗的非客戶區(Nonclientarea)之後,傳送訊息WM_NCCREATE;
在創建了視窗的客戶區(clientarea)之後,傳送訊息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調用deletethis刪除自身這個MFC視窗對象。
所以,對這些視窗,如前所述,應在堆(Heap)中分配,而且,不要對這些對象使用delete操作。
(2)對WindowsControl視窗
在它們的析構函式中,將調用DestroyWidnow來銷毀視窗。如果在棧中分配這樣的視窗對象,則在超出作用範圍的時候,隨著析構函式的調用,MFC視窗對象和它的Windowswindow對象都被銷毀。如果在堆(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選入新的除調色板以外的GDIObject到設備描述表中;
對於調色板,使用::SelectPalette函式選入邏輯調色板,並使用::RealizePalette把邏輯調色板的入口映射到物理調色板中。
用其他API函式改變其他屬性,如::SetMapMode改變映射模式。

設備描述表在MFC中的實現

MFC提供了CDC類作為設備描述表類的基類,它封裝了Windows的HDC設備描述表對象和相關函式。
CDC類
CDC類包含了各種類型的Windows設備描述表的全部功能,封裝了所有的Win32GDI函式和設備描述表相關的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(HDChdc,HGDIOBJECThgdiobject)函式時,採用了重載技術,即它針對不同的GDI對象,提供了名同而參數不同的成員函式:
SelectObject(CPen*pen)用於選入筆;
SelectObject(CBitmap*pBitmap)用於選入點陣圖;
SelectObject(CRgn*pRgn)用於選入剪裁區域;
SelectObject(CBrush*pBrush)用於選入刷子;
SelectObject(CFont*pFont)用於選入字型;
至於調色板,使用SelectPalette(CPalette*pPalette,BOOLbForceBackground)選入調色板到設備描述表,使用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訊息的實現方法如下:
voidCView::OnPaint()
{
//standardpaintroutine
CPaintDCdc(this);
OnPrepareDC(&dc);
OnDraw(&dc);
}
在棧中定義了CPaintDC類型的變數dc,隨著構造函式的調用獲取了設備描述表;設備描述表使用完畢,超出其有效範圍就被自動地清除,隨著析構函式的調用,其獲取的設備描述表被釋放。
如果希望在堆中創建,例如
CPaintDC*pDC;
pDC=newCPaintDC(this)
則在使用完畢時,用delete刪除pDC:
deletepDC;
直接使用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的例子是記憶體設備上下文,例如:
CDCdcMem;//聲明一個CDC對象
dcMem.CreateCompatibleDC(&dc);//創建設備描述表
pbmOld=dcMem.SelectObject(&m_bmBall);//更改設備描述表屬性
…//作一些繪製操作
dcMem.SelectObject(pbmOld);//恢復設備描述表的屬性
dcMem.DeleteDC();//可以不調用,而讓析構函式去刪除設備描述表

GDI對象

在討論設備描述表時,已經多次涉及到GDI對象。這裡,需強調一下:GDI對象要選入Windows設備描述表後才能使用;用畢,要恢復設備描述表的原GDI對象,並刪除該GDI對象。
一般按如下步驟使用GDI對象:
CreateorgetaGDIOBJECThNewGdi;
hOldGdi=::SelectObject(hdc,hNewGdi)
……
::SelectObject(hdc,hOldGdi)
::DeleteObject(hNewGdi)
先創建或得到一個GDI對象,然後把它選入設備描述表並保存它原來的GDI對象;用畢恢復設備描述表原來的GDI對象並刪除新創建的GDI對象。
需要指出的是,如果hNewGdi是一個StockGDI對象,可以不刪除(刪除也可以)。通過
HGDIOBJGetStockObject(
intfnObject//typeofstockobject
);
來獲取StockGDI對象。
MFCGDI對象
MFC用一些類封裝了WindowsGDI對象和相關函式,
CGdiObject封裝了WindowsGDIObject共有的特性。其派生類在繼承的基礎上,主要封裝了各類GDI的創建函式以及和具體GDI對象相關的操作。
CGdiObject的構造函式僅僅讓m_hObject為空。如果m_hObject不空,其析構函式將刪除對應的WindowsGDI對象。MFCGDI對象和WindowsGDI對象的關係如圖2-5所示。
使用MFCGDI類的使用
首先創建GDI對象,可分一步或兩步創建。一步創建就是構造MFC對象和WindowsGDI對象一步完成;兩步創建則先構造MFC對象,接著創建WindowsGDI對象。然後,把新創建的GDI對象選進設備描述表,取代原GDI對象並保存。最後,恢復原GDI對象。例如:
voidCMyView::OnDraw(CDC*pDC)
{
CPenpenBlack;//構造MFCCPen對象
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的句柄創建了一個MFCCPen對象。這個對象是否需要刪除呢?不必要,因為它是一個臨時對象,MFC框架會自動地刪除它。當然,在本函式執行完畢把控制權返回給主訊息循環之前,該對象是有效的。
關於臨時對象及MFC處理它們的內部機制,將在後續章節詳細討論。
至此,Windows編程的核心概念:視窗、GDI界面(設備描述表、GDI對象等)已經陳述清楚,特別揭示了MFC對這些概念的封裝機制,並簡明講述了與這些WindowsObject對應的MFC類的使用方法。還有其他Windows概念,可以參見SDK開發文檔。在MFC的實現上,基本上僅僅是對和這些概念相關的Win32函式的封裝。如果明白了MFC的視窗、GDI界面的封裝機制,其他就不難了。

相關詞條

相關搜尋

熱門詞條

聯絡我們