MFC中用戶自定義類響應自定義消息
這篇技術文章不是討論經典的MFC中的消息工作機理的,討論消息工作原理、方式和路徑的文章在網上和書本中隨處可見。網上眾多的討論都是關於如何響應並進行用戶自定義消息映射的;網上還有一些文章介紹如何在自定義類中響應Windows消息,在本文中都簡略敘述。但是,網上大部分的文章沒用透徹闡述如何在用戶自定義類中響應自定義消息這一通用方法。
問題定義如下:用戶自定義一個類,這個類不一定要有界面(完全可以是不可視的),要求自定義的類可以響應某個自定義消息。
首先能夠響應消息的類必須都從CCmdTarget類中派生,因為只有以這個類中提供了消息的框架和處理機制,而CWnd類也派生與此類。CWinApp類、CDocument類、CDocTemplate類等都是CmdTarget的派生類,即子類;而CFrameWnd類、CView類、CDialog類等都是從CWnd中派生的,其實也是CCmdTarget的子孫,所以都能夠響應消息,但是響應消息的種類不太相同。那麽,如果自己定義的類要求響應命令消息(就是WM_COMMAND,也就是一些菜單、工具欄中的消息,包括快捷鍵,這類消息處理的機制與其他以WM_開頭的消息處理機制不同,它具有一條層次明確的消息流動路徑),那麽自定義的類可以從CCmdTarget中派生。由於CWnd窗體類派生於CCmdTarget父類,那麽從CWnd中派生的類也可以理所應當的響應命令消息。這種命令消息無論是往已有的一些諸如CWinApp類中還是自定義的類中添加都是一件非常容易的事情,只需用向導即可,在此不再敘述。
如果用戶自定義的類要求響應普通的Windows消息(也就是以WM_開頭,除了WM_COMMAND以外的消息,這類消息在WM_USER以下的是系統消息,WM_USER以上的可以由用戶自己定義),那就要求自定義的類必須從CWnd中派生。這是由於此類消息的處理機制決定的,這類消息沒有命令消息那條繁瑣的流動路徑,而是消息發出者直接發給對應CWnd的窗體句柄,由CWnd負責消息的響應。所以這類消息必須同一個CWnd類對應,更精確的說必須與一個HWND類型的窗體句柄相對應。這樣得出一個重要的結論,就是從CCmdTarget中派生而沒有從CWnd派生的類沒有處理此類消息的能力。
綜上所述,就是為什麽命令消息可以放到大部分類中處理,包括CWinThread、CWinApp、CDocument、CView、CFrameWnd或是自定義的類中,而普通Windows消息和用戶自定義的消息只能放到CFrameWnd和CView等派生與CWnd中的類中處理。
由此可見,我們自定義的類要想響應自定義消息就只能從CWnd中派生(當然不響應任何消息的類可以從CObject中派生)。先來看看如何自定義消息:
在.h中做的工作:
第一步要聲明消息:
#define WM_MYMSG WM_USER+8
第二步要在類聲明中聲明消息映射:
DECLARE_MESSAGE_MAP()
第三步要在類聲明中定義消息處理函數:
afx_msg LRESULT MyMsgHandler(WPARAM,LPARAM);
在.cpp中做的工作:
第四步要實現消息映射:
BEGIN_MESSAGE_MAP(CMainFrame, CMDIFrameWnd) ON_MESSAGE(WM_MYMSG,OnMyMsgHandler) END_MESSAGE_MAP()
第五步要實現消息處理函數(當然可以不實現):
LRESULT CMainFrame::OnMyMsgHandler(WPARAM w,LPARAM l) { AfxMessageBox("Hello,World!"); return 0; }
在引發或發出消息的地方只用寫上:
::SendMessge(::AfxGetMainWnd()->m_hWnd,WM_MYMSG,0,0);
到此,自定義消息完畢,這是好多網上文章都寫的東西。大家會發現上面代碼是在CMainFrame類中實現的,但是如果要用自定義類,就沒有那麽簡單了。顯然把第四步與第五步的CMainFrame換成自定義的類名(這裏我用CMyTestObject來代表自定義類)是不能正常工作的。原因在於在發送消息的SendMessage函數中的第一個參數是要響應消息對應的HWND類型的窗體句柄,而CMyTestObject類中的m_hWnd中在沒有調用CWnd::Create之前是沒有任何意義的,也就是沒有調用CWnd::Create或CWnd::CreateEx函數時,CWnd不對應任何窗體,消息處理不能正常運作。
所以,又一個重要的結論,在自定義類能夠處理任何消息之前一定要確保m_hWnd關聯到一個窗體,即便這個窗體是不可見的。那麽有人說,在自定義類的構造函數中調用Create函數就行了,不錯,當然也可以在別處調用,只要確保在消息發送之前。但是,Create的調用很有說法,要註意兩個地方,第一個參數是類的名稱,我建議最好設為NULL;第五個參數是父窗體對象的指針,這個函數指定的對象一定要存在,我建議最好為整個程序的主窗體。還有很多人問第六個參數的意義,這個參數關系不大,是子窗體ID,用於傳給父窗體記錄以便識別。如下是我的自定義類的構造函數:
CMyTestObject::CMyTestObject() { CWnd::Create(NULL,"MyTestObject",WS_CHILD,CRect(0,0,0,0),::AfxGetMainWnd(),1234); } //一定要在生成主窗體後使用,在主窗體完成OnCreate消息的處理後 CMyTestObject::CMyTestObject(CWnd *pParent) { CWnd::Create(NULL,"MyTestObject",WS_CHILD,CRect(0,0,0,0),pParent,1234); }
不能如下調用Create,因為此時CMyTestObject不關聯任何窗體,所以this中的m_hWnd無效:
CWnd::Create(NULL,"MyTestObject",WS_CHILD,CRect(0,0,0,0),this,1234);
這時上面四、五兩步修改成:
BEGIN_MESSAGE_MAP(CMyTestObject, CWnd) ON_MESSAGE(WM_MYMSG,OnMyMsgHandler) END_MESSAGE_MAP() LRESULT CMyTestObject::OnMyMsgHandler(WPARAM w,LPARAM l) { AfxMessageBox("My Messge Handler in My Self-Custom Class!"); return 0; }
在類外部發出消息:
CMyTestObject *test=new CMyTestObject(); ::SendMessage(test->m_hWnd,WM_MYMSG,0,0);
在類內部某個成員函數(方法)中發出消息:
::SendMessage(m_hWnd,WM_MYMSG,0,0);
最後一個問題便是容易產生警告錯誤的窗體回收,自定義的類要顯式調用窗體銷毀,析構函數如下:
CMyTestObject::~CMyTestObject()
{
CWnd::DestroyWindow();
}
MFC中用戶自定義類響應自定義消息