詳細介紹MFC中的消息映射
MFC是微軟基礎(chǔ)類庫的簡稱,是微軟公司實現(xiàn)的一個c++類庫,主要封裝了大部分的windows API函數(shù),消息系統(tǒng)對于一個win32程序來說十分重要,它是一個程序運行的動力源泉。一個消息,是系統(tǒng)定義的一個32位的值,他唯一的定義了一個事件,向Windows發(fā)出一個通知,告訴應(yīng)用程序某個事情發(fā)生了。
眾所周知,windows是基于消息驅(qū)動的,作好消息處理是WINDOWS編程的關(guān)鍵任務(wù)之一,用VC制作WINDOWS程式同樣離不開消息的處理。雖然VC++6的類向?qū)Э梢酝瓿山^大部分工作,但不幸的是,它并不能完成所有的工作。這就要求我們對 VC中消息的處理有一個比較清淅的認識。只有這樣才可能在必要的時候親自動手完成一些復(fù)雜的消息映射處理。
在MFC中消息是通過一種所謂的消息映射機制來處理的。其實質(zhì)是一張消息及其處理函數(shù)的一一對應(yīng)表以及分析處理這張表的應(yīng)用框架內(nèi)部的一些程序代碼.這樣的好處是可以避免像早期的 SDK編程一樣需要羅列一大堆的CASE語句來處理各種消息.由于不同種類的消息其處理方法是不同的,所以我們有必要先弄清楚 WINDOWS消息的種類。
背景:
WINDOWS 消息的種類
WINDOWS中消息主要有以下三種類型:
1、標準的WINDOWS消息:這類消息是以WM_為前綴,不過WM_COMMAND例外。 例如: WM_MOVE、WM_QUIT等.
2、命令消息:命令消息以WM_COMMAND為消息名.在消息中含有命令的標志符ID,以區(qū)分具體的命令.由菜單,工具欄等命令接口對象產(chǎn)生.
3、控件通知消息:控件通知消息也是以WM_COMMAND為消息名.由編輯框,列表框,子窗口發(fā)送給父窗口的通知消息.在消息中包含控件通知碼.以區(qū)分具體控件的通知消息.
其中標準的WINDOWS消息及控件通知消息主要由窗口類即直接或間接由CWND類派生類處理.相對標準WINDOWS消息及控件通知消息而言,命令消息的處理對象范圍就廣得多.它不僅可以由窗口類處理,還可以由文檔類,文檔模板類及應(yīng)用類所處理。
方法:
不同種類消息的映射方法。
在以上三種消息中,標準的WINDOWS消息映射是相當簡單的。可直接通過類向?qū)瓿刹煌⒌挠成涮幚恚圆辉诒疚挠懻撝小?/p>
凡是從CcmdTarget類派生的類都可以有消息映射.消息映射包括如下兩方面的內(nèi)容:
在類的定義文件中(.H)中加上一條宏調(diào)用:
- DECLARE_MESSAGE_MAP()
通常這條語句中類定義的***.
在類的實現(xiàn)文件(.CPP)中加上消息映射表:
- BEGIN_MESSAGE_MAP(類名,父類名)
- ………..
- 消息映射入口項.
- ……….
- END_MESSAGE_MAP( )
幸運的是除了某些類(如沒有基類的類或直接從CobjectO類派生的類)外.其它許多類均可由類向?qū)?盡管生成的類只是一個框架,需要我們補充內(nèi)容.但消息映射表已經(jīng)為我們加好了.只是入口項有待我們加入.
命令消息映射入口項是一個ON_COMMAND的宏.比如文件菜單下的"打開…"菜單(ID值為ID_FILE_OPEN)對應(yīng)的消息映射入口項為:
- ON_COMMAND(ID_FILE_NEW,OnFileOpen)
加入消息映射入口項之后需要完成消息處理函數(shù).在類中消息處理函數(shù)都是類的成員函數(shù),要響應(yīng)一個消息,就必須定義一個該消息的處理函數(shù).定義一個消息處理函數(shù)包括以下三方面的內(nèi)容.
- 在類定義中加入消息處理函數(shù)的函數(shù)原型(函數(shù)聲明)
- 在類的消息映射表中加入相應(yīng)的消息映射入口項.
- 在類的實現(xiàn)中加入消息處理函數(shù)的函數(shù)體.
需要說明的是消息處理函數(shù)的原型一定要以afx_msg打頭.比如:
- afx_msg OnFileOpen();// 函數(shù)原型
作為約定.消息處理函數(shù)一般以O(shè)n打頭
但有時我們可能想用一個消息處理函數(shù)來處理一批消息。這時類向?qū)Ь蜔o能為力了。我們必須手工加入消息映射來完成這種工作。可用如下方法實現(xiàn):
首先在處理該消息所在類的實現(xiàn)文件(亦即.CPP)中加入的消息映射入口:
- ...
- BEGIN_MESSAGE_MAP(CMyApp, CWinApp)
- file://{{AFX_MSG_MAP(CMyApp)
- ...
- file://}}AFX_MSG_MAP
- ON_COMMAND_RANGE(ID_MYCMD_ONE, ID_MYCMD_TEN, OnDoSomething)
- END_MESSAGE_MAP( )
- ...
粗體標志的語句是我們加入的語句(以后約定我們加入的語句均用粗體標志).其中我們使用了宏ON_COMMAND_RANGE來實現(xiàn)從命令消息 ID_MYCMD_ONE到 ID_MYCMD_TEN都由OnDoSomthing一個消息函數(shù)處理.注意.ID_MYCMD_ONE到 ID_MYCMD_TEN的ID值一定要連續(xù).且ID_MYCMD_ONE值一般較小.
完成上述工作之后我們還需要在該類的頭文件(亦即.H)中加入消息處理函數(shù)的申明:
- // Generated message-map functions
- protected:
- file://{{AFX_MSG(CMyApp)
- ...
- file://}}AFX_MSG
- afx_msg void OnDoSomething( UINT nID );
- DECLARE_MESSAGE_MAP()
- 由于這不是VC類向?qū)Ъ尤氲暮瘮?shù)申明,所以放在了//}}AFX_MSG之外.
注意這個消息處理函數(shù)有一個UINT類型參數(shù).而處理單一命令的消息處理函數(shù)一般是沒有參數(shù)(除更新用戶接口對象狀態(tài)命令消息處理函數(shù)).這個參數(shù)的主要作用是提供用戶選擇的命令的ID值.
***要做的工作就是在該類的實現(xiàn)文件中實現(xiàn)該消息處理函數(shù). 同樣,有時我們也想使用一個消息處理函數(shù)處理一批更新用戶接口對象狀態(tài)命令消息.方法同上:
首先在.CPP文件中加入語句如下:
- ...
- BEGIN_MESSAGE_MAP(CMyApp, CWinApp)
- file://{{AFX_MSG_MAP(CMyApp)
- ...
- file://}}AFX_MSG_MAP
- ON_UPDATE_COMMAND_UI_RANGE (ID_MYCMD_ONE, ID_MYCMD_TEN, OnUpdateSomething)
- END_MESSAGE_MAP( )
- ...
在該類的頭文件(亦即.H)中加入消息處理函數(shù)的申明:
- // Generated message-map functions
- protected:
- file://{{AFX_MSG(CMyApp)
- ...
- file://}}AFX_MSG
- afx_msg void OnUpdateSomething( CcmdUI * pcmdui );
- DECLARE_MESSAGE_MAP()
請各位注意了,仔細的讀者已經(jīng)注意到這里的消息處理函數(shù)并未像命令消息處理函數(shù)需要一個額外的UINT類型的參數(shù).原因在于pcmdui中已包含了此信息.所以不再需要這個參數(shù)了.***不要忘了完成函數(shù)體!
關(guān)于命令消息就討論到這個地方.接下來討論控件通知消息.
控件通知消息相對而言就復(fù)雜一點了.限于篇幅不能一一涉及.這里我們僅討論 WM_NOTIFY消息的處理.
WM_NOTFY產(chǎn)生的原因如下。
在WINDOWS3.X中控件通知它們父窗口,如鼠標點擊,控件背景繪制事件,通過發(fā)送一個消息到父窗口.簡單的通知僅發(fā)送一個WM_COMMAND消息.包含一個通知碼(比如BN_CLICKED)和一個在wParam中的控件ID及一個在lPraram中的控件句柄.因為wParam 和lParam均被使用.就沒有方法傳送其它的附加信息了.比如在BN_CLICKED 通知消息中.就沒有辦法發(fā)送關(guān)于當鼠標點擊時光標的位置信息.在這種情況下就只能使用一些特殊的消息.包括:WM_CTLCOLOR,WM_VSCROLL, WM_HSCROLL等等.值得一提的是這些消息能被反射回發(fā)送它們的控件.就是所謂的消息反射.有興趣的讀者請參閱有關(guān)專著.
在WIN32中同樣可以使用那些在WINDOWS3.1中使用的通知消息.不過不像過去通過增加特殊目的的消息來為新的通知發(fā)送附加的數(shù)據(jù).而是使用一個叫 WM_NOTIFY的消息,它能以一個標準的風格傳送大量的附加數(shù)據(jù).
WM_NOTIFY 消息包含一個存在wParam中的發(fā)送消息控件的ID和一個存在 lParam中的指向一個結(jié)構(gòu)體的指針.這個結(jié)構(gòu)可能是NMHDR結(jié)構(gòu)體.也可能是***個成員是NMHDR的更大的結(jié)構(gòu).因為NMHDR是***個成員,所以指向這個結(jié)構(gòu)的指針也可以指向NMHDR.
在許多情況下,這個指針將指向一個更大的結(jié)構(gòu),當你使用時必需轉(zhuǎn)換它.只有很少的通知消息.比如通用通知消息(它的名字以NM_打頭),工具提示控件的 TTN_SHOW和TTN_POP實際上在使用NMHDR結(jié)構(gòu).
NMHDR結(jié)構(gòu)包含了發(fā)送消息控件的句柄,ID及通知碼(如TTN_SHOW),其格式如下:
- Typedef sturct tagNMHDR{
- HWND hwndFrom;
- UINT idFrom;
- UINT code;
- } NMHDR;
對TTN_SHOW消息而言,code成員的值將設(shè)為TTN_SHOW.
類向?qū)Э梢詣?chuàng)建ON_NOTIFY消息映射入口并為你提供一個處理函數(shù)的框架.來處理 WM_NOTIFY類型的消息.ON_NOTIFY消息映射宏有如下語法.
- ON_NOTIFY(wNotifyCode,id,memberFxn)
數(shù)意義如下:
- wNotifyCode:要處理的通知消息通知碼。比如:LVN_KEYDOWN.
- Id:控件標識ID.
- MemberFxn:處理此消息的成員函數(shù).
此成員函數(shù)必需有如下的原形申明:
- afx_msg void memberFxn( NMHDR * pNotifyStruct, LRESULT * result);
比如:假設(shè)你想成員函數(shù)OnKeydownList1處理ClistCtrl(標識ID=IDC_LIST1)的 LVN_KEYDOWN消息,你可以使用類向?qū)砑尤缦碌南⒂成?
- ON_NOTIFY( LVN_KEYDOWN, IDC_LIST1, OnKeydownList1 )
在上面的例子中,類向?qū)峁┤缦潞瘮?shù):
- void CMessageReflectionDlg::OnKeydownList1(NMHDR* pNMHDR, LRESULT* pResult)
- {
- LV_KEYDOWN* pLVKeyDow = (LV_KEYDOWN*)pNMHDR;
- // TODO: Add your control notification handler
- // code here
- *pResult = 0;
- }
這時類向?qū)峁┝艘粋€適當類型的指針.你既可以通過pNMHDR,也可以通過 pLVKeyDow來訪問這個通知結(jié)構(gòu)。
如前所述,有時我們可能需要為一組控件處理相同的WM_NOTIFY消息.這時需要使用ON_NOTIFY_RANGE而不是ON_NOTIFY.當你使用 ON_NOTIFY_RANGE時,你需要指定控件的ID范圍.其消息映射入口及函數(shù)原型如下:
- ON_NOTIFY_RANGE( wNotifyCode, id, idLast, memberFxn )
參數(shù)說明:
- wNotifyCode:消息通知碼.比如:LVN_KEYDOWN,
- id: ***控件的標識ID。
- idLast:***一個控件的標識ID。(標識值一定要連續(xù))
- memberFxn: 消息處理函數(shù)。
成員函數(shù)必須有如下原型申明:
- afx_msg void memberFxn( UINT id, NMHDR * pNotifyStruct, LRESULT * result );
其中id的表示發(fā)送通知消息的控件標識ID
結(jié)束語:
于目前介紹MFC消息映射的資料甚少.而這部分內(nèi)容對編程又相當重要.本文簡要地介紹了MFC中的幾種重要的消息映射處理.但基于篇幅有限沒能作更全面更深入的探討.