MFC框架剖析和消息机制

来源:互联网 发布:网站后端性能优化 编辑:程序博客网 时间:2024/06/05 20:00

即便是基于MFC的应用程序,建立窗口类也是会遵循如下的过程:
设计窗口类->注册窗口类->生成窗口->显示窗口->更新窗口->消息循环->消息路由到窗口过程函数处理。下面就剖析一下在MFC中是如何完成上述过程的。

(1)每个应用程序都有且仅有一个应用类的全局变量theApp,全局变量先于WinMain函数进行处理。
(2)WinMain函数体在APPMODUL.CPP文件中,定义如下:
extern "C" int WINAPI
_tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPTSTR lpCmdLine, int nCmdShow)
{
// call shared/exported WinMain
return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
}
其中#define _tWinMain WinMain
(3)AfxWinMain函数体在WINMAIN.CPP文件中,里面有如下两句话:
CWinThread* pThread = AfxGetThread();
CWinApp* pApp = AfxGetApp();
其实这里得到的这两个指针都是指向全局的对象theApp的;
接下来有函数调用pThread->InitInstance(),根据多态性,会调用CXXXApp类中的InitInstance()函数。该函数很重要,在对该函数的调用中就会完成:设计窗口类->注册窗口类->生成窗口->显示窗口->更新窗口。
接下来,该函数中会继续调用pThread->Run(),这就完成了:消息循环->消息路由到窗口过程函数处理。
(4)进入CXXXApp::InitInstance()函数体中,对于单文档应用程序,调用ProcessShellCommand(cmdInfo),通过调用该函数就会完成:设计窗口类->注册窗口类->生成窗口。
再接下来就会调用m_pMainWnd->ShowWindow(SW_SHOW);m_pMainWnd->UpdateWindow();这就完成了:显示窗口->更新窗口。
(5)在函数CWinApp::ProcessShellCommand(CCommandLineInfo& rCmdInfo)中会进入到如下的case分支:case CCommandLineInfo::FileNew:
if (!AfxGetApp()->OnCmdMsg(ID_FILE_NEW, 0, NULL, NULL))
(6)进入函数CCmdTarget::OnCmdMsg(UINT nID, int nCode, void* pExtra,
AFX_CMDHANDLERINFO* pHandlerInfo),调用_AfxDispatchCmdMsg(this, nID, nCode,
lpEntry->pfn, pExtra, lpEntry->nSig, pHandlerInfo);
(7)进入函数AFXAPI _AfxDispatchCmdMsg(CCmdTarget* pTarget, UINT nID, int nCode,
AFX_PMSG pfn, void* pExtra, UINT nSig, AFX_CMDHANDLERINFO* pHandlerInfo),调用
case AfxSig_vv:
// normal command or control notification
ASSERT(CN_COMMAND == 0); // CN_COMMAND same as BN_CLICKED
ASSERT(pExtra == NULL);
(pTarget->*mmf.pfn_COMMAND)();
(8)进入CWinApp::OnFileNew(),调用m_pDocManager->OnFileNew();这个函数很特殊,它本身是个消息响应函数,当我们点击ID为ID_FILE_NEW的菜单时,会产生一个命令消息,由于命令消息可以被CCmdTarget类及其派生类来捕获,而CWinApp是从CCmdTarget派生出来的,因此可以捕获这个消息。当应用程序创建完成并成功显示后,当我们点击文件菜单下的新建菜单项时,就会首先进入这个函数,然后再依次执行下去,最后就会执行到pDocument->OnNewDocument()中,往往我们会对这个函数不解,不知道它为什么会响应ID_FILE_NEW的命令消息,至此真相大白了。顺便说一句,为什么程序在刚启动的时候,我们并没有点击菜单项,为什么会自动的产生这个消息呢?这是因为在CXXXXApp::InitInstance()函数中有“CCommandLineInfo cmdInfo;”这个类的构造函数是这样的:CCommandLineInfo::CCommandLineInfo()
{
m_bShowSplash = TRUE;
m_bRunEmbedded = FALSE;
m_bRunAutomated = FALSE;
m_nShellCommand = FileNew;
},因此就会在第(5)步骤的时候进入到“case CCommandLineInfo::FileNew:”这个分支中,就相当于产生了这样一个FileNew的消息。同理对于ID为ID_FILE_OPEN(在CWinApp::OnFileOpen()中响应)、ID_FILE_SAVE(在CDocument::OnFileSave()中响应)等等在MFC向导为我们生成的单文档类中找不到消息响应的入口时,其实都是在基类CWinApp或者CDocument类中进行了响应。对于CXXXXDoc::Serialize(CArchive& ar)函数也是通过ID_FILE_SAVE和ID_FILE_OPEN产生命令消息后就行响应从而才调用该函数的。
(9)进入CDocManager::OnFileNew(),CDocManager类有一个成员变量是CPtrList m_templateList;该变量保存了一个文档模版链表指针,在CDocManager::OnFileNew()函数体中会调用CDocTemplate* pTemplate = (CDocTemplate*)m_templateList.GetHead();得到链表中的头,也就是第一个文档模版,后面就会用得到的这个指针去调用pTemplate->OpenDocumentFile(NULL);紧接着就会有一个判断,用来确定该链表中是否只有一项,如果链表中保存了多个文档模版,则会弹出一个对话框,来让我们选择到底是使用哪一套文档模版来构建应用程序,相信大家也都见到过这种情况吧。对了,还有一点要说明的是:pTemplate是一个CDocTemplate的指针,但接下来程序为什么会进入到CSingleDocTemplate::OpenDocumentFile的函数体内呢,这是因为CDocTemplate类中的OpenDocumentFile函数被定义为纯虚函数,而CSingleDocTemplate类又是从CDocTemplate类派生出来的,并且实现了该函数,因此就会进入到子类的函数体中了。
(10)进入CDocument* CSingleDocTemplate::OpenDocumentFile(LPCTSTR lpszPathName,
BOOL bMakeVisible),先调用CreateNewDocument()创建文档类,再调用pFrame = CreateNewFrame(pDocument, NULL);创建框架类和视图类,从这里也可以看出MFC体系结构中文档、框架、视图“三位一体”的模式,在这一个函数中同时创建三个类;再会调用pDocument->OnNewDocument();因此就会进入到子类的文档类中的pDocument->OnNewDocument()中了。
(11)进入CFrameWnd* CDocTemplate::CreateNewFrame(CDocument* pDoc, CFrameWnd* pOther),调用if (!pFrame->LoadFrame(m_nIDResource,
WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE, // default frame styles
NULL, &context))
(12)进入BOOL CFrameWnd::LoadFrame(UINT nIDResource, DWORD dwDefaultStyle,
CWnd* pParentWnd, CCreateContext* pContext),调用VERIFY(AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG));
(13)进入BOOL AFXAPI AfxEndDeferRegisterClass(LONG fToRegister),该函数内部就完成了:设计窗口类->注册窗口类。MFC通过给我们提供好一些已经订制好的窗口类,我们不需要自己再设计窗口类,只需要到那些订制好的窗口类“仓库”中寻找一种适合我们需要的窗口类就可以了,然后通过AfxRegisterClass函数注册窗口类。还需要说明的是,再后续的跟踪过程中,我们会发现还会进入到AfxEndDeferRegisterClass函数中进行设计和注册窗口类,这主要是因为单文档应用程序比较特殊,它提前通过这样的一种途径进行了窗口类的设计和注册步骤,其实是应该在BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)函数的调用中完成窗口类的设计和注册的,这一点我们要清楚,也就是说设计和注册窗口类的正宗发源地应该是PreCreateWindow(CREATESTRUCT& cs)。此外,我们还会注意到在该函数体的前部分有一语句为“wndcls.lpfnWndProc = DefWindowProc;”因此所有窗口类的窗口过程函数都是DefWindowProc,这一点在后面的跟踪中可以看到,每次生成窗口之后都会调用几次DefWindowProc函数。也就是说MFC都是让我们采用默认的窗口过程函数,这并不是说我们因此就不能使用自己的窗口过程函数实现个性化的消息处理了,MFC采用了一种基于消息映射的机制完成了消息个性化处理。
(14)回到BOOL CFrameWnd::LoadFrame(UINT nIDResource, DWORD dwDefaultStyle,
CWnd* pParentWnd, CCreateContext* pContext)中,调用LPCTSTR lpszClass = GetIconWndClass(dwDefaultStyle, nIDResource);
(15)进入LPCTSTR CFrameWnd::GetIconWndClass(DWORD dwDefaultStyle, UINT nIDResource),调用PreCreateWindow(cs);
(16)进入BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs),调用CFrameWnd::PreCreateWindow(cs)
(17)进入BOOL CFrameWnd::PreCreateWindow(CREATESTRUCT& cs),调用VERIFY(AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG));又一次设计和注册窗口类
(18)回到BOOL CFrameWnd::LoadFrame(UINT nIDResource, DWORD dwDefaultStyle,
CWnd* pParentWnd, CCreateContext* pContext)中,调用if (!Create(lpszClass, lpszTitle, dwDefaultStyle, rectDefault, pParentWnd, MAKEINTRESOURCE(nIDResource), 0L, pContext))
(19)进入BOOL CFrameWnd::Create(LPCTSTR lpszClassName,
LPCTSTR lpszWindowName,
DWORD dwStyle,
const RECT& rect,
CWnd* pParentWnd,
LPCTSTR lpszMenuName,
DWORD dwExStyle,
CCreateContext* pContext),调用if (!CreateEx(dwExStyle, lpszClassName, lpszWindowName, dwStyle,
rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top,
pParentWnd->GetSafeHwnd(), hMenu, (LPVOID)pContext))
(20)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),调用if (!PreCreateWindow(cs))
,接下来调用HWND hWnd = ::CreateWindowEx(cs.dwExStyle, cs.lpszClass,
cs.lpszName, cs.style, cs.x, cs.y, cs.cx, cs.cy,
cs.hwndParent, cs.hMenu, cs.hInstance, cs.lpCreateParams);好了,终于让我们找到生成窗口的地方了——函数::CreateWindowEx!
(21)进入int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct),调用if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
(22)进入int CFrameWnd::OnCreate(LPCREATESTRUCT lpcs),调用return OnCreateHelper(lpcs, pContext);
(23)进入int CFrameWnd::OnCreateHelper(LPCREATESTRUCT lpcs, CCreateContext* pContext),调用if (CWnd::OnCreate(lpcs) == -1)
(24)进入_AFXWIN_INLINE int CWnd::OnCreate(LPCREATESTRUCT),调用return (int)Default();
(25)进入LRESULT CWnd::Default(),调用return DefWindowProc(pThreadState->m_lastSentMsg.message, pThreadState->m_lastSentMsg.wParam, pThreadState->m_lastSentMsg.lParam);
(26)进入LRESULT CWnd::DefWindowProc(UINT nMsg, WPARAM wParam, LPARAM lParam),调用return ::CallWindowProc(m_pfnSuper, m_hWnd, nMsg, wParam, lParam);
(27)回到int CFrameWnd::OnCreateHelper(LPCREATESTRUCT lpcs, CCreateContext* pContext),调用if (!OnCreateClient(lpcs, pContext))
(28)进入BOOL CFrameWnd::OnCreateClient(LPCREATESTRUCT, CCreateContext* pContext),调用if (CreateView(pContext, AFX_IDW_PANE_FIRST) == NULL)
(29)进入CWnd* CFrameWnd::CreateView(CCreateContext* pContext, UINT nID),调用if (!pView->Create(NULL, NULL, AFX_WS_DEFAULT_VIEW, CRect(0,0,0,0), this, nID, pContext))
(30)进入BOOL CWnd::Create(LPCTSTR lpszClassName,
LPCTSTR lpszWindowName, DWORD dwStyle,
const RECT& rect,
CWnd* pParentWnd, UINT nID,
CCreateContext* pContext),调用return CreateEx(0, lpszClassName, lpszWindowName,
dwStyle | WS_CHILD,
rect.left, rect.top,
rect.right - rect.left, rect.bottom - rect.top,
pParentWnd->GetSafeHwnd(), (HMENU)nID, (LPVOID)pContext);
(31)进入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),重复生成框架类CMainFrame的过程来生成CXXXView,因为它也是一个窗口类,因此也需要进行那一系列过程才能最终显示更新出来。
调用的顺序是这个样子的:PreCreateWindow(cs)->BOOL CXXXView::PreCreateWindow(CREATESTRUCT& cs)->CView::PreCreateWindow(cs)->VERIFY(AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG));->::CreateWindowEx(...)->CWnd::DefWindowProc->::CallWindowProc(...)->...->CXXXView::OnCreate->CView::OnCreate->CWnd::OnCreate->...
写到这里,基本上就清楚了,中间的省略号表示的部分大多数都是在与窗口过程函数有关的,因为在生成窗口的时候需要响应一些消息,因此需要调用一些窗口过程函数,每次在调用::CreateWindowEx(...)函数后都会调用一些窗口过程函数,然后再去调用该窗口类对应的OnCreate函数,其实在调用OnCreate函数之前调用CreateWindowEx只是生成了一个窗口,至于这个窗口里面要放置些什么东西,以及该如何装饰该窗口,则就需要由OnCreate来完成了,往往我们都会在OnCreate函数的后面(这样做是为了不影响窗口本身应该布置的格局)添加一些代码,创建我们自己的东西,比如我们通常会在CMainFrame类的OnCreate函数后面放置一些Create代码,来创建我们自己的可停靠的工具栏或者按钮之类的东西,当然我们也可以在CXXXView类的OnCreate函数的后面添加一些代码,来创建我们需要的东西,比如按钮之类的东西。在完成了从设计、注册到生成窗口的过程之后,往往还需要显示更新,有些时候,我们不必要每次都显示的调用CWnd的ShowWindow和UpdateWindow两个函数,我们可以在创建的时候,给窗口风格中添加WS_VISIBLE即可,因此有些时候会跟踪不到ShowWindow和UpdateWindow两个函数这两个函数,因为窗口在创建的时候就可见了。
总的来说,先初始化应用类,然后注册生成框架类,然后再注册生成视图类,然后注册生成视图类OnCreate函数后面用户添加的、用Create来准备创建的窗口,然后再注册生成框架类的OnCreate函数后面需要生成的m_wndToolBar、m_wndStatusBar以及我们自己添加的要创建的窗口类,最后在回到应用类的初始化的函数体中,调用框架类的显示和更新函数,然后再进入由框架类定义的窗口的消息循环中。

