窗口消息概述:
所有的窗口消息的基本形式都是一样的,有3个部分:(1).一个无符号整数,包含消息的实际内容.(2)WPARAM,一个4字节整数.(3)LPARAM,一个4字节参数.无符号数是实际消息.
含MFC在内的任何WINDOWS程序的核心都是消息泵.消息泵是一个循环,它取出消息并将消息送给恰当的窗口消息处理函数.下面是个消息泵的示例:
while(GetMessage(&msg,NULL,NULL,NULL)){
TranslateMessage(&msg) ;
DispatchMessage(&msg) ;
}
return msg.wParam ;
程序会在开始运行时调用RegisterClass()并使用一个WNDCLASS结构来注册至少一个窗口类.每当该类的一个窗口窗口中有消息时,WINDOWS都会调用WNDCLASS中的lpfnWndProc所指定的函数.
典型的SDK程序中消息处理函数是个庞大且难于维护的switch语句.C++中可用虚函数处理每个消息,但是,这种方法将带因虚表而产生过多的开销,且不易适应消息的增减.
----------------------------
CCmdTarget和消息映射表----MFC的消息处理结构的两个基本组成部分
----------------------------
消息映射表数据结构
struct AFX_MSGMAP_ENTRY {//消息映射表实际入口.
UINT nMessage;//实际消息
UINT nCode;//控件代码或通知代码
UINT nID;//控件ID
UINT nLastID;//控件最大ID
UINT nSig;//消息处理函数的签名
AFX_PMSG pfn;//消息处理函数
};
struct AFX_MSGMAP { //实际的消息映射表.
const AFX_MSGMAP* pBaseMap;//基类的消息映射表.
const AFX_MSGMAP_ENTRY* lpEntries ;//消息表入口数组指针.
}
消息映射宏:
#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; \
#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 } \
};
常用消息表入口宏:
ON_WM_XXXX:预定义的窗口消息
ON_COMMAND:命令
ON_UPDATE_COMMAND_UI:更新命令
ON_XXXX:控件通知
ON_MESSAGE:用户自定义的消息
ON_REGISTERED_MESSAGE:注册的窗口消息
ON_COMMAND_RANGE:指定ID范围的命令
ON_UPDATE_COMMAND_UI_RANGE:指定ID范围的更新命令
ON_CONTROL_RANGE:指定ID范围的控件
ON_NOTIFY:通知消息
ON_NOTIFY_RANGE:指定ID范围的通知消息
...
实现示例:
#define ON_COMMAND(id, memberFxn) \
{ WM_COMMAND, CN_COMMAND, (WORD)id, (WORD)id, AfxSig_vv, (AFX_PMSG)&memberFxn },
// ON_COMMAND(id, OnFoo) is the same as
// ON_CONTROL(0, id, OnFoo) or ON_BN_CLICKED(0, id, OnFoo)
------------------------------
消息映射表的使用
消息映射表能处理两种消息:一般的窗口消息(如鼠标消息)和命令消息(如菜单消息).
窗口和AfxWndProc建立关联的过程
MFC中以DefWindowProc为消息处理函数进行注册.当新的CWnd派生类创建时(CreateEx中),::CreateAWindowEx之前会调用AfxHookWindowCreate()设置HOOK:_AfxCbtFilterHook(),以处理窗口的激活,创建,销毁等消息._AfxCbtFilterHook等到HCBT_CREATEWND消息到来时调用_AfxStandardSubClass(),由它调用SetWindowLong()将AfxWndProc()放入窗口,由AfxWndProc()处理实际的消息.----MFC不直接注册AfxWndProc作为消息处理函数以支持3D控件,此时要保证处理过程按以下顺序调用:AfxWndProc,3D控件的WndProc,默认的DefWindowProc.
AfxWndProc消息处理过程
1.AfxWndProc处理消息时首先判断是否是WM_QUERYAFXWNDPROC,是就返回1,表示使用MFC的消息映射系统.
2.调用AfxCallWndProc.AfxCallWndProc在WM_INITDIALOG消息中将调用_AfxHandleInitDialog使对话框居中.AfxCallWndProc还将在线程状态中对消息对得保存,并最后调用窗口对象的窗口过程:虚函数WindowProc();
3.CWnd::WindowProc调用CWnd::OnWndMsg(),如返回FALSE,则再调用CWnd::DefWindowProc();
4.CWnd::OnWndMsg()对应于SDK程序中的switch语句.首先它过滤特殊的消息WM_COMMAN,WM_NOTIFY,WM_ACTIVATE,WM_SETCURSOR并调用框架类对应的特殊处理函数.其它消息进入消息映射表中去查找处理函数.
5.WM_COMMAND的处理.
1).第一站:虚函数CWnd::OnCommand();消息是框架类产生的,故调用框架类的OnCommand()实现.OnCommand检查表示控件的LPARAM参数,控件产生的消息会在LPARAM中包含控件窗口,对控件通知消息会调用特写处理过程.如消息是为某个控件产生的,会OnCommand在将消息直接发送给该控件后返回;否则,它保证产生命令的用户界面元素没有被禁用,然后将调用OnCmdMsg.
2).CFrame::OnCmdMsg().它按以下顺序查找在消息映射表中查找处理函数:活动视图,活动视图的文档,主窗口,应用程序.找到后就调用DispatchCmdMsg以执行所找到的处理函数,没找到时调用DefWindowProc.
3).static BOOL DispatchCmdMsg():根据函数签名(消息表入口项中的nSig变量)执行不同操作.一般菜单命令的签名是AfxSig_xx,会直接调用处理函数,其它签名可能要预先分解消息参数LPARAM,WPARAM;
到达框架窗口的命令路由:AfxWndProc--AfxCallWndProc--CWnd::WindowProc--CFrameWnd::OnCommand--CWnd::OnCommand--CFrameWnd::OnCmdMsg--CCmdTarget::OnCmdMsg--DispatchCmdMsg--CMainFrame类命令处理AfxWndProc--AfxCallWndProc--CWnd::WindowProc--CFrameWnd::OnCommand--CWnd::OnCommand--CFrameWnd::OnCmdMsg--CCmdTarget::OnCmdMsg--DispatchCmdMsg--CMainFrame类命令处理函数
到达文档的命令路由:AfxWndProc--AfxCallWndProc--CWnd::WindowProc--CFrameWnd::OnCommand--CWnd::OnCommand--CFrameWnd::OnCmdMsg--CView::OnCmdMsg--CDocument::OnCmdMsg--CCmdTarget::OnCmdMsg--DispatchCmdMsg--文档类命令处理函数
到达视图的命令路由:AfxWndProc--AfxCallWndProc--CWnd::WindowProc--CFrameWnd::OnCommand--CWnd::OnCommand--CFrameWnd::OnCmdMsg--CView::OnCmdMsg--CCmdTarget::OnCmdMsg--DispatchCmdMsg--视图类命令处理函数
到达应用程序类的命令路由:AfxWndProc--AfxCallWndProc--CWnd::WindowProc--CFrameWnd::OnCommand--CWnd::OnCommand--CFrameWnd::OnCmdMsg--CCmdTarget::OnCmdMsg--DispatchCmdMsg--应用程序类命令处理函数
到达对话框类的命令路由:AfxWndProc--AfxCallWndProc--CWnd::WindowProc--CWnd::OnCommand--CWnd::OnCmdMsg--CDialog::OnCmdMsg--DispatchCmdMsg--对话框类命令处理函数
6.处理一般窗口消息:AfxWndProc--AfxCallWndProc--AfxCallWndProc--CWnd::WindowProc--CWnd::OnWndMsg--AfxFindMessageEntry--实际处理函数
7.调用成员函数.
函数签名的定义:
union MessageMapFunctions {
Afx_PMSG pfn ;//一般成员函数指针.
BOOL (AFX_MSG_CALL CWnd::*pfn_bD)(CDC*);
BOOL (AFX_MSG_CALL CWnd::*pfn_bb(BOOL);
.......
};
在OnWndMsg中会将此联合中的pfn设为消息处理函数的地址:mmf.pfn=lpEntry->pfn,同时,查找合适的签名,从WPARAM,LPARAM中取出必要的参数,使用与签名一致的原型调用处理函数.
8.其它类型的消息.
1).WM_NOTIFY.
CWnd::ONWndMsg()用CWnd::OnNotify()来进行处理.OnNotify调用OnChildNotify()将消息送给控件.
2).消息反射.
可用消息反射宏来实现,以便控件自己处理特定的消息.
3).WM_ACTIVATE.
OnWndMsg()中调用_AfxHandleActivate()检查最高层是否是WM_ACTIVATE,是则向最高层窗口发送WM_ACTIVATETOPLEVEL消息.
4).WM_SETCURSOR.
在_AfxHandleSetCursor()中处理.有鼠标按下时会激活最后一个活动窗口.
---------------------------------
PreTranslateMessage----消息预处理
共两个入口:CWinApp::PreTranslateMessage,CWnd::PreTranslateMessage.
在消息由TranslateMessage()和DispatchMessage()处理前,CWinApp::Run()调用CWinApp::PreTranslateMessage,然后,CWinApp::PreTranslateMessage会从消息结构中对指定的目标窗口及应用程序的主窗口调用每个窗口的CWnd::Translatemessage().
预处理过程返回TRUE,则消息不再进行后继处理.