引言:
眾所周知,windows是基於消息驅動的,作好消息處理是WINDOWS編程的關鍵任務之一,用VC制作Windows程式同樣離不開消息的處理。雖然VC++6的類向導可以完成絕大部分工作,但不幸的是,它並不能完成所有的工作。這就要求我們對 VC中消息的處理有一個比較清淅的認識。只有這樣才可能在必要的時候親自動手完成一些復雜的消息映射處理。
在MFC中消息是通過一種所謂的消息映射機制來處理的。其實質是一張消息及其處理函數的一一對應表以及分析處理這張表的應用框架內部的一些程序代碼.這樣的好處是可以避免像早期的SDK編程一樣需要羅列一大堆的CASE語句來處理各種消息.由於不同種類的消息其處理方法是不同的,所以我們有必要先弄清楚 Windows消息的種類。
背景:
Windows 消息的種類
Windows中消息主要有以下三種類型:
1、標准的Windows消息:這類消息是以WM_為前綴,不過WM_COMMAND例外。 例如: WM_MOVE、WM_QUIT等.
2、命令消息:命令消息以WM_COMMAND為消息名.在消息中含有命令的標志符ID,以區分具體的命令.由菜單,工具欄等命令接口對象產生.
3、控件通知消息:控件通知消息也是以WM_COMMAND為消息名.由編輯框,列表框,子窗口發送給父窗口的通知消息.在消息中包含控件通知碼.以區分具體控件的通知消息.
其中標准的WINDOWS消息及控件通知消息主要由窗口類即直接或間接由CWND類派生類處理.相對標准Windows消息及控件通知消息而言,命令消息的處理對象范圍就廣得多.它不僅可以由窗口類處理,還可以由文檔類,文檔模板類及應用類所處理。
方法:
不同種類消息的映射方法。
在以上三種消息中,標准的Windows消息映射是相當簡單的。可直接通過類向導完成不同消息的映射處理,所以不在本文討論之列。
凡是從CcmdTarget類派生的類都可以有消息映射.消息映射包括如下兩方面的內容:
在類的定義文件中(.H)中加上一條宏調用:
DECLARE_MESSAGE_MAP()
通常這條語句中類定義的最後.
在類的實現文件(.CPP)中加上消息映射表:
BEGIN_MESSAGE_MAP(類名,父類名)
………..
消息映射入口項.
……….
END_MESSAGE_MAP( )
幸運的是除了某些類(如沒有基類的類或直接從CobjectO類派生的類)外.其它許多類均可由類向導生成.盡管生成的類只是一個框架,需要我們補充內容.但消息映射表已經為我們加好了.只是入口項有待我們加入.
命令消息映射入口項是一個ON_COMMAND的宏.比如文件菜單下的"打開…"菜單(ID值為ID_FILE_OPEN)對應的消息映射入口項為:
ON_COMMAND(ID_FILE_NEW,OnFileOpen)
加入消息映射入口項之後需要完成消息處理函數.在類中消息處理函數都是類的成員函數,要響應一個消息,就必須定義一個該消息的處理函數.定義一個消息處理函數包括以下三方面的內容.
1.在類定義中加入消息處理函數的函數原型(函數聲明)
2.在類的消息映射表中加入相應的消息映射入口項.
3.在類的實現中加入消息處理函數的函數體.
需要說明的是消息處理函數的原型一定要以afx_msg打頭.比如:
afx_msg OnFileOpen();// 函數原型
作為約定.消息處理函數一般以On打頭
但有時我們可能想用一個消息處理函數來處理一批消息。這時類向導就無能為力了。我們必須手工加入消息映射來完成這種工作。可用如下方法實現:
首先在處理該消息所在類的實現文件(亦即.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來實現從命令消息ID_MYCMD_ONE到 ID_MYCMD_TEN都由OnDOSomthing一個消息函數處理.注意.ID_MYCMD_ONE到 ID_MYCMD_TEN的ID值一定要連續.且ID_MYCMD_ONE值一般較小.
完成上述工作之後我們還需要在該類的頭文件(亦即.H)中加入消息處理函數的申明:
// Generated message-map functions
protected:
file://{{AFX_MSG(CMyApp)
...
file://}}AFX_MSG
afx_msg void OnDOSomething( UINT nID );
DECLARE_MESSAGE_MAP()
由於這不是VC類向導加入的函數申明,所以放在了//}}AFX_MSG之外.
注意這個消息處理函數有一個UINT類型參數.而處理單一命令的消息處理函數一般是沒有參數(除更新用戶接口對象狀態命令消息處理函數).這個參數的主要作用是提供用戶選擇的命令的ID值.
最後要做的工作就是在該類的實現文件中實現該消息處理函數. 同樣,有時我們也想使用一個消息處理函數處理一批更新用戶接口對象狀態命令消息.方法同上:
首先在.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)中加入消息處理函數的申明:
// Generated message-map functions
protected:
file://{{AFX_MSG(CMyApp)
...
file://}}AFX_MSG
afx_msg void OnUpdateSomething( CcmdUI * pcmdui );
DECLARE_MESSAGE_MAP()
請各位注意了,仔細的讀者已經注意到這裡的消息處理函數並未像命令消息處理函數需要一個額外的UINT類型的參數.原因在於pcmdui中已包含了此信息.所以不再需要這個參數了.最後不要忘了完成函數體!
關於命令消息就討論到這個地方.接下來討論控件通知消息.
控件通知消息相對而言就復雜一點了.限於篇幅不能一一涉及.這裡我們僅討論 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),其格式如下:
Typedef sturct tagNMHDR{
HWND hwndFrom;
UINT idFrom;
UINT code;
} NMHDR;
對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 )
在上面的例子中,類向導提供如下函數:
void CMessageReflectionDlg::OnKeydownList1(NMHDR* pNMHDR, LRESULT* pResult)
{
LV_KEYDOWN* pLVKeyDow = (LV_KEYDOWN*)pNMHDR;
// TODO: Add your control notification handler
// code here
*pResult = 0;
}
這時類向導提供了一個適當類型的指針.你既可以通過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消息映射的資料甚少.而這部分內容對編程又相當重要.本文簡要地介紹了MFC中的幾種重要的消息映射處理.但基於篇幅有限沒能作更全面更深入的探討.