消息循环的过程是这个样子的:
(1)调用int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPTSTR lpCmdLine, int nCmdShow)函数中的pThread->Run()
(2)进入int CWinApp::Run(),调用return CWinThread::Run();
(3)进入int CWinThread::Run(),调用if (!PumpMessage())
(4)进入BOOL CWinThread::PumpMessage(),调用if (!::GetMessage(&m_msgCur, NULL, NULL, NULL))
(5)回到BOOL CWinThread::PumpMessage(),调用::TranslateMessage(&m_msgCur);::DispatchMessage(&m_msgCur);
(6)回到int CWinThread::Run(),调用while (::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE));
(7)再重复(4)-(6)的步骤
下面给出int CWinThread::Run()中消息循环的部分代码:
do
{
// pump message, but quit on WM_QUIT
if (!PumpMessage())
return ExitInstance();

// reset "no idle" state after pumping "normal" message
if (IsIdleMessage(&m_msgCur))
{
bIdle = TRUE;
lIdleCount = 0;
}

} while (::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE));
这段代码其实本质上与我们基于Win32 SDK手写的代码:
//消息循环
MSG msg;
while(GetMessage(&msg,NULL,0,0))
{
//简单的说,函数TranslateMessage就是把WM_KEYDOWN和WM_KEYUP翻译成WM_CHAR消息,没有该函数就不能产生WM_CHAR消息。
TranslateMessage(&msg);
::DispatchMessage(&msg);
}
是一致的。



