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