1. 程式人生 > >MFC視窗之間的訊息傳遞

MFC視窗之間的訊息傳遞

視窗訊息的傳遞 http://bbs.bccn.net/thread-348167-1-1.html
宣告:本文非本人所寫,本文已經寫在本人CSDN部落格裡。本原來源於一篇英文文章的翻譯。

        訊息(Message)是視窗間通訊的最重要的方式之一。傳統的程式從main()函式處開始一行一行的執行直到退出,但是視窗的概念則不同。視窗對事件(event)進行響應,這種事件稱為訊息。事件由程式本身、其他程式或系統程式產生,這些事件又產生訊息。滑鼠移動、按鍵等都會產生事件。訊息分為兩種即視窗訊息和執行緒訊息。這裡只分析視窗訊息。

         所謂的視窗訊息,大致上,必須傳遞給一個視窗。所有的訊息都儲存在訊息佇列(Message Queue)中。訊息佇列用於在應用程式之間傳遞訊息。

        從訊息佇列中捕獲訊息的方式是訊息迴圈(Message Loop)。一旦一個訊息被某個視窗接受,訊息迴圈即分配此訊息並呼叫一個訊息控制代碼,有一個由程式設計師設計的函式用於處理此訊息。


        訊息迴圈在接收到WM_QUIT訊息後終止,並指示程式結束。當用戶選擇File選單下的Exit子選單、點選關閉按鈕、按下alt+F4時均會產生WM_QUIT訊息。視窗有預設的訊息控制代碼用來進行預設行為。例如,按鈕(Button)派生於視窗類,當按鈕接收到WM_PAINT時會重新繪製按鈕,當左擊按鈕時會接收到WM_LBUTTONDOWN並自繪按下的按鈕形態。

        視窗定義了很多型別的訊息,他們通常以“WM”開頭。例如WM_SIZE,當視窗大小發生變化時傳送此訊息。在MFC中,用On代替“WM_”,例如WM_SIZE在MFC中表示為OnSize。

        一個訊息有兩個引數,這兩個引數攜帶該事件的一些資訊。每個引數均是32位寬的,lParam和wParam。有的時候訊息也會返回一個值給傳送該訊息的視窗。


        MFC自動的生成了訊息迴圈所需的程式碼,WinMain呼叫的CWinApp成員函式提供訊息迴圈並將這些訊息送給各個視窗。需要我們做的只是建立訊息控制代碼,這可以藉助於ClassWizard完成。下面即是一個響應WN_CLOSE訊息的例子。