1,寻找WinMain人口:
在安装目录下找到MFC文件夹下的SRC文件夹,SRC下是MFC源代码。
路径:MFC|SRC|APPMODUL.CPP:
_tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPTSTR lpCmdLine, int nCmdShow)
{
// call shared/exported WinMain
return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
}
注意:(#define _tWinMain WinMain)

2,对于全局对象或全局变量来说,在程序运行即WINMAIN函数加载的时候,已经为全局对象或全局变量分配了内存和赋初值。
所以:CTEApp theApp;->CTEApp ::CTEApp(){}->_tWinMain(){}
说明:每一个MFC程序,有且只有一个从WinApp类派生的类(应用程序类),也只有一个从应用程序类所事例化的对象,表示应用程序本身。在WIN32程序当中,表示应用程序是通过WINMAIN入口函数来表示的(通过一个应用程序的一个事例号这一个标识来表示的)。在基于MFC应用程序中,是通过产生一个应用程序对象,用它来唯一的表示了应用程序。

3,通过构造应用程序对象过程中调用基类CWinApp的构造函数,在CWinApp的构造函数中对程序包括运行时一些初始化工作完成了。
CWinApp构造函数:MFC|SRC|APPCORE.CPP
CWinApp::CWinApp(LPCTSTR lpszAppName){...}//带参数,而CTEApp构造函数没有显式向父类传参,难道CWinApp()有默认参数?见下:
(在CWinApp类定义中, CWinApp(LPCTSTR lpszAppName = NULL); )
注意:CWinApp()函数中:
pThreadState->m_pCurrentWinThread = this;
pModuleState->m_pCurrentWinApp = this
(this指向的是派生类CTEApp对象,即theApp)
调试:CWinApp::CWinApp();->CTEApp theApp;(->CTEApp ::CTEApp())->CWinApp::CWinApp()->CTEApp ::CTEApp()->_tWinMain(){}

4,_tWinMain函数中通过调用AfxWinMain()函数来完成它要完成的功能。(Afx*前缀代表这是应用程序框架函数,是一些全局函数,应用程序框架是一套辅助生成应用程序的框架模型,把一些类做一些有机的集成,我们可根据这些类函数来设计自己的应用程序)。
AfxWinMain()函数路径:MFC|SRC|WINMAIN.CPP:
在AfxWinMain()函数中:
CWinApp* pApp = AfxGetApp();
说明:pApp存储的是指向WinApp派生类对象(theApp)的指针。
//_AFXWIN_INLINE CWinApp* AFXAPI AfxGetApp()
// { return afxCurrentWinApp; }

调用pThread->InitInstance()
说明:pThread也指向theApp,由于基类中virtual BOOL InitApplication()定义为虚函数,所以调用pThread->InitInstance()时候,调用的是派生类CTEApp的InitInstance()函数。

nReturnCode = pThread->Run();
说明:pThread->Run()完成了消息循环。

5,注册窗口类:AfxEndDeferRegisterClass();
AfxEndDeferRegisterClass()函数所在文件:MFC|SRC|APPCORE.CPP
BOOL AFXAPI AfxEndDeferRegisterClass(LONG fToRegister){...}
说明:设计窗口类:在MFC中事先设计好了几种缺省的窗口类,根据不同的应用程序的选择,调用AfxEndDeferRegisterClass()函数注册所选择的窗口类。
调试:CWinApp::CWinApp();->CTEApp theApp;(->CTEApp ::CTEApp())->CWinApp::CWinApp()->CTEApp ::CTEApp()->_tWinMain(){}//进入程序
->AfxWinMain();->pApp->InitApplication();->pThread->InitInstance()//父类InitInstance虚函数;->CTEApp::InitInstance()//子类实现函数;->AfxEndDeferRegisterClass(LONG fToRegister)//注册所选择的窗口类(出于文档管理,注册提前,正常的应在PreCreateWindow中进行注册)//之后进入创建窗口阶段(以下再不做调试)

6,PreCreateWindow()://主要是注册窗口类
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
if( !CFrameWnd::PreCreateWindow(cs) )
return FALSE;
return TRUE;
}
说明:
CFrameWnd::PreCreateWindow()函数所在文件:MFC|SRC|WINFRM.CPP
BOOL CFrameWnd::PreCreateWindow(CREATESTRUCT& cs)
{
if (cs.lpszClass == NULL)
{
VERIFY(AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG));
//判断AFX_WNDFRAMEORVIEW_REG型号窗口类是否注册,如果没有注册则注册
cs.lpszClass = _afxWndFrameOrView; // COLOR_WINDOW background
//把注册后的窗口类名赋给cs.lpszClass
}

if ((cs.style & FWS_ADDTOTITLE) && afxData.bWin4)
cs.style |= FWS_PREFIXTITLE;

if (afxData.bWin4)
cs.dwExStyle |= WS_EX_CLIENTEDGE;

return TRUE;
}

其中:
virtual BOOL PreCreateWindow(CREATESTRUCT& cs);//PreCreateWindow()是个虚函数,如果子类有则调用子类的。
#define VERIFY(f) ASSERT(f)
#define AfxDeferRegisterClass(fClass) AfxEndDeferRegisterClass(fClass)
define AFX_WNDFRAMEORVIEW_REG 0x00008
const TCHAR _afxWndFrameOrView[] = AFX_WNDFRAMEORVIEW;//WINCORE.CPP文件中,定义为全局数组。
//#define AFX_WNDFRAMEORVIEW AFX_WNDCLASS("FrameOrView")

7,创建窗口:
Create()函数路径:MFC|SRC|WINFRM.CPP:
CFrameWnd::Create(...){
...
CreateEx(...);//从父类继承来的,调用CWnd::CreateEx().
...
}

CWnd::CreateEx()函数路径:MFC|SRC|WINCORE.CPP
BOOL CWnd::CreateEx(...){
...
if (!PreCreateWindow(cs))//虚函数,如果子类有调用子类的。
{
PostNcDestroy();
return FALSE;
}
...
HWND hWnd = ::CreateWindowEx(cs.dwExStyle, cs.lpszClass,
cs.lpszName, cs.style, cs.x, cs.y, cs.cx, cs.cy,
cs.hwndParent, cs.hMenu, cs.hInstance, cs.lpCreateParams);

...
}
说明:CreateWindowEx()函数与CREATESTRUCT结构体参数的对应关系,使我们在创建窗口之前通过可PreCreateWindow(cs)修改cs结构体成员来修改所要的窗口外观。PreCreateWindow(cs))//是虚函数,如果子类有调用子类的。
HWND CreateWindowEx(
DWORD dwExStyle,
LPCTSTR lpClassName,
LPCTSTR lpWindowName,
DWORD dwStyle,
int x,
int y,
int nWidth,
int nHeight,
HWND hWndParent,
HMENU hMenu,
HINSTANCE hInstance,
LPVOID lpParam
);
typedef struct tagCREATESTRUCT { // cs
LPVOID lpCreateParams;
HINSTANCE hInstance;
HMENU hMenu;
HWND hwndParent;
int cy;
int cx;
int y;
int x;
LONG style;
LPCTSTR lpszName;
LPCTSTR lpszClass;
DWORD dwExStyle;
} CREATESTRUCT;

8,显示和更新窗口:
CTEApp类,TEApp.cpp中
m_pMainWnd->ShowWindow(SW_SHOW);//显示窗口,m_pMainWnd指向框架窗口
m_pMainWnd->UpdateWindow();//更新窗口
说明:
class CTEApp : public CWinApp{...}
class CWinApp : public CWinThread{...}
class CWinThread : public CCmdTarget
{
...
public:
CWnd* m_pMainWnd;
...
...
}

9,消息循环:
int AFXAPI AfxWinMain()
{ ...
// Perform specific initializations
if (!pThread->InitInstance()){...}
//完成窗口初始化工作,完成窗口的注册,完成窗口的创建,显示和更新。
nReturnCode = pThread->Run();
//继承基类Run()方法,调用CWinThread::Run()来完成消息循环
...
}
////////////////////////////////////////////////////////////////
CWinThread::Run()方法路径:MFC|SRC|THRDCORE.CPP
int CWinThread::Run()
{ ...
// phase2: pump messages while available
do//消息循环
{
// pump message, but quit on WM_QUIT
if (!PumpMessage())//取消息并处理
return ExitInstance();
...
} while (::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE));
...
}
说明:
BOOL PeekMessage(,,,,)函数说明
The PeekMessage function checks a thread message queue for a message and places the message (if any) in the specified structure.
If a message is available, the return value is nonzero.
If no messages are available, the return value is zero.

