對用戶來說,用MFC開發的最終應用程序具有標準的、熟悉的Windows界面,這樣的應用程序易學易用;另外,新的應用程序還能立即支持所有標準Windows特性,而且是用普通的、明確定義的形式。事實上,也就是在Windows應用程序界面基礎上定義了一種新的標準——MFC標準。
爲了更好的理解MFC,我們有必要了解一下MFC的歷史。
2.4.1 MFC歷史
開始,Microsoft建立了一個AFX小組,AFX代表Application Framework,即應用程序框架。據說創建該小組原意是爲了發佈一個Borland C++的OWL的競爭性產品,因爲那時侯Borland 公司的應用程序框架OWL(object Windows Language)已經做的相當成功。AFX小組象OWL那樣,提出了一個高度抽象Windows API的一個類庫。
他們採用自頂向下的設計方法,逐步將對象抽象出來,並施加到Windows上。然後,他們試着花了幾個月時間用這個類庫來編寫應用程序,結果發現這個類庫偏離Windows API實在太遠,過分抽象並沒有太大的實用性,相反大大降低了應用程序的效率。
於是,他們乾脆放棄了整個AFX類庫,對類庫進行重新設計。這次,他們採用了自底向上的方法,從已有的Windows API着手,將類建立在Windows API對象基礎上,設計出後來成爲MFC1.0的一個類庫。但是,你現在仍然可以看到AFX時期的痕跡,許多源程序文件有afx前綴,如afxabort.cpp,afxmem.cpp。MFC延用了許多AFX類庫的宏,因此我們經常會看到以AFX開頭的宏。
AFX小組實際上做了兩件工作:MFC類庫和對MFC的IDE支持(即資源編譯器和操作嚮導)。在1994年4月份之後,AFX的名字停止使用,該小組成員成爲Visual C++開發組的一部分,即現在的MFC小組。
MFC1.0版於1992年同Microsoft C/C++7.0同時發佈。它提供了對Windows API簡單的抽象和封裝,還沒有我們現在常用的文檔/視結構特性。但它引入了CObject,通過CArchive的持續化和其他一些MFC中仍然使用的特性,從而奠定了MFC的基礎。
MFC2.0在MFC1.0基礎上增加了文檔/視結構框架、OLE1.0類、消息映射和公用對話框類,廢棄了1.0版中的CModalDialog類並將它的功能移入到CDialog中,並增加了工具條、對話條、分割視窗的支持。MFC2.1隨同Visual C++ 1.1 for NT發佈,它把MFC2.0移植到了Win32上。MFC2.5隨同Visual C++1.5一起發佈,它引入了OLE 2.0和ODBC類。它是最後的官方的16位發行版,於93年12月發佈。目前,在開發16位Windows程序時,Visual C++1.5和MFC 2.5仍然有大量的用戶。隨後的MFC2.51、2.52糾正了MFC.25中的一些錯誤,增加了標籤式對話框、WinSock和MAPI(Microsoft 電子郵件應用程序接口)支持。MFC3.1同Visual C++2.1一起於1995年1月份發佈,它引入了Windows95公共控件(包括動畫、熱鍵、圖象列表、工具條提示等等)。MFC4.0於1995年12月份同Visual C++4.0一起發佈。Microsoft直接從Visual C++2.0一下子跳過一個版本號,升級到了4.0,以保持MFC版本號和Visual C++版本號的一致性,但這種一致性又在Visual C++5.0中打破了。在MFC4.0中增加了CSynchronize,CMutex,CEvent,CMultiLock,CShellNew以更好的支持多線程以及Windows 95的其他一些特性。Visual C++還引入了Component Gallery(組件畫廊)、STL支持和大量的新特性。MFC4.1最重要的特性是支持Win32s。許多MFC開發者一直都在使用該版本。MFC4.1修正了4.0的一些錯誤並增加了Internet特性。MFC4.2增加了ISAPI和OCX容器支持。
MFC4.21於1997年3月19日同Visual C++5.0一起發佈,它是目前最新和最完善的MFC版本。它只增加了對微軟的IntelliMouse(智能鼠標器)的支持。現在MFC版本號又不與Visual C++匹配了。
MFC發行版列表如下:
MFC Release MSVC Release 16位或32位 備註
1.0 16 簡單的 封裝Windows
2.0 1.0 16 增加了文檔/視結構
2.1 1.1 for NT 32 第一個NT的發行版
2.5 1.5 16 OLE/ODBC,最後一個
16位版本
2.51 2.0 16 修正錯誤
2.52 2.1 16 增加標籤式對話框
2.52b 2.2 16
2.5c 4.0 16
3.0 2.0 32 標籤式對話框、可停泊工具條
3.1 2.1 32 Winsock/MAPI, Windows公共控 制
3.2 2.2 32
4.0 4.0 32 Win 95, 線程類, OCX 容器
4.1 4.1 32 sweeper (WinInet) classes
**以上是最後支持Win32s的版本
4.2 4.2 32 修正錯誤, ISAPI classes
4.2b internet dl 32 修正錯誤
4.21 5.0 32 IntelliMouse™ support.
2.4.2 MFC類庫概念和組成
類庫是一個可以在應用中使用的相互關聯的C++類的集合。類庫有些隨編譯器提供,如Borland C++ Turbo Vision等;有的是由其他軟件公司銷售,如用於數據庫開發的CodeBase;有的則是由用戶自己開發的。比如圖象處理類庫完成圖象顯示、格式轉換、量化等;串行通信類庫用於支持串行口輸入輸出。有些情況下用戶可以直接利用類庫中包含的類定義應用程序所需的變量,有時則需要從類庫所提供的類中派生出新的類,這依賴於類庫的設計和具體的應用程序。
Microsoft提供了一個基礎類庫MFC,其中包含用來開發C++和C++ Windows應用程序的一組類。基礎類庫的核心是以C++形式封裝了大部分的Windows API。類庫表示窗口、對話框、設備上下文、公共GDI對象如畫筆、調色板、控制框和其他標準的Windows部件。這些類提供了一個面向Windows中結構的簡單的C++成員函數的接口。
MFC可分爲兩個主要部分:(1)基礎類(2)宏和全程函數。
MFC基礎類
MFC中的類按功能來分可劃分爲以下幾類:
基類
應用程序框架類
應用程序類
命令相關類
文檔/視類
線程類
可視對象類
窗口類
視類
對話框類
屬性表
控制類
菜單類
設備描述表
繪畫對象類
通用類
文件
診斷
異常
收集
模板收集
其他支持類
OLE2類
OLE基類
OLE可視編輯包裝程序類
OLE 可視編輯服務器程序類
OLE數據傳輸類
OLE對話框類
其他OLE類
數據庫類
宏和全局函數
若某個函數或變量不是某個類的一個成員,那麼它是一個全程函數或變量。
Microsoft基本宏和全程函數提供以下功能:
數據類型
運行時刻對象類型服務
診斷服務
異常處理
CString格式化及信息框顯示
消息映射
應用消息和管理
對象連接和嵌入(OLE)服務
標準命令和Windows IDs
約定:全程函數以“Afx”爲前綴,所有全程變量都是以“afx”爲前綴,宏不帶任何特別前綴,但是全部大寫。
常見的全局函數和宏有:AfxGetApp,AfxGetMainWnd,AfxMessageBox,DEBUG_NEW等,我們會在後面的章節中用到並對它們進行介紹。
從繼承關係來看,又可將MFC中的類分成兩大類:大多數的MFC類是從CObject繼承下來;另外一些類則不是從CObject類繼承下來,這些類包括:字符串類CString,日期時間類CTime,矩形類CRect,點CPoint等,它們提供程序輔助功能。
由於MFC中大部分類是從CObject繼承下來的,CObject類描述了幾乎所有的MFC中其他類的一些公共特性,因此我們有必要理解CObject類。
我們首先查看一下CObject類的定義,CObject類定義如下清單2.1所示:
清單2.1CObject類的定義
// class CObject is the root of all compliant objects
class CObject
{
public:
// Object model (types, destruction, allocation)
virtual CRuntimeClass* GetRuntimeClass() const;
virtual ~CObject(); // virtual destructors are necessary
// Diagnostic allocations
void* PASCAL operator new(size_t nSize);
void* PASCAL operator new(size_t, void* p);
void PASCAL operator delete(void* p);
#if defined(_DEBUG) && !defined(_AFX_NO_DEBUG_CRT)
// for file name/line number tracking using DEBUG_NEW
void* PASCAL operator new(size_t nSize, LPCSTR lpszFileName, int nLine);
#endif
// Disable the copy constructor and assignment by default so you will get
// compiler errors instead of unexpected behaviour if you pass objects
// by value or assign objects.
protected:
CObject();
private:
CObject(const CObject& objectSrc); // no implementation
void operator=(const CObject& objectSrc); // no implementation
// Attributes
public:
BOOL IsSerializable() const;
BOOL IsKindOf(const CRuntimeClass* pClass) const;
// Overridables
virtual void Serialize(CArchive& ar);
// Diagnostic Support
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const;
// Implementation
public:
static const AFX_DATA CRuntimeClass classCObject;
#ifdef _AFXDLL
static CRuntimeClass* PASCAL _GetBaseClass();
#endif
};
CObject類爲派生類提供了下述服務:
對象診斷
MFC提供了許多診斷特性,它可以:
輸出對象內部信息:CDumpContext類與CObject的成員函數Dump配合,用於在調試程序時輸出對象內部數據。
對象有效性檢查:重載基類的AssertValid成員函數,可以爲派生類的對象提供有效性檢查。
運行時訪問類的信息:
MFC提供了一個非常有用的特性,它可以進行運行時的類型檢查。如果從CObject派生出一個類,並使用了以下三個宏(IMPLEMENT_DYNAMIC,IMPLEMENT_ DYNCREATE或IMPLEMENT_SERIAL)之一,就可以:
運行時訪問類名
安全可靠的把通用的CObject指針轉化爲派生類的指針
比如,我們定義一個主窗口類
CMyFrame:public CFrameWnd
{
......
}
然後我們使用這個類:
CMyFrame *pFrame=(CMyFrame*)AfxGetMainWnd();
pFrame->DoSomeOperation();
AfxGetMainWnd是一個全局函數,返回指向應用程序的主窗口的指針,類型爲CWnd*,因此我們必須對它進行強制類型轉換,但我們如何知道是否轉換成功了呢?我們可以使用CObject的IsKindOf()成員函數檢查pFrame的類型,用法如下:
ASSERT(pFrame->IsKindOf(RUN_TIMECLASS(CMyFrame)));
將上一語句插入到pFrame-> DoSomeOperation()之前,就可以在運行時作類型檢查,當類型檢查失敗時,引發一個斷言(ASSERT),中斷程序執行。
對象持續性
通過與非CObject派生的檔案類CArchive相結合,提供將多個不同對象以二進制形式保存到磁盤文件(Serilization)中以及根據磁盤文件中的對象狀態數據在內存中重建對象(Deserilization )的功能。
然而,MFC不僅僅是一個類庫,它還提供了一層建立在Windows API的C++封裝上的附加應用程序框架。該框架提供了Windows程序需要的多數公共用戶界面。
所謂應用程序框架指的是爲了生成一般的應用所必須的各種軟組件的集成。應用框架是類庫的一種超集。一般的類庫只是一種可以用來嵌入任何程序中的、提供某些特定功能(如圖象處理、串行通信)的孤立的類的集合,但應用框架卻定義了應用程序的結構,它的類既相互獨立,又相互依賴,形成一個統一的整體,可以用來構造大多數應用程序。中國用戶熟悉的Borland C++的DOS下的Turbo Vision和Windows下OWL(Object Windows Language)都是應用框架的例子。
下面我們舉個具體的例子來說明MFC所提供的應用程序框架,程序如清單2.2。
清單2.2應用程序框架示例
#include<afxwin.h>
//derived an application class
class CMinMFCApp:public CWinApp
{
public:
BOOL InitInstance();
};
//Derive the main window class
class CMainWindow:public CFrameWnd
{
public:
CMainWindow();
DECLARE_MESSAGE_MAP()
};
BEGIN_MESSAGE_MAP(CMainWindow,CFrameWnd)
END_MESSAGE_MAP()
/*CMinMFCApp Member Functions*/
BOOL CMinMFCApp::InitInstance()
{
m_pMainWnd=new CMainWindow();
m_pMainWnd->ShowWindow(m_nCmdShow);
m_pMainWnd->UpdateWindow();
return TRUE;
}
/*CMainWindow member functions*/
CMainWindow::CMainWindow()//constructor
{
Create(NULL,
"Min MFC Application",
WS_OVERLAPPEDWINDOW,
rectDefault,
NULL,
NULL);
}
/*an instance of type CMinMFCApp*/
CMinMFCApp ThisApp;
清單2.2程序段定義了一個最小的MFC應用程序所需的框架程序。其中聲明瞭CMinMFCApp類,它是從應用程序類CWinApp中派生下來的;和窗口CMainWindow類,它是從框架窗口CFrameWnd類派生出來。我們還用CMinMFCApp定義了一個全局對象ThisApp。讀者也許會問,爲什麼沒有WinMain函數?因爲MFC已經把它封裝起來了。在程序運行時,MFC應用程序首先調用由框架提供的標準的WinMain函數。在WinMain函數中,首先初始化由CMinMFCApp定義的唯一的實例,然後調用CMinMFCApp繼承CWinApp的Run成員函數,進入消息循環。退出時調用CWinApp的ExitInstance函數。
由上面的說明可以看到,應用程序框架不僅提供了構建應用程序所需要的類(CWinApp,CFrameWnd等),還定義了程序的基本執行結構。所有的應用程序都在這個基本結構基礎上完成不同的功能。
MFC除了定義程序執行結構之外,還定義了三種基本的主窗口模型:單文檔窗口,多文檔窗口和對話框作爲主窗口。
Visual C++提供了兩個重要的工具,用於支持應用程序框架,它們就是前面提到AppWizard和ClassWizard。AppWizard用於在應用程序框架基礎上迅速生成用戶的應用程序基本結構。ClassWizard用於維護這種應用程序結構。
2.4.3 MFC的優點
Microsoft MFC具有以下不同於其它類庫的優勢:
完全支持Windows所有的函數、控件、消息、GDI基本圖形函數,菜單及對話框。類的設計以及同API函數的結合相當合理。
使用與傳統的Windows API同樣的命名規則,即匈牙利命名法。
進行消息處理時,不使用易產生錯誤的switch/case語句,所有消息映射到類的成員函數,這種直接消息到方法的映射對所有的消息都適用。它通過宏來實現消息到成員函數的映射,而且這些函數不必是虛擬的成員函數,這樣不需要爲消息映射函數生成一個很大的虛擬函數表(V表),節省內存。
通過發送有關對象信息到文件的能力提供更好的判定支持,也可確認成員變量。
支持異常錯誤的處理,減少了程序出錯的機會
運行時確定數據對象的類型。這允許實例化時動態操作各域
有較少的代碼和較快的速度。MFC庫只增加了少於40k的目標代碼,效率只比傳統的C Windows程序低5%。
可以利用與MFC緊密結合的AppWizard和ClassWizard等工具快速開發出功能強大的應用程序。
另外,在使用MFC時還允許混合使用傳統的函數調用。
我們着重講解一下MFC對消息的管理,這是編寫MFC消息處理程序的基礎。
2.4.4 MFC對消息的管理
Windows消息的管理包括消息發送和處理。爲了支持消息發送機制,MFC提供了三個函數:SendMessage、PostMessage和SendDlgItemMessage。而消息處理則相對來說顯得複雜一些。MFC採用了一種新的機制取代C語言編程時對Windows消息的Switch/Case分支,簡化了Windows編程,使程序可讀性、可維護性大大提高。
MFC對消息的處理
MFC不使用用C語言編寫Windows程序時用的易產生錯誤的switch/case語句,而採用一種消息映射機制來決定如何處理特定的消息。這種消息映射機制包括一組宏,用於標識消息處理函數、映射類成員函數和對應的消息等。其中,用afx_msg放在函數返回類型前面,用以標記它是一個消息處理成員函數。類若至少包含了一個消息處理函數,那麼還需要加上一個DECLARE_MESSAGE_MAP()宏,該宏對程序執行部分所定義的消息映射進行初始化。清單2.3演示了消息處理函數的例子:
清單2.3 消息處理函數例子
class CMainFrame:CFrameWnd{
public:
CMainFrame();
protected:
//{{AFX_MSG(CMainFrame)
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
afx_msg void OnEditCopy();
afx_msg void OnClose();
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
成員函數OnCreate,OnEditCopy,OnClose分別用來處理消息WM_CREATE、ID_EDIT_COPY和WM_CLOSE。其中,WM_CREATE和WM_CLOSE是系統預定義消息,包含在Windows.h中。而ID_EDIT_COPY是菜單Edit->Copy的標識,也就是用戶選擇Edit->Copy菜單項時產生的消息,一般在資源文件頭文件中定義。在類的實現部分給出這三個成員函數的定義,以及特殊的消息映射宏。上面的例子的消息映射宏定義如下:
BEGIN_MESSAGE_MAP(CMainFrame,CFrameWnd)
ON_WM_CREATE()
ON_COMMAND(ID_EDIT_COPY, OnEditCopy)
ON_WM_CLOSE()
END_MESSAGE_MAP()
消息映射宏由BEGIN_MESSAGE_MAP() 和END_MESSAGE_MAP()。其中,BEGIN_MESSAGE_MAP宏包含兩個參數CMainFrame類和CFrameWnd,分別代表當前定義的類和它的父類。在BEGIN_MESSAGE_MAP()和END_MESSAGE_MAP()之間,包含了主窗口要處理的各個Windows消息的入口。在本例中,包含三個消息。其中ON_ WM_CREATE被用來指定缺省的成員函數OnCreate與WM_CREATE相對應。在MFC中,包含了大量的預定義消息映射宏,用來指定各種成員函數與各種形如WM_XXXX消息相對應。如ON_WM_CLOSE宏指定了WM_CLOSE消息的處理成員函數爲OnClose。這時侯,只需要寫出要處理的消息就夠了,不必再寫出處理函數。消息映射宏ON_COMMAND則被用來將菜單項和用戶自定義的命令同它們的處理成員函數聯繫起來。在上例中,用戶選擇Edit->Copy菜單項時,系統執行OnEditCopy()函數。ON_COMMAND宏的一般定義形式如下:
ON_COMMAND(command,command_function)
其中,command爲菜單消息或用戶自定義消息,command_function爲消息處理函數。MFC允許用戶自定義消息,常量WM_USER和第一個消息值相對應,用戶必須爲自己的消息定義相對於WM_USER的偏移值,偏移範圍在0~0x3FFF之間,這對絕大多數程序來說都是夠用的。用戶可以利用#define語句直接定義自己的消息:
#define WM_USER1 (WM_USER+0)
#define WM_USER2 (WM_USER+1)
#define WM_USER3 (WM_USER+2)
下表列出了Windows95中Windows消息值的範圍。
常 量 | 值 | 消息值範圍 | 意 義 |
WM_USER | 0x0400 | 0x0000-0x03FF | Windows消息 |
0x0400-0x7FFF | 用戶自定義的消息 | ||
0x8000-0xBFFF | Windows保留值 | ||
0xC000-0xFFFF | 供應用使用的字符串消息 |
爲了說明如何使用用戶自定義消息,我們看一個例子,見程序清單2.4:
清單2.4 使用用戶自定義消息
#include<afxwin.h>
#define CM_APPLE (WM_USER+0)
#define CM_ORANGE (WM_USER+1)
class CMainFrame:CFrameWnd{
public:
CMainFrame();
protected:
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
afx_msg void OnClose();
//handle user select apple
afx_msg LRESULT CMApple(WPARAM wParam, LPARAM lParam);
//handle user select orange
afx_msg LRESULT CMOrange(WPARAM wParam, LPARAM lParam);
DECLARE_MESSAGE_MAP()
};
相應的消息映射如下:
BEGIN_MESSAGE_MAP(CMainFrame,CFrameWnd)
ON_WM_CREATE()
ON_MESSAGE(CM_APPLE, CMApple)
ON_MESSAGE(CM_ORANGE,CMOrange)
ON_WM_CLOSE()
END_MESSAGE_MAP()
第一個ON_MESSAGE宏用於指定 CM_APPLE 命令消息的處理成員函數爲CMApple ,而第二個ON_MESSAGE宏用於指定CM_ORANGE命令消息的處理函數爲CMOrange。
消息的發送
Windows應用程序允許應用程序向自己發送消息、向其他應用程序發送消息,甚至可以向Windows操作系統本身發送消息(比如要求關閉操作系統或重新啓動操作系統)。Windows提供了三個API函數用於發送消息,這三個函數是:SendMessage、PostMessage和SendDlgItemMessage。
SendMessage用於向窗口發送消息,該函數說明如下:
LRESULT SendMessage(
HWND hWnd, //消息要發往的窗口的句柄
UINT Msg, //要發送的消息
WPARAM wParam, //消息的第一個參數
LPARAM lParam //消息的第二個參數
);
其中,hWnd爲接收消息窗口的句柄,參數Msg指定發送的消息,參數wParam和lParam依賴於消息Msg。該函數調用目標窗口的窗口函數,直到目標窗口處理完該消息才返回。
PostMessage函數同SendMessage類似,它把消息放在指定窗口創建的線程的消息隊列中,然後不等消息處理完就返回,而不象SendMessage那樣必須等到消息處理完畢才返回。目標窗口通過GetMessage或PeekMessage從消息隊列中取出並處理。PostMessage函數說明如下:
BOOL PostMessage(
HWND hWnd, //消息發往的窗口
UINT Msg, //要發送的消息
WPARAM wParam, //消息的第一個參數
LPARAM lParam //消息的第二個參數
);
其中,參數hWnd爲接收消息的窗口的句柄,參數Msg指定所發送的消息,參數wParam和lParam依賴於消息Msg。
SendDlgItemMessage函數用於向對話框的某個控制發送消息,函數聲明如下:
LONG SendDlgItemMessage(
HWND hDlg, //對話框句柄
int nIDDlgItem, //對話框控件的ID
UINT Msg, //要發送的消息
WPARAM wParam, //消息的第一個參數
LPARAM lParam //消息的第二個參數
);
其中,hDlg爲包含目標控制的對話框的窗口句柄,參數nIDDlgItem爲接收消息的對話框控制的整數標識符,參數Msg指定了所發送的消息,參數wParam和lParam提供附加的特定消息的信息。
MFC將這三個函數封裝爲CWnd類的成員函數,隱藏了窗口句柄和對話框句柄。這三個成員函數用於向本窗口發送消息,函數的說明如下:
LRESULT SendMessage( UINT message, WPARAM wParam = 0, LPARAM lParam = 0 );BOOL PostMessage( UINT message, WPARAM wParam = 0, LPARAM lParam = 0 );LRESULT SendDlgItemMessage( int nID, UINT message, WPARAM wParam = 0, LPARAM lParam = 0 );
2.4.5學習MFC的方法
首先要對Windows API有一定的瞭解,否則無法深入學習MFC。至少要知道Windows對程序員來說意味着什麼,它能完成什麼工作,它的一些常用數據結構等。
另一點是不要過分依賴於Wizards。Wizards能做許多工作,但同時掩飾了太多的細節。應當看看AppWizard和ClassWizard爲你所做的工作。在mainfrm.cpp中運行調試器來觀察一下MFC運行的流程。除非你理解了生成的代碼的含義,否則無法瞭解程序是如何運行。
還有很重要的一點就是要學會抽象的把握問題,不求甚解。許多人一開始學習Visual C++就試圖瞭解整個MFC類庫,實際上那幾乎是不可能的。一般的學習方法是,先大體上對MFC有個瞭解,知道它的概念、組成、基本約定等。從最簡單的類入手,由淺入深,循序漸進、日積月累的學習。一開始使用MFC提供的類時,只需要知道它的一些常用的方法、外部接口,不必要去了解它的細節和內部實現,把它當做一個模塊或者說黑盒子來用,這就是一種抽象的學習方法。在學到一定程度時,再可以深入研究,採用繼承的方法對原有的類的行爲進行修改和擴充,派生出自己所需的類。
學習MFC,最重要的一點是理解和使用MFC類庫,而不是記憶。