一 MFC的發展
VC 1.0->VC 5.0->VC 6.0->VC2008 SP1)->VS2010
二 MFC基礎
1 MFC 微軟基礎類庫
采用類的方式,將Win32 API等進行封裝,形成的庫.
2 MFC相關的頭文件
afx.h (application framework, X)
afxwin.h (類似於windows.h)
afxext.h (MFC擴展頭文件)
...
三 MFC應用程序
MFC應用程序主要分為以下三類,對比通常的應用程序,變化的地方如下:
1 MFC的控制台程序
1.1 包含afx系列的頭文件 |
1.2 CWinApp theApp - MFC的應用程序類,封裝了應用程序的啟動過程. |
1.3 AfxWinInit MFC初始化函數,將應用程序的信息初始化. |
2 MFC的動態庫和靜態庫
2.1 靜態庫
|
||||||
2.2 動態庫 動態庫分類如下:
動態庫與C++動態庫的不同點如下:
|
3 MFC應用程序
3.1 單文檔視圖應用程序
|
|||||
3.2 多文檔視圖應用程序
|
|||||
3.3 對話框應用程序
|
四 MFC中的類
在MSDN中搜索“Hierarchy chart”就可以看到整個MFC類繼承圖的關系
常用的類分類如下:
1 CObject類
MFC類的基礎,大部分MFC類都是它的子類。
CObject封裝了MFC的基礎的機制,比如:
1.1 new和Delete |
1.2 Assert |
1.3 運行式信息 |
1.4 動態創建 |
1.5 序列化 |
2 應用程序框架類
封裝了應用程序啟動相關信息,以及MFC消息映射機制。
3 窗口支持類
封裝了窗口操作的API,各種控件及窗口的框架.
4 繪圖類
提供了繪圖API的封裝,以及相關的GDI設備封裝.
5 MFC的集合類
提供了數組、鏈表、映射的數據結構的操作.
6 數據庫支持類
ODBC支持類和DAO的支持類。DAO的類已作廢.
7 同步類
臨界區/事件/互斥/信號量的封裝
8 Socket類
封裝了socket的編程.
9 常用數據結構
CString CRect CPoint等.
五第一個MFC程序
不使用向導,自己創建一個MFC應用程序。
1.新建一個Win32 Application項目“WinMFC” |
|||||
2.選擇一個簡單的Win32應用程序 |
|||||
3.修改環境為MFC應用程序的環境 |
|||||
4.增加應用程序類CWinApp
|
這樣,一個基本的MFC應用程序就創建好了。編譯運行,會看到如下結果:
這個MFC是怎么執行起來的呢??先別急
程序可以啟動了,下面開始創建窗口
1.在WinMFC.cpp文件中編寫繼承自CFrameWnd類的類CMyFrameWnd類 |
2.在App的InitInstance函數定義窗口對象 |
3.創建窗口(Create)並顯示(ShowWindow) |
4.將窗口設置成App的主窗口m_pMainWnd = pWnd; |
5.在CMyFrameWnd中添加窗口處理函數WindowProc,在WindowProc中處理消息 |
WinMFC.cpp完整代碼 |
// WinMFC.cpp : Defines the entry point for the application. //
#include "stdafx.h"
//框架窗口類 class CMyFrameWnd : public CFrameWnd { public: //窗口處理函數 virtual LRESULT WindowProc( UINT message, WPARAM wParam, LPARAM lParam ); };
//窗口處理函數 LRESULT CMyFrameWnd::WindowProc( UINT message, WPARAM wParam, LPARAM lParam ) { switch( message ) { case WM_CREATE: AfxMessageBox( "WM_CREATE" ); break; case WM_PAINT: { PAINTSTRUCT ps = { 0 }; HDC hDC = ::BeginPaint( m_hWnd, &ps );
CHAR szText[] = "Hello world!"; TextOut( hDC, 100, 100, szText, strlen( szText ) );
::EndPaint( m_hWnd, &ps ); } break; }
return CFrameWnd::WindowProc( message, wParam, lParam ); }
//應用程序類 class CMyApp : public CWinApp { public: virtual BOOL InitInstance( ); };
//定義CMyApp的全局變量 CMyApp theApp;
//初始化函數 BOOL CMyApp::InitInstance( ) { //定義窗口對象 CMyFrameWnd * pWnd = new CMyFrameWnd(); //創建窗口 pWnd->Create( NULL, "MyApp" ); //顯示窗口 pWnd->ShowWindow( SW_SHOW ); //設置主窗口 m_pMainWnd = pWnd;
return TRUE; } |
六 MFC應用程序的啟動
MFC應用程序與Win32程序一樣,都需要程序的入口函數.
1 CWinApp
應用程序類,封裝了應用程序的相關信息,可以提供初始化 消息循環等處理. CWinApp的構造函數中執行了什么:
|
2 程序的入口函數
在CMyApp::InitInstance()函數中加一個斷點,然后運行,程序在斷點處停下來。 這時候打開“Call Stack”窗口,觀察程序的調用過程。 PS:雙擊調用棧里面的函數,就會跳過去~~ |
||||||||||||
根據跟蹤調用過程,得到如下結論:
|
七窗口創建及窗口處理函數
1 窗口創建過程
1 窗口的參數初始化,包括窗口類,窗口風格,窗口處理函數等信息. 注意:將DefWindowProc注冊成窗口處理函數. |
2 設置 "創建HOOK" (鈎子) 當窗口創建的時候,調用這個HOOK函數. Wnd -> HOOK -> WndProc |
3 創建窗口 CreateWindowEx |
4 卸載 "創建HOOK" 將HOOK程序從當前程序中移除 |
2 HOOK(鈎子)程序
上面說的"創建HOOK"到底做了什么:
1 使用AfxGetAfxWndProc函數獲取了一個WNDPROC函數指針, 也就是AfxWndProc(AfxWndProcBase)函數地址. |
2 將這個WNDPROC函數設置成當前窗口的處理函數 |
3 將窗口句柄和窗口類的指針保存到MFC的映射數據中(afxMapHWND函數)。這樣就可以通過窗口句柄獲取對應的窗口對象指針了。 |
總得來說,就是在窗口創建時,將AfxWndProc函數設置為當前窗口的窗口過程函數。
3 AfxWndProc(Base)窗口處理函數
AfxGetAfxWndProc函數到底做了什么:
1 根據窗口句柄獲取了相對應的窗口的CWnd *類型的指針,從映射數據中根據窗口句柄查找CWnd *指針.(afxMapHWND函數) |
2 調用AfxCallWndProc函數 |
3 在AfxCallWndProc函數中,調用CWnd的WindowProc函數 注意: 在MFC程序當中,所有窗口處理都是使用一個函數(AfxWndProc函數). |
4 窗口的創建及處理過程
經過上面的分析,總結窗口的創建及處理過程如下:
1 將DefWindowProc函數注冊成當前窗口的處理函數 |
2 設置鈎子函數 |
3 創建窗口,並執行鈎子函數 |
4 在鈎子函數中將窗口類指針和窗口句柄的對應關系保存. |
5 在鈎子函數中將AfxWndProc(Base)函數設置當前窗口的窗口處理函數 |
6 在AfxWndProc(Base)收到窗口消息,從窗口對應關系中,查詢相應的窗口類指針. |
7 調用窗口類WindowProc函數處理消息 |
八消息映射
為了簡化Win32的API編程中復雜的switch...case...,MFC重新封裝了消息的傳遞方式。
下面,新建一個Win32應用程序,通過修改它實現MFC的消息映射,來觀察一下。(PS:其實創建過程跟上面的過程差不多,多寫幾遍,寫到吐為止)
1.修改stdafx.h頭文件,將#include <Windows.h> 改成#include <afxwin.h> |
|||
2. |
|||
3.刪掉MFCMsg.cpp文件中的WinMain()函數 |
|||
4.
|
|||
5.下面是跟前面不同的地方了。 在CMsgFrame中增加消息映射的宏
|
|||
6.增加一個OnPaint函數作為WM_PAINT消息的響應函數,並在BEGIN_MESSAGE_MAP和END_MESSAGE_MAP之間添加消息映射
|
|||
此時的運行結果: |
|||
7.同理,添加一個WM_CREATE消息:
|
經過以上的分析,總結消息映射如下:
1 消息映射添加步驟
1 在FrameWnd添加消息宏定義 DECLARE_MESSAGE_MAP |
2 添加消息宏實現 |
3 添加消息處理函數 |
4 添加消息和處理函數的對應 |
2 消息宏的實現
DECLARE_MESSAGE_MAP等到底是什么呢?將這些宏展開如下:
//消息映射宏DECLARE_MESSAGE_MAP展開 private: static const AFX_MSGMAP_ENTRY _messageEntries[]; protected: static AFX_DATA const AFX_MSGMAP messageMap; static const AFX_MSGMAP* PASCAL _GetBaseMessageMap(); virtual const AFX_MSGMAP* GetMessageMap() const; |
//BEGIN_MESSAGE_MAP( CMsgFrame, CFrameWnd )展開 const AFX_MSGMAP* PASCAL CMsgFrame::_GetBaseMessageMap() { return &CFrameWnd::messageMap; } const AFX_MSGMAP* CMsgFrame::GetMessageMap() const { return &CMsgFrame::messageMap; } AFX_COMDAT AFX_DATADEF const AFX_MSGMAP CMsgFrame::messageMap = { &CMsgFrame::_GetBaseMessageMap, &CMsgFrame::_messageEntries[0] }; AFX_COMDAT const AFX_MSGMAP_ENTRY CMsgFrame::_messageEntries[] = { |
//ON_MESSAGE( WM_PAINT, OnPaint )展開 { WM_PAINT, 0, 0, 0, AfxSig_lwl,(AFX_PMSG)(AFX_PMSGW)(LRESULT (AFX_MSG_CALL CWnd::*)(WPARAM, LPARAM))&OnPaint }, |
//END_MESSAGE_MAP()展開 {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } }; |
先不要暈,將展開的代碼多看幾遍,分析一下就清楚了。
其中有些數據類型需要解釋一下:
1 AFX_MSGMAP_ENTRY 是用於保存消息ID與對應函數指針,及相關的信息
|
|
2 AFX_MSGMAP 用於保存GetBaseMap的函數地址及AFX_MSGMAP_ENTRY數組的地址.
|
下面對上面的宏代碼做一些說明
1 _messageEntries[], 靜態成員,類型為 AFX_MSGMAP_ENTRY。 保存CMsgFrame中消息ID和對應的消息處理函數的數組。 |
2 messageMap, 靜態成員,類型為 AFX_MSGMAP。 保存了CMsgFrame中的_GetBaseMessageMap函數指針以及_messageEntries數組地址 |
3 _GetBaseMessageMap, 靜態成員 獲取父類的messageMap的地址 |
4 GetMessageMap,虛函數 獲取自己的messageMap地址 |
下面通過“->”來捋一下幾個成員直接的關系:
CMsgFrame::GetMessageMap -〉&messageMap{ &_messageEntries[0] -> { ID<->Func, ID<->Func, .... } _GetBaseMessageMap -> &parent::messageMap { &_messageEntries[0], _GetBaseMessageMap->&parent::messageMap } } |
3 消息映射過程
1 消息處理函數WindowProc收到消息后,調用OnWndMsg處理消息,OnWndMsg如果不處理消息,那么WindowProc將調用DefWindowProc默認處理消息並返回. |
|||||||
2 OnWndMsg處理消息
|
九MFC的消息分類
MFC的消息分類按照處理方式來分。主要分為4類:
1 窗口消息
例如WM_CREATE、WM_PAINT、鼠標、鍵盤等消息,這些消息的處理方式是直接調用消息處理函數.
這類消息使用的宏:
ON_MESSAGE( )
ON_WM_XXXXX( ): ON_WM_CREATE()
消息處理時,采用上面的處理方式.
前面的WM_CREATE消息就不用寫成ON_MESSAGE( WM_CREATE, OnCreate ),而直接寫成ON_WM_CREATE即可。
函數的定義和聲明也要相應地修改為:
afx_msg LRESULT OnCreate( WPARAM wParam, LPARAM lParam ); |
afx_msg int OnCreate(); |
LRESULT CMsgFrame::OnCreate( WPARAM wParam, LPARAM lParam ) { AfxMessageBox("OnCreate"); return 0; } |
int CMsgFrame::OnCreate() { AfxMessageBox("OnCreate"); return 0; } |
2 命令消息 WM_COMMAND
菜單、工具欄、按鈕等點擊時的命令. 消息首先發送到主窗口,由主窗口逐層向子窗口派發。
這類的消息使用的宏:
ON_COMMAND( )
ON_COMMAND_RANGE( )
消息處理時,在OnWndMsg中調用OnCommand處理函數進行消息處理.
3 通知消息 WM_NOTIFY
子窗口對父窗口的通知消息。
控件消息宏,例如: EDIT控件 ON_EN_CHANGE、ON_NOTIFY/ON_NOTIFY_RANGE消息處理時,在OnWndMsg中調用OnNotify(OnCommand)處理函數進行消息處理。
4 自注冊消息
用戶自注冊消息的處理。
用戶需調用RegisterWindowMessage函數注冊消息,然后在消息映射中使用.
UINT RegisterWindowMessage(
LPCTSTR lpString //消息名字符串
);
返回注冊成功的消息ID(0xC000-0xFFFF)
消息映射宏: ON_REGISTERED_MESSAGE
消息處理時, 與窗口消息處理類似,但是在查找消息處理函數和執行消息處理函數時不同.
自注冊消息使用舉例:
// RegisterMsg.cpp : Defines the entry point for the application. //
#include "stdafx.h"
//注冊消息ID UINT g_nRegMsg = RegisterWindowMessage( "MYREGMSG" );
class CRegsiterFrame : public CFrameWnd { DECLARE_MESSAGE_MAP( ) public: afx_msg int OnCreate( LPCREATESTRUCT lpCreateStruct ); afx_msg void OnTest( ); //消息處理函數 afx_msg LRESULT OnRegMsg( WPARAM wParam,LPARAM lParam ); };
BEGIN_MESSAGE_MAP( CRegsiterFrame, CFrameWnd ) ON_WM_CREATE( ) ON_COMMAND( 1001, OnTest ) //消息宏映射 ON_REGISTERED_MESSAGE( g_nRegMsg, OnRegMsg ) END_MESSAGE_MAP( )
int CRegsiterFrame::OnCreate( LPCREATESTRUCT lpCreateStruct ) { //父類的OnCreate處理 /*if(!CFrameWnd::OnCreate( lpCreateStruct ) ) { return 0; }*/
//創建按鈕 CreateWindow( "BUTTON", "Test", WS_CHILD|WS_VISIBLE, 50, 50, 200, 30, m_hWnd, (HMENU)1001, AfxGetApp()->m_hInstance, NULL );
return 1; }
void CRegsiterFrame::OnTest( ) { //發送消息 SendMessage( g_nRegMsg ); }
LRESULT CRegsiterFrame::OnRegMsg( WPARAM wParam,LPARAM lParam ) { AfxMessageBox( "OnRegMsg" ); return 0; }
class CRegisterApp : public CWinApp { public: virtual BOOL InitInstance( ); };
CRegisterApp theApp;
BOOL CRegisterApp::InitInstance( ) { CRegsiterFrame * pWnd = new CRegsiterFrame( ); pWnd->Create( NULL, "Register Msg" ); m_pMainWnd = pWnd; m_pMainWnd->ShowWindow( SW_SHOW ); m_pMainWnd->UpdateWindow( ); return TRUE; } |