/////////////////////////////////////////////////////////////
BOOL CWinThread::PumpMessage()
{
...
if (!::GetMessage(&m_msgCur, NULL, NULL, NULL))//取消息
{...}
...
// process this message
if (m_msgCur.message != WM_KICKIDLE && !PreTranslateMessage(&m_msgCur))
{
::TranslateMessage(&m_msgCur);//进行消息(如键盘消息)转换
::DispatchMessage(&m_msgCur);//分派消息到窗口的回调函数处理(实际上分派的消息经过消息映射,交由消息响应函数进行处理。)
}
return TRUE;
}

9,文档与视结构:
可以认为View类窗口是CMainFram类窗口的子窗口。
DOCument类是文档类。
DOC-VIEW结构将数据本身与它的显示分离开。
文档类:数据的存储,加载
视类:数据的显示,修改

10,文档类,视类,框架类的有机结合:
在CTEApp类CTEApp::InitInstance()函数中通过文档模板将文档类,视类,框架类的有机组织一起。
...
CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CTEDoc),
RUNTIME_CLASS(CMainFrame), // main SDI frame window
RUNTIME_CLASS(CTEView));
AddDocTemplate(pDocTemplate);//增加到模板 
                                         详解 消息机制

MFC消息机制涉及许多知识,比如消息分类,消息映射等。
知识先了解一下,马上动手实践才是硬道理。
我建了个SDI项目,把常用的消息试验了一遍。
如果像我一样初学的,可以留下邮箱索取源码。
复制代码
// MainFrm.h
afx_msg void OnMenuMsg(); // 菜单命令消息
afx_msg void OnMenuItem(UINT uId); // 范围消息,不限菜单
afx_msg void OnMenuItemUI(CCmdUI *pCmdUI); // 命令消息接口
afx_msg void OnToolMsg(); // 工具条命令消息
afx_msg LRESULT OnUserMsg(WPARAM wp,LPARAM lp);// 自定义消息
afx_msg void OnSendUserMsg();// 发送定义消息
复制代码
复制代码
// MainFrm.cpp
// 自定义消息宏,固定格式
#define WM_USER_MSG WM_USER + 1001

BEGIN_MESSAGE_MAP(CMainFrame, CFrameWndEx)
    ON_WM_CREATE() // 定义消息映射不加分号
    ON_COMMAND(ID_MENU_MSG, &CMainFrame::OnMenuMsg) // 菜单命令消息
    ON_COMMAND_RANGE(ID_0,ID_2, &CMainFrame::OnMenuItem) // 范围消息映射
    ON_UPDATE_COMMAND_UI_RANGE(ID_0,ID_2, &CMainFrame::OnMenuItemUI) // 命令用户接口
    
//ON_UPDATE_COMMAND_UI(ID_0, &CMainFrame::OnMenuItemUI)// 单个定义
    ON_COMMAND(ID_TOOL_MSG, &CMainFrame::OnToolMsg) // 工具条命令消息
    ON_MESSAGE(WM_USER_MSG, &CMainFrame::OnUserMsg) // 自定义消息映射
    ON_COMMAND(ID_USER_MSG, &CMainFrame::OnSendUserMsg) // 发送自定义消息
    ON_COMMAND(ID_VIEW_CUSTOMIZE, &CMainFrame::OnViewCustomize)
    ON_REGISTERED_MESSAGE(AFX_WM_CREATETOOLBAR, &CMainFrame::OnToolbarCreateNew)
END_MESSAGE_MAP()

void CMainFrame::OnMenuMsg()
{
    AfxMessageBox(_T("menu msg"));
}
void CMainFrame::OnToolMsg()
{
    //AfxMessageBox(_T("tool msg"));
    CDlg dlg;
    dlg.DoModal();
}
void CMainFrame::OnMenuItem(UINT uId)
{
    // 可根据id生成不同的响应。
    
// 我曾经用map<int,void (*)()>
    
// m_map[uId]();
}
void CMainFrame::OnMenuItemUI(CCmdUI *pCmdUI)
{
    pCmdUI->SetCheck(1);        // 设置按下
    pCmdUI->Enable(FALSE);        // 设置禁用
    int nMenuId = pCmdUI->m_nID;//菜单项ID
}
LRESULT CMainFrame::OnUserMsg(WPARAM wp,LPARAM lp)
{
    UINT uWp = wp;
    AfxMessageBox(_T("user msg"));
    return 1L;
}
void CMainFrame::OnSendUserMsg()
{
    SendMessage(WM_USER_MSG,123,456);
}
复制代码
// Dlg.h 消息响应函数定义
DECLARE_MESSAGE_MAP()
afx_msg void OnBtnMsg();        // 按钮命令消息        
afx_msg void OnEditChangeMsg();    // 编辑框改变消息
afx_msg void OnContextMenu(CWnd *pWnd,CPoint point); // 上下文菜单消息
复制代码
// Dlg.cpp
void CDlg::OnBtnMsg()
{
    AfxMessageBox(_T("btn msg"));
}
void CDlg::OnEditChangeMsg()
{
    AfxMessageBox(_T("edit change msg"));
}
void CDlg::OnContextMenu(CWnd *pWnd,CPoint point)
{
    AfxMessageBox(_T("context menu msg"));
}
// 定义消息映射
BEGIN_MESSAGE_MAP(CDlg, CDialog)
    ON_COMMAND(IDC_BTN,CDlg::OnBtnMsg)    // 命令消息映射
    ON_EN_CHANGE(IDC_EDIT_MSG, &CDlg::OnEditChangeMsg) // 控件通知消息映射
    ON_WM_CONTEXTMENU()    // 上下文菜单消息映射
END_MESSAGE_MAP()
复制代码
url:http://greatverve.cnblogs.com/archive/2012/11/04/mfc-message.html 
参考一:
  众所周知,windows是基于消息驱动的,作好消息处理是WINDOWS编程的关键任务之一,用VC制作WINDOWS程式同样离不开消息的处理。这就要求我们对 VC中消息的处理有一个比较清淅的认识。只有这样才可能在必要的时候亲自动手完成一些复杂的消息映射处理。
  在MFC中消息是通过一种的消息映射机制来处理的。其实质是一张消息及其处理函数的一一对应表以及分析处理这张表的应用框架内部的一些程序代码.这样的好处是可以避免像早期的SDK编程一样需要罗列一大堆的CASE语句来处理各种消息。由于不同种类的消息其处理方法是不同的,所以我们有必要先弄清楚 WINDOWS消息的种类。 

  WINDOWS 消息的种类:
  1、标准WINDOWS消息:这类消息是以WM_为前缀,不过WM_COMMAND例外。 例如: WM_MOVE、WM_QUIT等。
  2、命令消息:命令消息以WM_COMMAND为消息名。在消息中含有命令的标志符ID,以区分具体的命令。由菜单,工具栏等命令接口对象产生。
  3、控件通知消息:控件通知消息也是以WM_COMMAND为消息名。由编辑框、列表框和子窗口发送给父窗口的通知消息。在消息中包含控件通知码,以区分具体控件的通知消息。
  其中从CWnd派生的类可以接受上面的三种消息,从CCmdTarget派生的类能够接收命令消息。在使用MFCAppWizard创建的应用程序中,消息传递的机制是:

      App 类与Doc类都是从CCmdTarget类派生而来,所以只能接收命令消息(如单击菜单产生的消息),View类与Frame框架类都是从CWnd类派生而来,所以能够接收上面的三种消息。对于以上三种消息,期响应的顺序是这样的:首先由框架类接收到该消息,框架类将该消息递交给其子窗口View,如果View没有对该消息进行响应,则由View递交给Doc,如果Doc也没有对该消息进行响应,那么它回再次将该消息回交给View,View再回交给Frame框架,框架检查自己是否对该消息有相应的处理函数,如果没有则递交给App,如果App也没有则由Windows系统自己处理。

      命令消息:
