天天看點

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();
}