程式核心,輸入焦點,訊息分類,標準的訊息,滑鼠訊息,淺析訊息機制,訊息的組成,誰將收到訊息,未處理的訊息,視窗句柄,示例,訊息佇列,訊息循環,訊息分類,佇列化訊息,非佇列化訊息,函式區別,
程式核心
在Windows中,不同的訊息由應用程式的不同部分進行處理。MFC庫將很多底層的訊息都禁止了,使用戶更加方便、簡易地處理訊息。例如,用戶接收到諸如移動
滑鼠鍵(
WM_MOUSEMOVE)訊息或單擊滑鼠鍵(WM_LRBUTTONDOWN)訊息時不必處理視窗和滑鼠的重畫工作,MFC及應用程式框架會替用戶做這些工作。在使用MFC進行編程時,用戶只需處理一些高層的訊息,例如,“用戶在單擊視窗中的OK按扭”,“用戶現在選中了下拉
列表框中的第五項”等等,這樣就大大減輕了程式設計師的負擔。
輸入焦點
Windows是一個以訊息為導向的系統,應用程式只能被動地等待用戶按鍵的訊息,不能主動地去讀鍵盤的狀態,也就是說,每當鍵盤上有個鍵被按下,系統就會發出一個按鍵訊息給視窗,告訴它某個鍵被按下去了,只要滑鼠移動一下,系統也會發出相應的訊息,並把滑鼠的坐標信息傳給視窗。
Windows可以同時執行許多程式,但鍵盤只有一個,怎么判斷由哪個視窗接收鍵盤及滑鼠的訊息呢?採用“
輸入焦點”(input focus)技術可以解決這個問題。只要某個視窗取得輸入焦點,它不但會被提升到螢幕的最前面,顏色也會有所不同,所有的鍵盤訊息就會導向該視窗,該視窗也成為“
活動視窗”。
視窗如何取得輸入焦點?通常被滑鼠單擊的視窗會得到輸入焦點,除此之外,程式本身也可以利用
SetFocus()來指定哪個視窗擁有輸入焦點。
如果調用某視窗的SetFocus()成員函式,該視窗就可以取得輸入焦點,該函式返回前一個擁有
輸入焦點的視窗。
如果某個視窗的輸入焦點被搶走,
Windows系統就會發出WM_KILLFOCUS訊息給這個失去輸入焦點的視窗,同時還會告訴該視窗下一個取得輸入焦點的視窗的
指針。而獲得輸入焦點的視窗則會收到WM_SETFOCUS訊息。
訊息回響函式分別為:
Afx_msg void OnSetFocus(CWnd* pOldWnd);
其中的參數為得到輸入焦點的視窗的指針。
訊息分類
Windows系統預定義了許多訊息,每個訊息都擁有一個
宏定義,即用形象的字元串來標識訊息,一系列#define 語句將訊息與特定數值聯繫起來,可以在頭檔案WinUser.h中找到這些宏定義,例如
#define WM_PAINT 120
可以在程式中通過訊息名“WM_PAINT”來訪問它。其他訊息如:
#define WM_RBUTTONUP 0x0205
#define WM_RBUTTONDBLCLK 0x0206
#define WM_MBUTTONDOWN 0x0207
#define WM_MBUTTONUP 0x0208
#define WM_MBUTTONDBLCLK 0x0209
系統定義的訊息有不同的前綴,不同的前綴有不同的含義。
標準的訊息
1) 鍵盤訊息
對於視窗而言,來自用戶的按鍵輸入可分為兩類,一類是系統鍵(system key),另一類則是非系統鍵。凡是ALT和其它鍵一同按下的組合稱為“系統鍵”,視窗收到系統鍵之後,會自動地將它解釋成系統事件,或者查閱鍵盤加速表,將系統鍵翻譯成加速表指定的信息。如:
ALT+F4的組合會迫使視窗關閉,“ALT+字母”的組合可能會拉下某個選單。
當用戶按下某個鍵時,Windows系統會先發出
WM_KEYDOWN訊息給視窗,這個訊息的意思是“按鍵被壓下去”。接著Windows系統會發出WM_CHAR給同一個視窗,這個訊息代表的意義是“系統送來某個
字元”,如果用戶放開此鍵,Windows系統會發出WM_KEYUP訊息,表示“按鍵被放開”。如果用戶一直按住某個鍵不放,經過一段時間之後會產生“連發”的效果,造成Windows系統不停地發出WM_KEYDOWN與WM_CHAR訊息。
計算機內部以
ASCII碼的規則來記錄所有的英文字母和數字元號。不過不是鍵盤上每個按鍵都可以對應成ASCII碼中的字元,如大小寫鍵、CTRL鍵、F1到F12鍵等。
每個按鍵都有對應的掃描碼,PC BIOS收到鍵盤的中斷訊息後,會自動將掃描碼翻譯成ASCII碼,但有些控制鍵無法譯成ASCII碼,如Page UP、Page Down等。Windows定義了一套與硬體無關的“
虛擬鍵碼”來表示鍵盤上所有的按鍵,如A鍵就是VK_A、ESC鍵就是VK_ESC、F1鍵是VK_F1、
ALT鍵是VK_MENU等。因為“虛擬鍵碼”定義的規則與硬體無關,所以有些虛擬鍵在通常的鍵盤上根本就找不著。
#define VK_LBUTTON 0x01
#define VK_RBUTTON 0x02
#define VK_CANCEL 0x03
#define VK_MBUTTON 0x04 /* NOT contiguous with L & RBUTTON */
#define VK_BACK 0x08
#define VK_TAB 0x09
#define VK_CLEAR 0x0C
#define VK_RETURN 0x0D
#define VK_SHIFT 0x10
#define VK_CONTROL 0x11
#define VK_MENU 0x12
#define VK_PAUSE 0x13
#define VK_CAPITAL 0x14
#define VK_F1 0x70
#define VK_F2 0x71
#define VK_F3 0x72
#define VK_F4 0x73
#define VK_F5 0x74
#define VK_F6 0x75
#define VK_F7 0x76
#define VK_F8 0x77
#define VK_F9 0x78
#define VK_F10 0x79
#
#define WM_CHAR 0x0102 //
字元訊息
WM_CHAR也稱為鍵盤訊息,如果某視窗擁有
輸入焦點,當用戶在應用程式運行時按下一個鍵時,系統就會產生一個鍵盤訊息WM_CHAR,告訴此視窗鍵盤上哪個鍵被按下了。該訊息的處理函式為OnChar()。具體形式為:
afx_msg void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
各參數含義為:
nRepCnt: 按鍵的重複次數,當用戶按下某個鍵不放時,該參數將持續增加。
nFlag: 用於傳遞按鍵的其它一些信息,如掃描碼,上一次按鍵狀態等。具體如下:
8 此按鍵為擴充按鍵,如F1,F12等
功能鍵,此位元組等於1時為真
9-12 保留
13 此位元組為1表示按下鍵的同時,
ALT鍵也被按住了
14 前一個按鍵狀態。此位元組為1代表信息在按鍵被按下之前就送出來了
15 此位元組為1表示這個按鍵已經被放開了,反之就表示還被按著
WM_KEYDOWN訊息是當用戶按下一個非系統鍵時產生的,非系統鍵就是不按下ALT鍵時的按鍵。
WM_KEYUP 訊息是當用戶釋放一個非系統鍵時產生的。
滑鼠訊息
當滑鼠在某個視窗內移動時,Windows會不斷地發出滑鼠移動訊息WM_MOUSEMOVE,並把滑鼠的最新位置傳給該視窗。如果在視窗的範圍內按下滑鼠左鍵,系統就會發出“按下左鍵”的
WM_LBUTTONDOWN訊息給該視窗,等到用戶放開按鍵後,再發出“放開左鍵”的
WM_LBUTTONUP訊息給該視窗。
滑鼠移動訊息的訊息回響函式為:
其中的參數含義如下:
UINT nFlag:此事件發生時,滑鼠按鍵、鍵盤控制鍵的狀態,可以是以下值的任意組合:
當用戶按下CTRL鍵時,nFlags設定為MK_CONTROL。
當用戶按下滑鼠左鍵時,nFlags設定為MK_LBUTTON。
當用戶按下滑鼠中鍵時,nFlags設定為MK_MBUTTON
淺析訊息機制
Windows系統是一個訊息驅動的OS,什麼是訊息呢?我很難說得清楚,也很難下一個定義,我下面從不同的幾個方面講解一下,希望大家看了後有一點了解。
訊息的組成
一個訊息由一個訊息名稱(UINT),和兩個參數(
WPARAM,
LPARAM)組成。當用戶進行了輸入或是視窗的狀態發生改變時系統都會傳送訊息到某一個視窗。例如當選單轉中之後會有
WM_COMMAND訊息傳送,WPARAM的高字中(
HIWORD(
wParam))是命令的ID號,對選單來講就是選單ID。當然用戶也可以定義自己的訊息名稱,也可以利用自定義訊息來傳送通知和傳送數據。
誰將收到訊息
一個訊息必須由一個視窗接收。在視窗的過程(
WNDPROC)中可以對訊息進行分析,對自己感興趣的訊息進行處理。例如你希望對選單選擇進行處理那么你可以定義對
WM_COMMAND進行處理的代碼,如果希望在視窗中進行
圖形輸出就必須對WM_PAINT進行處理。
未處理的訊息
M$為視窗編寫了默認的視窗過程,這個視窗過程將負責處理那些你不處理訊息。正因為有了這個默認視窗過程我們才可以利用Windows的視窗進行開發而不必過多關注視窗各種訊息的處理。例如視窗在被拖動時會有很多訊息傳送,而我們都可以不予理睬讓系統自己去處理。
視窗句柄
說到訊息就不能不說
視窗句柄,系統通過視窗句柄來在整個系統中唯一標識一個視窗,傳送一個訊息時必須指定一個視窗句柄表明該訊息由那個視窗接收。而每個視窗都會有自己的視窗過程,所以用戶的輸入就會被正確的處理。例如有兩個視窗共用一個視窗過程代碼,你在視窗一上按下滑鼠時訊息就會通過視窗一的句柄被傳送到視窗一而不是視窗二。
示例
LONG yourWndProc(HWND hWnd,UINT uMessageType,WPARAM wP, LPARAM) { switch(uMessageType) {//使用 SWITCH語句將各種訊息分開 case(WM_PAINT): doYourWindow(...);//在視窗需要重新繪製時進行輸出 break; case( WM_LBUTTONDOWN): doYourWork(...);//在滑鼠左鍵被按下時進行處理 break; default: callDefaultWndProc(...);//對於其它情況就讓系統自己處理 break; } } |
接下來談談什麼是訊息機制:系統將會維護一個或多個
訊息佇列,所有產生的訊息都會被放入或是插入佇列中。系統會在佇列中取出每一條訊息,根據訊息的接收句柄而將該訊息傳送給擁有該視窗的程式的
訊息循環。每一個運行的程式都有自己的訊息循環,在循環中得到屬於自己的訊息並根據接收視窗的句柄調用相應的視窗過程。而在沒有訊息時訊息循環就將控制權交給系統所以Windows可以同時進行多個任務。下面的
偽代碼演示了訊息循環的用法:
當該程式沒有訊息通知時getMessage就不會返回,也就不會占用系統的
CPU時間。 圖示訊息投遞模式
在16位的系統中系統中只有一個
訊息佇列,所以系統必須等待當前任務處理訊息後才可以傳送下一訊息到相應程式,如果一個程式陷如
死循環或是耗時操作時系統就會得不到控制權。這種多任務系統也就稱為協同式的多任務系統。Windows3.X就是這種系統。
而32位的系統中每一運行的程式都會有一個訊息佇列,所以系統可以在多個訊息佇列中轉換而不必等待當前程式完成訊息處理就可以得到控制權。這種多任務系統就稱為搶先式的多任務系統。Windows95/NT就是這種系統。
訊息佇列
Windows中有一個系統
訊息佇列,對於每一個正在執行的Windows應用程式,系統為其建立一個“訊息佇列”,即應用程式佇列,用來存放該程式可能創建的各種視窗的訊息。應用程式中含有一段稱作“
訊息循環”的代碼,用來從訊息佇列中檢索這些訊息並把它們分發到相應的
視窗函式中。
訊息循環
Windows為當前執行的每個Windows程式維護一個「
訊息佇列」。在發生輸入事件之後,Windows將事件轉換為一個「訊息」並將訊息放入程式的訊息佇列中。程式通過執行一塊稱之為「
訊息循環」的程式代碼從訊息佇列中取出訊息:
while(GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
msg變數是型態為MSG的結構,型態MSG在WINUSER.H中定義如下:
typedef struct tagMSG
{
HWND hwnd ;
UINT message ;
WPARAM wParam ;
LPARAM lParam ;
DWORD time ;
POINT pt ;
}
MSG, * PMSG ;
POINT數據型態也是一個結構,它在WINDEF.H中定義如下:
typedef struct tagPOINT
{
LONG x ;
LONG y ;
}
POINT, * PPOINT;
DispatchMessage(&msg);又將msg結構回傳給Windows。然後,Windows將該訊息傳送給適當的視窗訊息處理程式,讓它進行處理。這也就是說,Windows將呼叫視窗訊息處理程式。在HELLOWIN中,這個視窗訊息處理程式就是
WndProc函式。處理完訊息之後,WndProc傳回到Windows。此時,Windows還停留在DispatchMessage呼叫中。在結束DispatchMessage呼叫的處理之後,Windows回到HELLOWIN程式中,並且接著從下一個
GetMessage呼叫開始
訊息循環。
訊息分類
訊息能夠被分為「佇列化的」和「非佇列化的」。
佇列化訊息
佇列化的訊息是由Windows放入程式
訊息佇列中的。在程式的
訊息循環中,重新傳回並分配給視窗訊息處理程式。非佇列化的訊息在Windows呼叫視窗時直接送給視窗訊息處理程式。也就是說,佇列化的訊息被「傳送」給訊息佇列,而非佇列化的訊息則「傳送」給視窗訊息處理程式。任何情況下,視窗訊息處理程式都將獲得視窗所有的訊息--包括佇列化的和非佇列化的。視窗訊息處理程式是視窗的「訊息中心」。佇列化訊息基本上是使用者輸入的結果,以擊鍵(如WM_KEYDOWN和WM_KEYUP訊息)、擊鍵產生的
字元(WM_CHAR)、滑鼠移動(WM_MOUSEMOVE)和滑鼠按鈕(WM_LBUTTONDOWN)的形式給出。佇列化訊息還包含時鐘訊息(WM_TIMER)、更新訊息(WM_PAINT)和退出訊息(WM_QUIT)。
非佇列化訊息
函式區別
它們兩者是用於向應用程式傳送訊息的。PostMessagex()將訊息直接加入到應用程式的
訊息佇列中,不等程式返回就退出;而SendMessage()則剛好相反,應用程式處理完此訊息後,它才返回。我想下圖能夠比較好的體現這兩個函式的關係:
兩個函式主要有以下兩個區別:
2.GetMessage會將訊息從佇列中刪除,而PeekMessage可以設定最後一個參數wRemoveMsg來決定是否將訊息保留在佇列中。