在以上三种消息中,标准的WINDOWS消息映射是相当简单的。可直接通过类向导完成不同消息的映射处理,所以不在本文讨论之列。
凡是从CcmdTarget类派生的类都可以有消息映射,消息映射包括如下两方面的内容:
1、在类的定义文件中(.H)中加上一条宏调用(通常这条语句中类定义的最后):

DECLARE_MESSAGE_MAP()

  2、在类的实现文件(.CPP)中加上消息映射表:

1BEGIN_MESSAGE_MAP(类名,父类名)
2………..
3消息映射入口项.
4………. 
5END_MESSAGE_MAP( )
6

  幸运的是除了某些类(如没有基类的类或直接从CobjectO类派生的类)外,其它许多类均可由类向导生成.尽管生成的类只是一个框架,需要我们补充内容。但消息映射表已经为我们加好了,只是入口项有待我们加入。命令消息映射入口项是一个ON_COMMAND的宏。比如文件菜单下的"打开…"菜单(ID值为ID_FILE_OPEN)对应的消息映射入口项为:
ON_COMMAND(ID_FILE_NEW,OnFileOpen)
3、加入消息映射入口项之后需要完成消息处理函数。在类中消息处理函数都是类的成员函数,要响应一个消息,就必须定义一个该消息的处理函数。定义一个消息处理函数包括以下三方面的内容:
    3.1 在类定义中加入消息处理函数的函数原型(函数声明)
    3.2 在类的消息映射表中加入相应的消息映射入口项
    3.3 在类的实现中加入消息处理函数的函数体
需要说明的是消息处理函数的原型一定要以afx_msg打头,作为约定,消息处理函数名一般以On打头。比如:
    afx_msg OnFileOpen();// 函数原型

      控件通知消息:

      控件通知消息相对而言就复杂一点了,限于篇幅不能一一涉及,这里我们仅讨论 WM_NOTIFY消息的处理。
  WM_NOTFY产生的原因如下:
  在WINDOWS3.X中控件通知它们父窗口,如鼠标点击,控件背景绘制事件,通过发送一个消息到父窗口。简单的通知仅发送一个WM_COMMAND消息,包含一个通知码(比如BN_CLICKED)和一个在wParam中的控件ID及一个在lPraram中的控件句柄。因为wParam 和lParam均被使用,就没有方法传送其它的附加信息了。比如在BN_CLICKED 通知消息中,就没有办法发送关于当鼠标点击时光标的位置信息,在这种情况下就只能使用一些特殊的消息,包括:WM_CTLCOLOR、WM_VSCROLL和 WM_HSCROLL等等。值得一提的是这些消息能被反射回发送它们的控件,就是所谓的消息反射。
  在WIN32中同样可以使用那些在WINDOWS3.1中使用的通知消息,不过不像过去通过增加特殊目的的消息来为新的通知发送附加的数据,而是使用一个叫 WM_NOTIFY的消息,它能以一个标准的风格传送大量的附加数据。
      WM_NOTIFY消息包含一个存在wParam中的发送消息控件的ID和一个存在lParam中的指向一个结构体的指针。这个结构可能是NMHDR结构体,也可能是第一个成员是NMHDR的更大的结构,因为NMHDR是第一个成员,所以指向这个结构的指针也可以指向NMHDR。在许多情况下,这个指针将指向一个更大的结构,当你使用时必需转换它,只有很少的通知消息,比如通用通知消息(它的名字以NM_打头)。工具提示控件的 TTN_SHOW和TTN_POP实际上在使用NMHDR结构.
  NMHDR结构包含了发送消息控件的句柄,ID及通知码(如TTN_SHOW),其格式如下:
1Typedef sturct tagNMHDR{
2    HWND hwndFrom;
3    UINT idFrom;
4    UINT code;
5   }
 NMHDR;
6
  对TTN_SHOW消息而言,code成员的值将设为TTN_SHOW。
  类向导可以创建ON_NOTIFY消息映射入口并为你提供一个处理函数的框架,来处理 WM_NOTIFY类型的消息。ON_NOTIFY消息映射宏有如下语法:   
ON_NOTIFY(wNotifyCode,id,memberFxn)
  wNotifyCode:要处理的通知消息通知码,比如:LVN_KEYDOWN;Id:控件标识ID;MemberFxn:处理此消息的成员函数。此成员函数必需有如下的原形申明:
afx_msg void memberFxn( NMHDR * pNotifyStruct, LRESULT * result); 
  比如:假设你想成员函数OnKeydownList1处理ClistCtrl(标识ID=IDC_LIST1)的 LVN_KEYDOWN消息,你可以使用类向导添加如下的消息映射:   
ON_NOTIFY( LVN_KEYDOWN, IDC_LIST1, OnKeydownList1 )
  在上面的例子中,类向导提供如下函数:

1void CMessageReflectionDlg::OnKeydownList1(NMHDR* pNMHDR, LRESULT* pResult)
2{
3     LV_KEYDOWN* pLVKeyDow = (LV_KEYDOWN*)pNMHDR;
4     // TODO: Add your control notification handler
5     // code here
6     *pResult = 0;
7}
  这时类向导提供了一个适当类型的指针.你既可以通过pNMHDR,也可以通过 pLVKeyDow来访问这个通知结构。
  如前所述,有时我们可能需要为一组控件处理相同的WM_NOTIFY消息,这时需要使用ON_NOTIFY_RANGE而不是ON_NOTIFY。当你使用 ON_NOTIFY_RANGE时,你需要指定控件的ID范围.其消息映射入口及函数原型如下:
ON_NOTIFY_RANGE( wNotifyCode, id, idLast, memberFxn )
      wNotifyCode:消息通知码,比如:LVN_KEYDOWN;id: 第一控件的标识ID;idLast:最后一个控件的标识ID(标识值一定要连续);memberFxn: 消息处理函数。成员函数必须有如下原型申明:
afx_msg void memberFxn( UINT id, NMHDR * pNotifyStruct, LRESULT * result );

  其中id表示发送通知消息的控件标识ID。

参考二: 

---- MFC是Windows下程序设计的最流行的一个类库,但是该类库比较庞杂,尤其是它的消息映射机制,更是涉及到很多低层的东西,我们在这里,对它的整个消息映射机制进行了系统的分析,可以帮助程序开发人员对MFC的消息映射机制有一个比较透彻的了解。

1.引言

---- VC++的MFC类库实际上是Windows下C++编程的一套最为流行的类库。MFC的框架结构大大方便了程序员的编程工作,但是为了更加有效、灵活的使用MFC编程,了解MFC的体系结构往往可以使编程工作事半功倍。它合理的封装了WIN32 API函数,并设计了一套方便的消息映射机制。但这套机制本身比较庞大和复杂,对它的分析和了解无疑有助于我们写出更为合理的高效的程序。这里我们简单的分析MFC的消息响应机制,以了解MFC是如何对Windows的消息加以封装,方便用户的开发。

2. SDK下的消息机制实现
---- 这里简单的回顾一下SDK下我们是如何进行Windows的程序开发的。一般来说,Windows的消息都是和线程相对应的。即Windows会把消息发送给和该消息相对应的线程。在SDK的模式下,程序是通过GetMessage函数从和某个线程相对应的消息队列里面把消息取出来并放到一个特殊的结构里面,一个消息的结构是一个如下的STRUCTURE。 
typedef struct tagMSG {
HWND hwnd;
UINT message; 
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt; 
}MSG;
---- 其中hwnd表示和窗口过程相关的窗口的句柄,message表示消息的ID号,wParam和lParam表示和消息相关的参数,time表示消息发送的时间,pt表示消息发送时的鼠标的位置。

---- 然后TranslateMessage函数用来把虚键消息翻译成字符消息并放到响应的消息队列里面,最后DispatchMessage函数把消息分发到相关的窗口过程。然后窗口过程根据消息的类型对不同的消息进行相关的处理。在SDK编程过程中,用户需要在窗口过程中分析消息的类型和跟消息一起的参数的含义,做不同的处理,相对比较麻烦,而MFC把消息调用的过程给封装起来,使用户能够通过ClassWizard方便的使用和处理Windows的各种消息。

