MFC訊息機制---訊息對映
Win32程式訊息流動
在講MFC訊息機制之前,先來介紹一下Win32程式的訊息處理。
Win32程式的編寫流程一般就分為:註冊視窗,建立視窗,顯示視窗,更新視窗,
訊息迴圈,訊息處理。
Win32程式執行起來後,會在如下程式碼進行訊息迴圈:
while(GetMessage(&msg...)) {//獲取訊息
TranslateMessage(...);//翻譯訊息
DispatchMessage(...);//派發訊息
}
GetMessage獲取的訊息來自兩種途徑:
1. 系統訊息佇列:滑鼠鍵盤等訊息進入系統訊息佇列。
2. 使用者訊息佇列:使用者程式觸發的事件訊息
DispatchMessage會把訊息派發到訊息處理函式WndProc由我們處理
其中,我們可以通過PostMessage函式將訊息投送至使用者訊息佇列,通過SendMessage將訊息直接投遞至WndProc函式處理。
MFC訊息對映
(下面我們用模擬的方式講解MFC的訊息對映,相關的巨集與函式與MFC中不完全一樣,我去掉了與訊息對映無關的東西,這裡我們只關注訊息對映)
Windows 程式靠訊息的流動而維護生命。我們已經在上面看到了訊息的一般處理方式,
也就是在視窗函式中藉著一個大大的switch/case 比對動作,判別訊息再呼叫對應的處理
例程。為了讓大大的switch/case 比對動作簡化,也讓程式程式碼更模組化一些,MFC
提供了一個訊息對映表作法,把訊息和其處理例程關聯起來。
當我們的類別庫成立,如果其中與訊息有關的類別(姑且叫作「訊息標的類別」好了,
在MFC 之中就是CCmdTarget)都是一條鞭式地繼承,我們應該為每一個「訊息標的類
別」準備一個訊息對映表,並且將基礎類別與衍生類別之訊息對映表串接起來。然後,
當視窗函式做訊息的比對時,我們就可以想辦法導引它沿著這條路走過去,
但是,MFC 之中用來處理訊息的C++ 類別,並不呈單鞭發展。作為application framework
的重要架構之一的document/view,也具有處理訊息的能力(你現在可能還不清楚什麼是
document/view,沒有關係)。因此,訊息藉以攀爬的路線應該有橫流的機會。
訊息如何流動,我們暫時先不管。是直線前進,或是中途換跑道,我們都暫時不管,我們先
把這個攀爬路線網講明白。這整個攀爬路線網就是所謂的訊息對映表(Message Map);說它是一張地圖,當然也沒有錯。將訊息與表格中的元素比對,然後呼叫對應的處理例程,這種動作我們也稱之為訊息對映(Message Mapping)。
訊息對映表
AFX_MSGMAP和AFX_MSGMAP_ENTRY
訊息對映表的建立用到了兩個重要的資料結構:
struct AFX_MSGMAP
{
AFX_MSGMAP* pBaseMessageMap;
AFX_MSGMAP_ENTRY* lpEntries;
};
AFX_MSGMAP可以理解為訊息對映表的一個節點。其中pBaseMessageMap成員變數儲存上一個節點的地址,lpEntries為訊息對映表節點的資料,它將指向一個數組首地址,陣列中每一個元素為AFX_MSGMAP_ENTRY,這時大家可能已經猜到了,AFX_MSGMAP_ENTRY這個結構體肯定有成員變數為訊息ID和訊息處理函式,這樣就實現了訊息和處理函式的對映。
struct AFX_MSGMAP_ENTRY // MFC 4.0 format
{
UINT nMessage; // windows message
UINT nCode; // control code or WM_NOTIFYcode
UINT nID; // control ID (or 0 for windowsmessages)
UINT nLastID; // used for entriesspecifying a range of control id's
UINT nSig; // signature type (action) orpointer to message #
AFX_PMSG pfn; // routine to call (orspecial value)
};
AFX_MSGMAP_ENTRY結構包含了
一個訊息的所有相關資訊,其中
nMessage為Windows訊息的ID號
nCode為控制訊息的通知碼
nID為Windows控制訊息的ID
nLastID表示如果是一個指定範圍的訊息被對映的話,nLastID用來表示它的範圍。
nSig表示訊息的動作標識
AFX_PMSG pfn 它實際上是一個指向和該訊息相應的執行函式的指標。
AFX_PMSG定義為:
typedef void (CCmdTarget::*AFX_PMSG)(void);
重要的巨集
有了上面兩個結構體,我們可以在每個類中宣告一個AFX_MSGMAP_ENTRY陣列,用來儲存該類要接收的訊息和該類中的處理函式的對映關係,然後再宣告一個AFX_MSGMAP成員變數,用來指向這個陣列和該類的父類的AFX_MSGMAP成員變數,這樣就把每個類串起來了。
上面說的這些工作,都由這幾個巨集來完成:
DECLARE_MESSAGE_MAP和BEGIN_MESSAGE_MAP、END_MESSAGE_MAP
我們來這樣定義巨集:
#defineDECLARE_MESSAGE_MAP() \
staticAFX_MSGMAP_ENTRY _messageEntries[]; \
static AFX_MSGMAPmessageMap; \
virtual AFX_MSGMAP*GetMessageMap() const;
於是,DECLARE_MESSAGE_MAP就相當於聲明瞭這樣一個數據結構:
這個資料結構的內容填塞工作由三個巨集完成:
#define BEGIN_MESSAGE_MAP(theClass,baseClass) \
AFX_MSGMAP* theClass::GetMessageMap() const\
{ return &theClass::messageMap; } \
AFX_MSGMAP theClass::messageMap = \
{ &(baseClass::messageMap), \
(AFX_MSGMAP_ENTRY*)&(theClass::_messageEntries) }; \
AFX_MSGMAP_ENTRYtheClass::_messageEntries[] = \
{
#define ON_COMMAND(id, memberFxn) \
{ WM_COMMAND, 0, (WORD)id, (WORD)id,AfxSig_vv, (AFX_PMSG)memberFxn },
#define END_MESSAGE_MAP() \
{ 0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } \
};
其中的AfxSig_end 定義為:
enum AfxSig
{
AfxSig_end = 0, // [marks end of messagemap]
AfxSig_vv,
};
注:AfxSig_xx 用來描述訊息處理例程memberFxn 的型別(引數與回返值)。
END_MESSAGE_MAP中有一個{ 0, 0,0, 0, AfxSig_end, (AFX_PMSG)0 },它的作用是在訊息進行匹配的時候,作為匹配結束標識。
我們以CView 為例來演示一下這幾個巨集的用法:
// in header file
class CView : public CWnd
{
public:
...
DECLARE_MESSAGE_MAP()
};
// in implementation file
#define CViewid 122
...
BEGIN_MESSAGE_MAP(CView, CWnd)
ON_COMMAND(CViewid, 0)
END_MESSAGE_MAP()<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> </span>
將巨集展開我們就清晰了:
// in header file
class CView : public CWnd
{
public:
...
static AFX_MSGMAP_ENTRY _messageEntries[];
static AFX_MSGMAP messageMap;
virtual AFX_MSGMAP* GetMessageMap() const;
};
// in implementation file
AFX_MSGMAP* CView::GetMessageMap() const
{ return &CView::messageMap; }
AFX_MSGMAP CView::messageMap =
{ &(CWnd::messageMap),
(AFX_MSGMAP_ENTRY*)&(CView::_messageEntries) };
AFX_MSGMAP_ENTRY CView::_messageEntries[] =
{
{ WM_COMMAND, 0, (WORD)122, (WORD)122, 1,(AFX_PMSG)0 },
{ 0, 0, 0, 0, 0, (AFX_PMSG)0 }
};
用圖表示為:
只要在每個我們想處理訊息的類中增加這幾個巨集,我們的訊息就可以很方便的順著基類向上傳遞了。
普通訊息(COMMAND訊息會有橫向傳遞)的傳遞的過程就是從子類找尋匹配的處理函式,沒有則向上傳遞去父類找尋,我們可以寫一個這樣的函式(實際這個函式在CCmdTarget類中的OnCmdMsg函式中有,用來找尋匹配COMMAND訊息;在CWnd::WindowProc中也有,用來找尋匹配非普通訊息)
//傳遞一個AFX_MSGMAP,它會縱向傳遞
void MsgMapPrinting(AFX_MSGMAP*pMessageMap)
{
//縱向傳遞,每到達一層,呼叫printlpEntries去找尋匹配
for(; pMessageMap != NULL; pMessageMap =pMessageMap->pBaseMessageMap)
{
AFX_MSGMAP_ENTRY* lpEntry =pMessageMap->lpEntries;
printlpEntries(lpEntry);//這裡我們不模擬找尋匹配,只是輸出一個值,表示我們來過
}
}
void printlpEntries(AFX_MSGMAP_ENTRY*lpEntry)
{
struct {
int classid;
char* classname;
} classinfo[] = {
CCmdTargetid, "CCmdTarget",
CWinThreadid, "CWinThread",
CWinAppid, "CWinApp",
CMyWinAppid, "CMyWinApp",
CWndid, "CWnd",
CFrameWndid, "CFrameWnd",
CMyFrameWndid, "CMyFrameWnd",
CViewid, "CView",
CMyViewid, "CMyView",
CDocumentid, "CDocument",
CMyDocid, "CMyDoc",
0, " "
};
for (int i=0; classinfo[i].classid != 0;i++)
{
if (classinfo[i].classid ==lpEntry->nID)
{
cout << lpEntry->nID <<" ";
cout << classinfo[i].classname<< endl;
break;
}
}
}