PreTranslateMessage作用和使用方法
MFC消息控制流最具特色的地方是CWnd類的虛擬函數(shù)PreTranslateMessage(),通過重載這個(gè)函數(shù),可以改變MFC的消息控制流程,甚至可以作一個(gè)全新的控制流出來。只有穿過消息隊(duì)列的消息才受PreTranslateMessage()影響,采用SendMessage()或其他類似的方式向窗口直接發(fā)送的而不經(jīng)過消息隊(duì)列的消息根本不會(huì)理睬PreTranslateMessage()的存在。
是否調(diào)用TranslateMessage()和DispatchMessage()是由一個(gè)名稱為PreTranslateMessage()函數(shù)的返回值決定的,如果該函數(shù)返回TRUE,則不會(huì)把該消息分發(fā)給窗口函數(shù)處理。
傳給PreTranslateMessage()的消息是未經(jīng)翻譯過的消息,它沒有經(jīng)過TranslateMessage()處理??梢栽谠摵瘮?shù)中使用(pMsg->wParam==VK_RETURN)來攔截回車鍵。wParam中存放的是鍵盤上字符的虛擬碼。
PeekMessage和GetMessage的區(qū)別:
GetMessage在沒有消息的時(shí)候等待消息,cpu當(dāng)然低
PeekMessage沒有消息的時(shí)候立刻返回,所以cpu占用率高。
因?yàn)橛螒虿荒芸?/span>windows消息驅(qū)動(dòng),所以要用PeekMessage();
PretranslateMessage的實(shí)現(xiàn),不得不談到MFC消息循環(huán)的實(shí)現(xiàn)。MFC通過CWinApp類中的Pumpmessage函數(shù)實(shí)現(xiàn)消息循環(huán),但是實(shí)際的消息循環(huán)代碼位于CWinThread中,CWinApp只是從CWinThread繼承過來。其簡化后的代碼大概如下:
2 {
3 _AFX_THREAD_STATE *pState = AfxGetThreadState();
4
5 ::GetMessage(&(pState->m_msgCur), NULL, NULL, NULL))
6
7 if (!AfxPreTranslateMessage(&(pState->m_msgCur)))
8 {
9 ::TranslateMessage(&(pState->m_msgCur));
10 ::DispatchMessage(&(pState->m_msgCur));
11 }
12 return TRUE;
13 }
可以看到,PumpMessage在實(shí)際的TranslateMessage和DispatchMessage發(fā)生之前會(huì)調(diào)用AfxPreTranslateMessage,AfxPreTranslateMessage又會(huì)調(diào)用CWnd::WalkPreTranslateTree(雖然也會(huì)調(diào)用其他函數(shù),但是這個(gè)最為關(guān)鍵),其代碼如下:
2 {
3 ASSERT(hWndStop == NULL || ::IsWindow(hWndStop));
4 ASSERT(pMsg != NULL);
5
6 // walk from the target window up to the hWndStop window checking
7 // if any window wants to translate this message
8
9 for (HWND hWnd = pMsg->hwnd; hWnd != NULL; hWnd = ::GetParent(hWnd))
10 {
11 CWnd* pWnd = CWnd::FromHandlePermanent(hWnd);
12 if (pWnd != NULL)
13 {
14 // target window is a C window
15 if (pWnd->PreTranslateMessage(pMsg))
16 return TRUE; // trapped by target window (eg: accelerators)
17 }
18
19 // got to hWndStop window without interest
20 if (hWnd == hWndStop)
21 break;
22 }
23 return FALSE; // no special processing
24 }
可以看到,代碼還是很直接的。從接受到消息的窗口層層往上遍歷,并調(diào)用PretranslateMessage看是否返回TRUE,是則結(jié)束,否則繼續(xù)。
這里有一個(gè)地方非常關(guān)鍵:CWnd *pWnd = CWnd::FromHandlePermanent(hWnd) 這一句代碼從當(dāng)前AfxModuleThreadState拿到Permanent句柄表,從而找到hWnd對應(yīng)的CWnd MFC中PreTranslateMessage是GetMessage(...)函數(shù)的下一級(jí)操作,即GetMessage(...)從消息隊(duì)列中獲取消息后,交由PreTranslateMessage()處理,若其返回FALSE則再交給TranslateMessage和DispatchMessage處理(進(jìn)入WindowProc);
如果用SendMessage, 則消息直接交到WindowProc處理,所以GetMessage不會(huì)取得SendMessage的消息,當(dāng)然PreTranslateMessage也就不會(huì)被調(diào)用。 [Page]
如果用PostMessage,則消息進(jìn)入消息隊(duì)列,由GetMessage取得,PreTranslateMessage就有機(jī)會(huì)進(jìn)行處理。
windows消息處理機(jī)制是這樣的:
首先系統(tǒng)(也就是windows)把來自硬件(鼠標(biāo),鍵盤等消息)和來自應(yīng)用程序的消息 放到一個(gè)系統(tǒng)消息隊(duì)列中去. 而應(yīng)用程序需要有自己的消息隊(duì)列,也就是線程消息隊(duì)列,每一個(gè)線程有自己的消息隊(duì)列,對于多線程的應(yīng)用程序就有和線程數(shù)目相等的線程消息隊(duì)列.
windows消息隊(duì)列把得到的消息發(fā)送到線程消息隊(duì)列,線程消息隊(duì)列每次取出一條消息發(fā)送到指定窗口,不斷循環(huán)直到程序退出實(shí)現(xiàn)的.這個(gè)循環(huán)就是靠消息環(huán)(while(GetMessage()) TranslateMessage();DispatchMessage();.GetMessage()只是從線程消息中取出一條消息,TranslateMessage()把virtue key消息轉(zhuǎn)化成character消息,如VK_F1會(huì)轉(zhuǎn)化成WM_HELP,而DispatchMessage 則把取出的消息發(fā)送到目的窗口.如果收到WM_CLOSE消息則結(jié)束循環(huán),發(fā)送postqiutmessage(0),處理WM_DESTROY銷毀窗口!
while (GetMessage(&msg, NULL, 0, 0)) //C++ code
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
在 win32 程序中,關(guān)于消息有兩種傳遞方式:
1. MFC 消息, MFC 會(huì)把所有的消息一條條放到一個(gè) AFX_MSGMAP_ENTRY 結(jié)構(gòu)中,形成一個(gè)數(shù)組,該數(shù)組存放了所有的消息和與
它們相關(guān)的參數(shù)。也可以說是放到消息隊(duì)列里去。
2. 采用 SendMessage() 或其他類似的方式向窗口直接發(fā)送的而不經(jīng)過消息隊(duì)列的消息。
這兩種方式中只有第一種(穿過消息隊(duì)列的消息)才受 PreTranslateMessage() 影響,
第二種消息并不會(huì)理睬 PreTranslateMessage() 的存在。
1) 是否調(diào)用 TranslateMessage() 和 DispatchMessage() 是由一個(gè)名稱為 PreTranslateMessage() 函數(shù)的返回值決定的,如果該函數(shù)返回
TRUE ,則不會(huì)把該消息分發(fā)給窗口函數(shù)處理。
2) 傳給 PreTranslateMessage() 的消息是未經(jīng)翻譯過的消息,它沒有經(jīng)過 TranslateMessage() 處理??梢栽谠摵瘮?shù)中使用
(pMsg->wParam==VK_RETURN) 來攔截回車鍵。
3) 在 WindowProc 里不能處理 WM_Char 消息。( WindowProc 函數(shù)見 MFC 消息響應(yīng)機(jī)制一文)
4) SetWindowText 會(huì)發(fā)送 WM_Char 給窗口。
5) PeekMessage 和 GetMessage 的區(qū)別:
6) GetMessage 在沒有消息的時(shí)候等待消息, cpu 當(dāng)然低
7) PeekMessage 沒有消息的時(shí)候立刻返回,所以 cpu 占用率高。因?yàn)橛螒虿荒芸?nbsp;windows 消息驅(qū)動(dòng),所以要用 PeekMessage();
另一篇文章中:
在一個(gè) WIN32 程序中, WINDOWS 會(huì)將消息傳遞給相應(yīng)的窗口。但是消息不是立即就被傳遞給相應(yīng)的窗口,而是會(huì)從整個(gè)程序最頂層
的窗口傳遞到下一級(jí)窗 口,再傳遞到下一級(jí)窗口,直到傳遞給目標(biāo)窗口。在整個(gè)過程中,有些消息,在某些特定的情況下,無法默認(rèn)傳遞到目
標(biāo)窗口的。比如用戶在 EDIT 控件中按下回 車鍵, CANCEL 鍵等,如果 EDIT 窗口之前有對話框窗口,對話框會(huì)默認(rèn)處理回車消息(即響應(yīng)
ONOK 函數(shù),然后關(guān)閉對話框),然后退出消息傳遞。所以 EDIT 會(huì)收不到。要解決這個(gè)問題,可以在 EDIT 窗口之前所有的對話框中重載
PreTranslateMessage 函數(shù),然后在函數(shù)內(nèi)加上:
if (pMsg->message==WM_KEYDOWN && pMsg->wParam==VK_RETURN) // 如果消息類型為WM_KEYDOWN 并且用戶按下的是回車
return FALSE; // 不翻譯消息,直接將消息傳遞下去。具體可查 MSDN 。注意,這里返回值不能為TRUE , TRUE 的意思是翻譯消息后退出消
息傳遞,如此一來雖然也能避開對話框默認(rèn)處理,但是會(huì)退出消息傳遞,這樣 EDIT 控件照樣得不到消息。
如此,就可避開對話框默認(rèn)處理,將消息傳遞下去。注意:只有對話框才會(huì)默認(rèn)處理按下回車,CANCEL 消息,其他控件窗口則不會(huì),所以在其
他窗口中不必重載 PreTranslateMessage 函數(shù),當(dāng)然如果重載了也不會(huì)錯(cuò)。
附:關(guān)于 PreTranslateMessage() 函數(shù)的小程序示例:
3 {
5 if (pMsg->message==WM_KEYDOWN) // 判斷是否有按鍵按下
7 {
9 switch (pMsg->wParam)
11 {
13 case VK_DOWN: // 表示是方向鍵中的向下的鍵
15 //add handle code here
17 break ;
19 case VK_UP: // 表示是方向鍵中的向上的鍵
21 //add handle code here
23 break ;
25 default :
27 break ;
29 }
31 }
33 }
36
37 BOOL CMyDlg::PreTranslateMessage(MSG* pMsg)
38 {
39 // TODO: Add your specialized code here and/or call the base class
41 // 按鍵相應(yīng)
42 if (pMsg->message == WM_KEYDOWN)
43 {
44 if (pMsg->wParam == VK_DOWN)
45 {
46 // 向下鍵按下
47 }
48 else if (pMsg->wParam == VK_RIGHT)
49 {
50 // 向右鍵按下
51 }
52 else if (pMsg->wParam == VK_LEFT)
53 {
54 // 向左鍵按下
55 }
56 else if (pMsg->wParam == VK_UP)
57 {
58 // 向上鍵按下
59 }
60 else if (pMsg->wParam == VK_SHIFT)
61 {
62 //VK_LSHIFT 為左 Shift 鍵按下
63 //Shift 鍵按下
64 }
65 else if (pMsg->wParam == VK_CONTROL)
66 {
67 //Ctrl 鍵按下
68 }
69 else if (pMsg->wParam>=VK_NUMPAD0 && pMsg->wParam<=VK_NUMPAD9)
70 {
71 // 小鍵盤數(shù)字鍵按下
72 }
73 else if (pMsg->wParam>=0x30 && pMsg->wParam<=0x39)
74 {
75 // 數(shù)字鍵按下 ( 我記得不能使用 VK_0)
76 }
77 else if (pMsg->wParam>=0x41 && pMsg->wParam<=0x5A)
78 {
79 // 鍵盤字母鍵按下 ( 我記得不能使用 VK_A)
80 }
81 else if (pMsg->wParam == VK_BACK)
82 {
83 // 退格鍵按下
84 }
85 else if (pMsg->wParam == VK_DELETE)
86 {
87 // 刪除鍵按下
88 }
89 else if (pMsg->wParam == VK_F1)
90 {
91 //F1 鍵按下
92 }
93
94 //return true; // 使消息不再進(jìn)行處理
95 }
96
97 if (pMsg->message == WM_KEYUP)
98 {
99 if (pMsg->wParam == VK_SHIFT)
100 {
101 //Shift 鍵彈起
102 }
103 else if (pMsg->wParam == VK_CONTROL)
104 {
105 //Ctrl 鍵彈起
106 }
107 //return true; // 使消息不再進(jìn)行處理
108 }
109
110 return CDialog::PreTranslateMessage(pMsg);
111 }
112
113 // 同時(shí)按下 ctrl 鍵
114 BOOL CDemo_DevStudioView::PreTranslateMessage(MSG* pMsg) // 根據(jù)鍵盤上的按鍵對圖形進(jìn)行相應(yīng)的操作
116 {
117 if (pMsg->message==256) // 256 有鍵按下, 46 DEL 鍵
118 {
119 switch (pMsg->wParam)
120 {
121 /// 向左鍵被按下
122 case 37:
123 {
124 // 同時(shí)按下了 CTRL 鍵
125 if (::GetKeyState(VK_CONTROL) < 0)
126 {
127 }
128 }
129 }
130 }
131 }