3.MFC的消息实现机制
---- 我们可以看到,在MFC的框架结构下,可以进行消息处理的类的头文件里面都会含有DECLARE_MESSAGE_MAP()宏,这里主要进行消息映射和消息处理函数的声明。可以进行消息处理的类的实现文件里一般都含有如下的结构。 
BEGIN_MESSAGE_MAP(CInheritClass, CBaseClass)
//{{AFX_MSG_MAP(CInheritClass)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()


---- 这里主要进行消息映射的实现和消息处理函数的实现。 
---- 所有能够进行消息处理的类都是基于CCmdTarget类的,也就是说CCmdTarget类是所有可以进行消息处理类的父类。CCmdTarget类是MFC处理命令消息的基础和核心。

---- 同时MFC定义了下面的两个主要结构:

AFX_MSGMAP_ENTRY
struct AFX_MSGMAP_ENTRY
{
UINT nMessage; // windows message
UINT nCode; // control code or WM_NOTIFY code
UINT nID; 
// control ID (or 0 for windows messages)
UINT nLastID; 
// used for entries specifying a range of control id's
UINT nSig; 
// signature type (action) or pointer to message #
AFX_PMSG pfn; // routine to call (or special value)
};
和AFX_MSGMAP
struct AFX_MSGMAP
{
#ifdef _AFXDLL
const AFX_MSGMAP* (PASCAL* pfnGetBaseMap)();
#else
const AFX_MSGMAP* pBaseMap;
#endif
const AFX_MSGMAP_ENTRY* lpEntries;
};

其中AFX_MSGMAP_ENTRY结构包含了
一个消息的所有相关信息,其中

nMessage为Windows消息的ID号
nCode为控制消息的通知码
nID为Windows控制消息的ID
nLastID表示如果是一个指定范围的消息被映射的话,
nLastID用来表示它的范围。
nSig表示消息的动作标识
AFX_PMSG pfn 它实际上是一个指向和该消息相应的执行函数的指针。

---- 而AFX_MSGMAP主要作用是两个,一:用来得到基类的消息映射入口地址。二:得到本身的消息映射入口地址。

---- 实际上,MFC把所有的消息一条条填入到AFX_MSGMAP_ENTRY结构中去,形成一个数组,该数组存放了所有的消息和与它们相关的参数。同时通过AFX_MSGMAP能得到该数组的首地址,同时得到基类的消息映射入口地址,这是为了当本身对该消息不响应的时候,就调用其基类的消息响应。

---- 现在我们来分析MFC是如何让窗口过程来处理消息的,实际上所有MFC的窗口类都通过钩子函数_AfxCbtFilterHook截获消息,并且在钩子函数_AfxCbtFilterHook中把窗口过程设定为AfxWndProc。原来的窗口过程保存在成员变量m_pfnSuper中。

---- 所以在MFC框架下,一般一个消息的处理过程是这样的。

函数AfxWndProc接收Windows操作系统发送的消息。

函数AfxWndProc调用函数AfxCallWndProc进行消息处理,这里一个进步是把对句柄的操作转换成对CWnd对象的操作。

函数AfxCallWndProc调用CWnd类的方法WindowProc进行消息处理。注意AfxWndProc和AfxCallWndProc都是AFX的API函数。而WindowProc已经是CWnd的一个方法。所以可以注意到在WindowProc中已经没有关于句柄或者是CWnd的参数了。

方法WindowProc调用方法OnWndMsg进行正式的消息处理,即把消息派送到相关的方法中去处理。消息是如何派送的呢?实际上在CWnd类中都保存了一个AFX_MSGMAP的结构,而在AFX_MSGMAP结构中保存有所有我们用ClassWizard生成的消息的数组的入口,我们把传给OnWndMsg的message和数组中的所有的message进行比较,找到匹配的那一个消息。实际上系统是通过函数AfxFindMessageEntry来实现的。找到了那个message,实际上我们就得到一个AFX_MSGMAP_ENTRY结构,而我们在上面已经提到AFX_MSGMAP_ENTRY保存了和该消息相关的所有信息,其中主要的是消息的动作标识和跟消息相关的执行函数。然后我们就可以根据消息的动作标识调用相关的执行函数,而这个执行函数实际上就是通过ClassWizard在类实现中定义的一个方法。这样就把消息的处理转化到类中的一个方法的实现上。举一个简单的例子,比如在View中对WM_LButtonDown消息的处理就转化成对如下一个方法的操作。 
void CInheritView::OnLButtonDown
(UINT nFlags, CPoint point) 
{
// TODO: Add your message 
handler code here and/or call default 
CView::OnLButtonDown(nFlags, point);
}

注意这里CView::OnLButtonDown(nFlags, point)实际上就是调用CWnd的Default()方法。 而Default()方法所做的工作就是调用DefWindowProc对消息进行处理。这实际上是调用原来的窗口过程进行缺省的消息处理。

如果OnWndMsg方法没有对消息进行处理的话,就调用DefWindowProc对消息进行处理。这是实际上是调用原来的窗口过程进行缺省的消息处理。 
---- 所以如果正常的消息处理的话,MFC窗口类是完全脱离了原来的窗口过程,用自己的一套体系结构实现消息的映射和处理。即先调用MFC窗口类挂上去的窗口过程,再调用原先的窗口过程。并且用户面对和消息相关的参数不再是死板的wParam和lParam,而是和消息类型具体相关的参数。比如和消息WM_LbuttonDown相对应的方法OnLButtonDown的两个参数是nFlags和point。nFlags表示在按下鼠标左键的时候是否有其他虚键按下,point更简单,就是表示鼠标的位置。 
---- 同时MFC窗口类消息传递中还提供了两个函数,分别为WalkPreTranslateTree和PreTranslateMessage。我们知道利用MFC框架生成的程序,都是从CWinApp开始执行的,而CWinapp实际继承了CWinThread类。在CWinThread的运行过程中会调用窗口类中的WalkPreTranslateTree方法。而WalkPreTranslateTree方法实际上就是从当前窗口开始查找愿意进行消息翻译的类,直到找到窗口没有父类为止。在WalkPreTranslateTree方法中调用了PreTranslateMessage方法。实际上PreTranslateMessage最大的好处是我们在消息处理前可以在这个方法里面先做一些事情。举一个简单的例子,比如我们希望在一个CEdit对象里,把所有的输入的字母都以大写的形式出现。我们只需要在PreTranslateMessage方法中判断message是否为WM_CHAR,如果是的话,把wParam(表示键值)由小写字母的值该为大写字母的值就实现了这个功能。

---- 继续上面的例子,根据我们对MFC消息机制的分析,我们很容易得到除了上面的方法,我们至少还可以在另外两个地方进行操作。

---- 一:在消息的处理方法里面即OnChar中,当然最后我们不再调用CEdit::OnChar(nChar, nRepCnt, nFlags),而是直接调用DefWindowProc(WM_CHAR,nChar,MAKELPARAM (nRepCnt,nFlags))。因为从我们上面的分析可以知道CEdit::OnChar(nChar, nRepCnt, nFlags)实际上也就是对DefWindowProc方法的调用。

---- 二:我们可以直接重载DefWindowProc方法,对message类型等于WM_CHAR的,直接修改nChar的值即可。

4.小结
---- 通过对MFC类库的分析和了解,不仅能够使我们更好的使用MFC类库,同时,对于我们自己设计和实现框架和类,无疑也有相当大的帮助。

 

 

二.MFC的消息映射机制 