程式程式碼: view plaincopy to clipboardprint?void CAboutWindow::OnClose() 

    int Ret = MessageBox(_T("Are you sure you want to close the window?"), 
                         _T("
Close Window?"), MB_YESNO); 
    if(Ret == IDYES){ 
        // The User is sure, close the window by calling the base class 
        
// member         CWnd::OnClose() 
    } 
    else
        // The user pressed no, screen out the message by not calling 
        
// the base class member 
 
        
//Do nothing     } 

void CAboutWindow::OnClose()
{
    int Ret = MessageBox(_T("Are you sure you want to close the window?"),
                         _T("Close Window?"), MB_YESNO);
    if(Ret == IDYES){
        // The User is sure, close the window by calling the base class
        
// member        CWnd::OnClose()
    }
    else{
        // The user pressed no, screen out the message by not calling
        
// the base class member

        
//Do nothing    }
}
        為了視窗間的通訊,程式設計師需要自己傳送訊息。由於訊息均是視窗傳送的,所以需要一個C++視窗指標。可以通過CWnd::FindWindow、GetDlgItem()、GetParent()等獲得視窗指標。CWnd類有一個SendMessage()成員函式用於傳送訊息給他的視窗。例如,如果有一個日曆控制元件需要去關閉,可以通過產生一個WM_CLOSE訊息去告知該控制元件。可以通過CWnd::FindWindow()傳遞一個Caption來獲得指向該控制元件的C++視窗指標。
程式程式碼: view plaincopy to clipboardprint?CWnd *pCalc; 
//Get a pointer to the "Calculator" Window pCalc = CWnd::FindWindow(NULL, _T("Calculator)); if(pCalc == NULL){ 
    //Couldn't find Calculator 
else
    pCalc->SendMessage(WM_CLOSE); 
    //Presto! The Calculator should close. 
當一個視窗接收到某個訊息後,MFC將呼叫類的成員函式。但是MFC如何知道該呼叫哪個函式呢?

        為了解決上述問題,MFC運用了一個叫做訊息對映(Message Map)機制。訊息對映就是將訊息和所要呼叫的函式繫結在一起。一旦接受到一個訊息,MFC將進入訊息對映去尋找與該訊息相對應的訊息控制代碼。

         MFC採用的是一系列的巨集(Macros)去新增訊息對映到類中。當運用ClassWizard去新增一個訊息控制代碼時首先新增該 函式到類中然後新增響應的巨集到訊息對映中。例如若用ClassWizard新增WM_CLOSE的訊息控制代碼時,會有一下三個動作:

1.在類的實現(.cpp)中的訊息對映中:

程式程式碼: view plaincopy to clipboardprint?BEGIN_MESSAGE_MAP(CAboutDlg, CDialog) 
    //{{AFX_MSG_MAP(CAboutDlg)      ON_WM_CLOSE() 
    //}}AFX_MSG_MAP  END_MESSAGE_MAP() 
BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
    //{{AFX_MSG_MAP(CAboutDlg)    ON_WM_CLOSE()
    //}}AFX_MSG_MAPEND_MESSAGE_MAP()
2.在類(.h)宣告中宣告函式:該函式前有afx_msg關鍵字

程式程式碼: view plaincopy to clipboardprint?protected
    //{{AFX_MSG(CAboutDlg)      afx_msg void OnClose(); 
    //}}AFX_MSG      DECLARE_MESSAGE_MAP() 
protected:
    //{{AFX_MSG(CAboutDlg)    afx_msg void OnClose();
    //}}AFX_MSG    DECLARE_MESSAGE_MAP()
3.在類的實現(.cpp)中:

程式程式碼: view plaincopy to clipboardprint?void CAboutDlg::OnClose()  

    // TODO: Add your message handler code here and/or call default       
    CDialog::OnClose(); 

void CAboutDlg::OnClose()
{
    // TODO: Add your message handler code here and/or call default   
    CDialog::OnClose();
}

巨集DECLARE_MESSAGE_MAP()告知MFC給訊息對映新增必要的程式碼。

巨集BEGIN_MESSAGE_MAP()表示訊息對映的開始,括弧類的兩個引數指示了傳送訊息的類和其基類。

巨集END_MESSAGE_MAP()表示訊息對映的結束。

        有一些訊息ClassWizard並不支援但是可以手工新增。但有得時候沒有訊息對映巨集,這個時候可以採用通用巨集ON_MESSAGE,利用該巨集可以傳遞任何訊息。

view plaincopy to clipboardprint?afx_msg LRESULT OnMessage(WPARAM wParam, LPARAM lParam); 
afx_msg LRESULT OnMessage(WPARAM wParam, LPARAM lParam);

         OnMessage()是控制代碼函式,ON_MESSAGE有兩個引數,控制代碼的地址和訊息。例如下面的例子將WM_GETTEXTLENGTH對映到OnGetTextLength():

程式程式碼: view plaincopy to clipboardprint?ON_MESSAGE (WM_GETTEXTLENGTH, OnGetTextLength) 
ON_MESSAGE (WM_GETTEXTLENGTH, OnGetTextLength)
OnGetTextLength的原形為:

view plaincopy to clipboardprint?afx_msg LRESULT OnGetTextLength(WPARAM wParam, LPARAM lParam); 
afx_msg LRESULT OnGetTextLength(WPARAM wParam, LPARAM lParam);

         有得時候,需要在兩個視窗或兩個應用程式間進行通訊,一個簡單的解決方法是採用自定義訊息。為了避免與系統定義的訊息衝突,需要運用一個大於0xBFF的數WM_APP。

程式程式碼: view plaincopy to clipboardprint?#define WM_DELETEALL WM_APP + 0x100  
//...  pYourDialog->SendMessage(WM_DELETEALL, 00); 
#define WM_DELETEALL WM_APP + 0x100
//...pYourDialog->SendMessage(WM_DELETEALL, 00); 

        運用巨集WM_MESSAGE完成訊息對映,將WM_DELETEALL與控制代碼函式OnDeleteAll()繫結。

程式程式碼: view plaincopy to clipboardprint?#define WM_DELETEALL WM_APP + 0x100  
//...  
//Message Map entry:  ON_MESSAGE (WM_DELETEALL, OnDeleteAll) 
//OnDeleteAll is prototyped as   afx_msg LRESULT OnDeleteAll(WPARAM wParam, LPARAM lParam); 
//And is implemented as   LRESULT OnDeleteAll(WPARAM wParam, LPARAM lParam){ 
    //Do What ever you want      return somevalue; 

#define WM_DELETEALL WM_APP + 0x100
//...
//Message Map entry:ON_MESSAGE (WM_DELETEALL, OnDeleteAll)
//OnDeleteAll is prototyped asafx_msg LRESULT OnDeleteAll(WPARAM wParam, LPARAM lParam);
//And is implemented asLRESULT OnDeleteAll(WPARAM wParam, LPARAM lParam){
    //Do What ever you want    return somevalue;
}
     RegisterWindowMessage()用於定義一個新的視窗訊息,必須保證其獨一無二。巨集ON_REGISTERED_MESSAGE表示該訊息。例如:

view plaincopy to clipboardprint?class CMyWnd : public CMyParentWndClass 

public
    CMyWnd(); 
 
    //{{AFX_MSG(CMyWnd)      afx_msg LRESULT OnFind(WPARAM wParam, LPARAM lParam); 
    //}}AFX_MSG  
    DECLARE_MESSAGE_MAP() 
}; 
 
static UINT WM_FIND = RegisterWindowMessage("YOURAPP_FIND_MSG"); 
 
BEGIN_MESSAGE_MAP(CMyWnd, CMyParentWndClass) 
    //{{AFX_MSG_MAP(CMyWnd)          ON_REGISTERED_MESSAGE(WM_FIND, OnFind) 
    //}}AFX_MSG_MAP  END_MESSAGE_MAP() 
class CMyWnd : public CMyParentWndClass
{
public:
    CMyWnd();

    //{{AFX_MSG(CMyWnd)    afx_msg LRESULT OnFind(WPARAM wParam, LPARAM lParam);
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
};

static UINT WM_FIND = RegisterWindowMessage("YOURAPP_FIND_MSG");

BEGIN_MESSAGE_MAP(CMyWnd, CMyParentWndClass)
    //{{AFX_MSG_MAP(CMyWnd)        ON_REGISTERED_MESSAGE(WM_FIND, OnFind)
    //}}AFX_MSG_MAPEND_MESSAGE_MAP()

        用該方式定義的訊息的範圍為0xC000 - 0xFFFF,並採用sendmessage()傳送該訊息。

程式程式碼: view plaincopy to clipboardprint?static UINT WM_FIND = RegisterWindowMessage("YOURAPP_FIND_MSG"); 
//...  pFindWindow->SendMessage(WM_FIND, lParam, wParam); 
static UINT WM_FIND = RegisterWindowMessage("YOURAPP_FIND_MSG");
//...pFindWindow->SendMessage(WM_FIND, lParam, wParam);