MFC的设计者们在设计MFC时,紧紧把握一个目标,那就是尽可能使得MFC的代码要小,速度尽可能快。为了这个目标,他们使用了许多技巧,其中很多技巧体现在宏的运用上,实现MFC的消息映射的机制就是其中之一。
  同MFC消息映射机制有关的宏有下面几个:
  DECLARE_MESSAGE_MAP()宏
  BEGIN_MESSAGE_MAP(theClass, baseClass)和END_MESSAGE_MAP()宏
  弄懂MFC消息映射机制的最好办法是将找出一个具体的实例,将这些宏展开,并找出相关的数据结构。
  DECLARE_MESSAGE_MAP()
   DECLARE_MESSAGE_MAP()宏的定义如下:
  #define DECLARE_MESSAGE_MAP() \
  private: \
  static const AFX_MSGMAP_ENTRY _messageEntries[]; \
  protected: \
  static AFX_DATA const AFX_MSGMAP messageMap; \
  virtual const AFX_MSGMAP* GetMessageMap() const; \
从上面的定义可以看出,DECLARE_MESSAGE_MAP()作下面三件事:
  定义一个长度不定的静态数组变量_messageEntries[];
  定义一个静态变量messageMap;
  定义一个虚拟函数GetMessageMap();
在DECLARE_MESSAGE_MAP()宏中,涉及到MFC中两个对外不公开的数据结构
AFX_MSGMAP_ENTRY和AFX_MSGMAP。为了弄清楚消息映射,有必要考察一下这两个数据结构的定义。
  AFX_MSGMAP_ENTRY的定义
  struct AFX_MSGMAP_ENTRY
  {
   UINT nMessage; // windows message
   UINT nCode; // control code or WM_NOTIFY code
   UINT nID; // control ID (or 0 for windows messages)
   UINT nLastID; // used for entries specifying a range of control id's
   UINT nSig; // signature type (action) or pointer to message #
   AFX_PMSG pfn; // routine to call (or special value)
  };
结构中各项的含义注释已经说明得很清楚了,这里不再多述,从上面的定义你是否看出,AFX_MSGMAP_ENTRY结构实际上定义了消息和处理此消息的动作之间的映射关系。因此静态数组变量_messageEntries[]实际上定义了一张表,表中的每一项指定了相应的对象所要处理的消息和处理此消息的函数的对应关系,因而这张表也称为消息映射表。再看看AFX_MSGMAP的定义。
  (2)AFX_MSGMAP的定义
  struct AFX_MSGMAP
  {
   const AFX_MSGMAP* pBaseMap;
   const AFX_MSGMAP_ENTRY* lpEntries;
   };
不难看出,AFX_MSGMAP定义了一单向链表,链表中每一项的值是一指向消息映射表的指针(实际上就是_messageEntries的值)。通过这个链表,使得在某个类中调用基类的的消息处理函数很容易,因此,“父类的消息处理函数是子类的缺省消息处理函数”就“顺理成章”了。在后面的“MFC窗口的消息处理”一节中会对此作详细的讲解。

由上述可见,在类的头文件中主要定义了两个数据结构:消息映射表和单向链表。(孙建东总结)
  BEGIN_MESSAGE_MAP()和END_MESSAGE_MAP()
  它们的定义如下:
  #define BEGIN_MESSAGE_MAP(theClass, baseClass) \
  const AFX_MSGMAP* theClass::GetMessageMap() const \
  { return &theClass::messageMap; } \
  AFX_COMDAT AFX_DATADEF const AFX_MSGMAP theClass::messageMap = \
  { &baseClass::messageMap, &theClass::_messageEntries[0] }; \
  AFX_COMDAT const AFX_MSGMAP_ENTRY theClass::_messageEntries[] = \
  { \
   #define END_MESSAGE_MAP() \
   {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } \
   }; \
对应BEGIN_MESSAGE_MAP()的定义可能不是一下子就看得明白,不过不要紧,举一例子就很清楚了。对于BEGIN_MESSAGE_MAP(CView, CWnd),VC预编译器将其展开成下面的形式:
  const AFX_MSGMAP* CView::GetMessageMap() const
  { 
   return &CView::messageMap; 
   }
  AFX_COMDAT AFX_DATADEF const AFX_MSGMAP CView::messageMap =
  { 
   &CWnd::messageMap, 
   &CView::_messageEntries[0] 
  };
  AFX_COMDAT const AFX_MSGMAP_ENTRY CView::_messageEntries[] =
  {
  至于END_MESSAGE_MAP()则不过定义了一个表示映射表结束的标志项,我想大家对于这种简单的技巧应该是很熟悉的,无需多述。

到此为止,我想大家也已经想到了象ON_COMMAND这样的宏的具体作用了,不错它们只不过定义了一种类型的消息映射项,看看ON_COMMAND的定义:
  #define ON_COMMAND(id, memberFxn) \
  { WM_COMMAND, CN_COMMAND, (WORD)id, (WORD)id, AfxSig_vv, (AFX_PMSG)&memberFxn },
   根据上面的定义,ON_COMMAND(ID_FILE_NEW, OnFileNew)将被VC预编译器展开
   如下:
  {WM_COMMAND, CN_COMMAND, (WORD)id, (WORD)id, AfxSig_vv, 
  (AFX_PMSG)&OnFileNew},

到此,MFC的消息映射机制已经清楚了,现在提出并解答两个问题以作为对这一节的小结。
  为什么不直接使用虚拟函数实现消息处理函数呢?这是一个GOOD QUESTION。前面已经说过,MFC的设计者们在设计MFC时有一个很明确的目标,就是使得“MFC的代码尽可能小,速度尽可能快”,如果采用虚拟函数,那么对于所有的窗口消息,都必须有一个与之对应的虚拟函数,因而对每一个从CWnd派生的类而言,都会有一张很大的虚拟函数表vtbl。但是在实际应用中,一般只对少数的消息进行处理,大部分都交给系统缺省处理,所以表中的大部分项都是无用项,这样做就浪费了很多内存资源,这同MFC设计者们的设计目标是相违背的。当然,MFC所使用的方法只是解决这类问题的方式之一,不排除还有其他的解决方式,但就我个人观点而言,这是一种最好的解决方式,体现了很高的技巧性,值得我们学习。

  至于这第二个问题,是由上面的问题引申出来的。如果在子类和父类中出现了相同的消息出来函数,VC编译器会怎么处理这个问题呢?VC不会将它们看作错误,而会象对待虚拟函数类似的方式去处理,但对于消息处理函数(带afx_msg前缀),则不会生成虚拟函数表vtbl。

MFC下一个消息的处理过程是一般是这样的。
1、_AfxCbtFilterHook截获消息(这是一个钩子函数)
2、_AfxCbtFilterHook把窗口过程设定为AfxWndProc。
3、函数AfxWndProc接收Windows操作系统发送的消息。
4、函数AfxWndProc调用函数AfxCallWndProc进行消息处理。
5、函数AfxCallWndProc调用CWnd类的方法WindowProc进行消息处理。

如何添加自己的消息?
我们已经了解了WINDOW的消息机制,如何加入我们自己的消息呢?好我们来看
一个标准的消息处理程序是这个样子的
在 CWnd 类中预定义了标准 Windows 消息 (WM_XXXX  WM是WINDOW MESSAGE的缩写) 的默认处理程序。类库基于消息名命名这些处理程序。例如,WM_PAINT 消息的处理程序在 CWnd 中被声明为:
afx_msg void OnPaint();
afx_msg 关键字通过使这些处理程序区别于其他 CWnd 成员函数来表明 C++ virtual 关键字的作用。但是请注意,这些函数实际上并不是虚拟的,而是通过消息映射实现的。我们在本文的一开始便说明了为什么要这样做。
所有能够进行消息处理的类都是基于CCmdTarget类的,也就是说CCmdTarget类是所有可以进行消息处理类的父类。CCmdTarget类是MFC处理命令消息的基础和核心。

若要重写基类中定义的处理程序,只需在派生类中定义一个具有相同原型的函数,并创建此处理程序的消息映射项。我们通过ClassWizard可以建立大多数窗口消息或自定义的消息,通过ClassWizard可以自动建立消息映射,和消息处理函数的框架,我们只需要把我们要做的事情填空,添加你要做的事情到处理函数。这个非常简单,就不细说了。但是也许我们需要添加一些ClassWizard不支持的窗口消息或自定义消息,那么就需要我们亲自动手建立消息映射和消息处理的框架,通常步骤如下:
第一步:定义消息。Microsoft推荐用户自定义消息至少是WM_USER+100,因为很多新控件也要使用WM_USER消息。
#define WM_MYMESSAGE (WM_USER + 100)

第二步:实现消息处理函数。该函数使用WPRAM和LPARAM参数并返回LPESULT。
LPESULT CMainFrame::OnMyMessage(WPARAM wParam, LPARAM lParam)
{
// TODO: 处理用户自定义消息,填空就是要填到这里。
return 0;
}
第三步:在类头文件的AFX_MSG块中说明消息处理函数:
// {{AFX_MSG(CMainFrame)
afx_msg LRESULT OnMyMessage(WPARAM wParam, LPARAM lParam);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
第四步:在用户类的消息块中,使用ON_MESSAGE宏指令将消息映射到消息处理函数中。
ON_MESSAGE( WM_MYMESSAGE, OnMyMessage )

控件通知消息:
控件通知消息
     在《深度解析VC中的消息(上)》中,我们提到了消息的分类有3种:窗口消息、命令消息和控件通知消息,我们这里要谈的是最后一种:控件通知消息。
     控件通知消息,是指这样一种消息,一个窗口内的子控件发生了一些事情,需要通知父窗口。通知消息只适用于标准的窗口控件如按钮、列表框、组合框、编辑框,以及Windows公共控件如树状视图、列表视图等。例如,单击或双击一个控件、在控件中选择部分文本、操作控件的滚动条都会产生通知消息。她类似于命令消息,当用户与控件窗口交互时,那么控件通知消息就会从控件窗口发送到它的主窗口。但是这种消息的存在并不是为了处理用户命令,而是为了让主窗口能够改变控件,例如加载、显示数据。例如按下一个按钮,他向父窗口发送的消息也可以看作是一个控件通知消息;单击鼠标所产生的消息可以由主窗口直接处理,然后交给控件窗口处理。
     控件通知消息主要由窗口类即直接或间接由CWND类派生类处理。

    控件通知格式
     控件通知经历了一个演变过程,因而SendMessage( )的变量Message、wParam和lParam有三种格式。
     第一控件通知格式
     第一控件通知格式只是窗口消息的子集。它的特征格式如下:WM_XXXX。它主要来自下面的3种消息类型:
     (1)表示一个控件窗口要么已经被创建或销毁,要么已经被鼠标单击的消息:WM_PARENTNOTIFY;
     (2)发送到父窗口,用来绘制自身窗口的消息,例如: WM_CTLCOLOR、WM_DRAWITEM、WM_MEASUREITEM、WM_DELETEITEM、WM_CHARTOITEM、WM_VKTOITEM、WM_COMMAND和WM_COMPAREITEM
     (3)有滚动调控件发送,通知父窗口滚动窗口的消息:WM_VSCROLL和WM_HSCROLL
     第二控件通知格式
     第二控件通知格式与命令消息共享,它的特征格式如下:WM_COMMAND。
     在WM_COMMAND中,lParam用来区分是命令消息还是控件通知消息:如果lParam为NULL,则这是个命令消息,否则lParam里面放的必然就是控件的句柄,是一个控件通知消息。对于wParam则是低位放的是控件ID,高位放的是相应的消息事件。
     第三控件通知格式
     这个才真正涉及到我们要讲的内容,同时他也是最为灵活的一种格式。它的特征格式如下:WM_NOTIFY。
     在WM_NOTIFY中,lParam中放的是一个称为NMHDR结构的指针。在wParam中放的则是控件的ID。

    NMHDR结构的由来
    NMHDR结构是很值得一提的,该结构包括有关制作该通知的控件的任何内容,而不受空间和类型的限制,他的来历也是很有意思的。
     在最初的windows3.x中,根本就不存在什么WM_NOTIFY,控件通知它们父窗口,如鼠标点击,控件背景绘制事件,通过发送一个消息到父窗口。简单的通知仅发送一个WM_COMMAND消息,包含一个通知码和一个在wParam中的控件ID及一个在lPraram中的控件句柄。这样一来,wParam和lParam就都被填充了,没有额外的空间来传递一些其它的消息,例如鼠标按下的位置和时间。
     为了克服这个困难,windows3.x就提出了一个比较低级的解决策略,那就是给一些消息添加一些附加消息,最为明显的就是控件自画用到的DRAWITEMSTRUCT。不知道大家对这个结构熟悉不,不过,如果你是老手,你应该非常清楚这个结构,这个结构包含了9个内容,几乎你需要控制的信息都给你提供了。为什么说它比较低级呢?因为不同的消息附加的内容不同,结果就是一盘散沙,非常混乱。
     在win32中,MS又提出了一个更好的解决方案:引进NMHDR结构。这个结构的引进就是消息统一起来,利用它可以传递复杂的信息。这个结构的布局如下:
    NMHDR
     {
         HWnd hWndFrom ; 相当于原WM_COMMAND传递方式的lParam
         UINT idFrom ;    相当于原WM_COMMAND传递方式的wParam(low-order)
         UINT code ;      相当于原WM_COMMAND传递方式的Notify Code(wParam"s high-order)
     };
     对于这个结构的应用于WM_NOTIFY信息结构,结果WM_NOTIFY就变成了:
     A、无附加信息。结构变得很简单,就是一个NMHDR结构。
     B、有附加信息。定义一个大的结构,它的第一个元素就是NMHDR结构,它的后面放置附加信息。
    
    WM_NOTIFY结构的好处
     除了上面我们所说的好处外,WN_NOTIFY还有自己的独特的好处:
     由于在大结构中,第一个成员为NMHDR,这样一来,我们就可以利用指向NMHDR的指针来传递结构地址,根据指针的特性,无论消息有没有附加信息,这个指针都适用,也能够很方便的进行强制转换。

    分析ON_NOTIFY
  类向导可以创建ON_NOTIFY消息映射入口并提供一个处理函数的框架,来处理 WM_NOTIFY类型的消息。ON_NOTIFY消息映射宏有如下语法.
ON_NOTIFY(wNotifyCode,id,memberFxn)
其中:wNotifyCode:要处理的通知消息通知码。比如上面我们提到的LVN_KEYDOWN;Id:控件标识ID;MemberFxn:处理此消息的成员函数。
此成员函数有如下的原型声明:
afx_msg void memberFxn( NMHDR * pNotifyStruct, LRESULT * result); 
比如:假设你想成员函数OnKeydownList1处理ClistCtrl(标识ID=IDC_LIST1)的 LVN_KEYDOWN消息,你可以使用类向导添加如下的消息映射:
ON_NOTIFY( LVN_KEYDOWN, IDC_LIST1, OnKeydownList1 )
在上面的例子中,类向导提供如下函数:
void CMessageReflectionDlg::OnKeydownList1(NMHDR* pNMHDR, LRESULT* pResult)
{
LV_KEYDOWN* pLVKey= (LV_KEYDOWN*)pNMHDR;
*pResult = 0;
}
这时类向导提供了一个适当类型的指针,你既可以通过pNMHDR,也可以通过 pLVKey来访问这个通知结构。

    ON_NOTIFY_RANGE
有时我们可能需要为一组控件处理相同的WM_NOTIFY消息。这时需要使用ON_NOTIFY_RANGE而不是ON_NOTIFY。不过,很不幸的是,VC6的ClassWizard并不支持这个消息,所以我们必须手工添加。方法和一般的手工添加的消息一样,不过需要注意的是:
     (1)当你使用 ON_NOTIFY_RANGE时,你需要指定控件的ID范围.其消息映射入口及函数原型如下:
   ON_NOTIFY_RANGE( wNotifyCode, id, idLast, memberFxn )
其中:wNotifyCode:消息通知码.比如:LVN_KEYDOWN。id: 第一控件的标识ID。
idLast:最后一个控件的标识ID。(标识值一定要连续)memberFxn: 消息处理函数。
(2)成员函数必须有如下原型申明:afx_msg void memberFxn( UINT id, NMHDR * pNotifyStruct, LRESULT * result );

    结束语:
     WM_NOTIFY是一个很值得思考的结构,另外他和Reflect Message联系非常紧密,我们接下来就要讨论一下反射消息。



1 0
原创粉丝点击