編輯本段簡介消息,就是指Windows發(fā)出的一個通知,告訴應(yīng)用程序某個事情發(fā)生了。例如,單擊鼠標、改變窗口尺寸、按下鍵盤上的一個鍵都會使Windows發(fā)送一個消息給應(yīng)用程序。消息本身是作為一個記錄傳遞給應(yīng)用程序的,這個記錄(一般在 C/Java/匯編 中稱為“結(jié)構(gòu)體”)中包含了消息的類型以及其他信息。例如,對單擊鼠標所產(chǎn)生的消息來說,這個記錄(結(jié)構(gòu)體)中包含了單擊鼠標的消息號(WM_LBUTTONDOWN)、單擊鼠標時的坐標(由X,Y值連接而成的一個32位整數(shù))。這個記錄類型叫做TMsg。 在Delphi的Windows單元中是這樣聲明的: type TMsg = packed record hwnd: HWND; / /窗口句柄 message: UINT; / /消息常量標識符 wParam: WPARAM ; // 16位消息的特定附加信息 lParam: LPARAM ; // 32位消息的特定附加信息 time: DWORD; / /消息創(chuàng)建時的時間 pt: TPoint; / /消息創(chuàng)建時的鼠標位置 end ; 其中TPoint定義是: TPoint= record x:integer; y:integer; end; 在C語言中的定義是: typedef struct Msg { HWND hwnd; / /窗口句柄 UINT message; / /消息常量標識符 WPARAM wParam; // 32位消息的特定附加信息 LPARAM lParam; // 32位消息的特定附加信息 DWORD time; / /消息創(chuàng)建時的時間 TPoint pt; / /消息創(chuàng)建時的鼠標位置 } TMsg; typedef struct TPoint { int x; int y; }TPoint; 編輯本段消息內(nèi)容hwnd 32位的窗口句柄。窗口可以是任何類型的屏幕對象,因為Win32能夠維護大多數(shù)可視對象的句柄(窗口、對話框、按鈕、編輯框等)。 message 用于區(qū)別其他消息的常量值,這些常量可以是Windows單元中預(yù)定義的常量,也可以是自定義的常量。 wParam 通常是一個與消息有關(guān)的常量值,也可能是窗口或控件的句柄。 lParam 通常是一個指向內(nèi)存中數(shù)據(jù)的指針。 由于WParam、lParam和Pointer都是32位的,即等同于DWORD。因此,它們之間可以相互轉(zhuǎn)換。 編輯本段消息系統(tǒng)Windows的消息系統(tǒng)是由3個部分組成的: 消息隊列。Windows能夠為所有的應(yīng)用程序維護一個消息隊列。應(yīng)用程序必須從消息隊列中獲取 消息,然后分派給某個窗口。 消息循環(huán)。通過這個循環(huán)機制應(yīng)用程序從消息隊列中檢索消息,再把它分派給適當?shù)拇翱?,?/p> 后繼續(xù)從消息隊列中檢索下一條消息,再分派給適當?shù)拇翱?,依次進行。 窗口過程。每個窗口都有一個窗口過程來接收傳遞給窗口的消息,它的任務(wù)就是獲取消息然后 響應(yīng)它。窗口過程是一個回調(diào)函數(shù);處理了一個消息后,它通常要返回一個值給Windows。 注意回調(diào)函數(shù)是程序中的一種函數(shù),它是由Windows或外部模塊調(diào)用的。 一個消息從產(chǎn)生到被一個窗口響應(yīng),其中有5個步驟: 1) 系統(tǒng)中發(fā)生了某個事件。 2) Windows把這個事件翻譯為消息,然后把它放到消息隊列中。 3)應(yīng)用程序從消息隊列中接收到這個消息,把它存放在TMsg記錄中。 4)應(yīng)用程序把消息傳遞給一個適當?shù)拇翱诘拇翱谶^程。 5) 窗口過程響應(yīng)這個消息并進行處理。 步驟3和4構(gòu)成了應(yīng)用程序的消息循環(huán)。消息循環(huán)往往是Windows應(yīng)用程序的核心,因為消息循環(huán) 使一個應(yīng)用程序能夠響應(yīng)外部的事件。消息循環(huán)的任務(wù)就是從消息隊列中檢索消息,然后把消息傳遞給適當?shù)拇翱?。如果消息隊列中沒有消息,Windows就允許其他應(yīng)用程序處理它們的消息。 Windows操作系統(tǒng)最大的特點就是其圖形化的操作界面,其圖形化界面是建立在其消息處理機制這個基礎(chǔ)之上的。如果不理解Windows消息處理機制,肯定無法深入的理解Windows編程??上Ш芏喑绦騿T對Windows消息只是略有所聞,對其使用知之甚少,更不了解其內(nèi)部實現(xiàn)原理,本文試著一步一步向大家披露我理解的Windows消息機制??梢哉f,掌握了這一部分知識,就是掌握了Windows編程中的神兵利器,靈活運用它,將會極大的提高我們的編程能力。 編輯本段消息實現(xiàn)Windows 消息概述 Windows窗體是怎樣展現(xiàn)在屏幕上的呢?眾所周知,是通過API繪制實現(xiàn)的。Windows操作系統(tǒng)提供了一系列的API函數(shù)來實現(xiàn)界面的繪制功能,例如: DrawText 繪制文字 DrawEdge 繪制邊框 DrawIcon 繪制圖標 BitBlt 繪制位圖 Rectangle 繪制矩形 … 再復(fù)雜的程序界面都是通過這些函數(shù)來實現(xiàn)的。 那什么時候調(diào)用這些函數(shù)呢?顯然我們需要一個控制中心,用來進行“發(fā)號施令”,我們還需要一個命令傳達機制,將命令即時的傳達到目的地。這個控制中心,就是一個動力源,就像一顆心臟,源源不斷地將血液送往各處。這個命令傳達機制就是Windows消息機制,Windows消息就好比是身體中的血液,它是命令傳達的使者。 Windows消息控制中心一般是三層結(jié)構(gòu),其頂端就是Windows內(nèi)核。Windows內(nèi)核維護著一個消息隊列,第二級控制中心從這個消息隊列中獲取屬于自己管轄的消息,后做出處理,有些消息直接處理掉,有些還要發(fā)送給下一級窗體(Window)或控件(Control)。第二級控制中心一般是各Windows應(yīng)用程序的Application對象。第三級控制中心就是Windows窗體對象,每一個窗體都有一個默認的窗體過程,這個過程負責處理各種接收到的消息。如下圖所示: 說明圖(注:windows指windows操作系統(tǒng);窗口:即windows窗口;窗體:包括窗口,以及有句柄的控件;control指控件,控件本身也可能是一個window,也可能不是;Application即應(yīng)用程序,應(yīng)用程序也可能不會用到Windows消息機制,這里我們專門討論有消息循環(huán)的應(yīng)用程序) 消息是以固定的結(jié)構(gòu)傳送給應(yīng)用程序的,結(jié)構(gòu)如下: Public Type MSG hwnd As Long message As Long wParam As Long lParam As Long time As Long pt As POINTAPI End Type 其中hwnd是窗體的句柄,message是一個消息常量,用來表示消息的類型,wParam和lParam都是32位的附加信息,具體表示什么內(nèi)容,要視消息的類型而定,time是消息發(fā)送的時間,pt是消息發(fā)送時鼠標所在的位置。 Windows操作系統(tǒng)中包括以下幾種消息: 1、標準Windows消息: 這種消息以WM_打頭。 2、通知消息 通知消息是針對標準Windows控件的消息。這些控件包括:按鈕(Button)、組合框(ComboBox)、編輯框(TextBox)、列表框(ListBox)、ListView控件、Treeview控件、工具條(Toolbar)、菜單(Menu)等。每種消息以不同的字符串打頭。 3、自定義消息 編程人員還可以自定義消息。 關(guān)于Windows句柄不是每個控件都能接收消息,轉(zhuǎn)發(fā)消息和繪制自身,只有具有句柄(handle)的控件才能做到。有句柄的控件本質(zhì)上都是一個窗體(window),它們可以獨立存在,可以作為其它控件的容器,而沒有句柄的控件,如Label,是不能獨立存在的,只能作為窗口控件的子控件,它不能繪制自身,只能依靠父窗體將它繪制來。 句柄的本質(zhì)是一個系統(tǒng)自動維護的32位的數(shù)值,在整個操作系統(tǒng)的任一時刻,這個數(shù)值是唯一的。但該句柄代表的窗體釋放后,句柄也會被釋放,這個數(shù)值又可能被其它窗體使用。也就是說,句柄的數(shù)值是動態(tài)的,它本身只是一個唯一性標識,操作系統(tǒng)通過句柄來識別和查找它所代表的對象。 然而,并非所有的句柄都是窗體的句柄,Windows系統(tǒng)中還中很多其它類型的句柄,如畫布(hdc)句柄,畫筆句柄,畫刷句柄,應(yīng)用程序句柄(hInstance)等。這種句柄是不能接收消息的。但不管是哪種句柄,都是系統(tǒng)中對象的唯一標識。本文只討論窗體句柄。 那為什么句柄使窗口具有了如此獨特的特性呢?實際是都是由于消息的原因。由于有了句柄,窗體能夠接收消息,也就知道了該什么時候繪制自己,繪制子控件,知道了鼠標在什么時候點擊了窗口的哪個部分,從而作出相應(yīng)的處理。句柄就好像是一個人的身份證,有了它,你就可以從事各種社會活動;否則的話,你要么是一個社會看不到的黑戶,要么跟在別人后面,通過別人來證明你的存在。 消息的傳送1、從消息隊列獲取消息: 可以通過PeekMessage或GetMessage函數(shù)從Windows消息隊列中獲取消息。Windows保存的消息隊列是以線程(Thread)來分組的,也就是說每個線程都有自己的消息隊列。 2、發(fā)送消息 發(fā)送消息到指定窗體一般通過以下兩個函數(shù)完成:SendMessage和PostMessage。兩個函數(shù)的區(qū)別在于:PostMessage函數(shù)只是向線程消息隊列中添加消息,如果添加成功,則返回True,否則返回False,消息是否被處理,或處理的結(jié)果,就不知道了。而SendMessage則有些不同,它并不是把消息加入到隊列里,而是直接翻譯消息和調(diào)用消息處理(線程向自己發(fā)送消息才是這樣),直到消息處理完成后才返回。所以,如果我們希望發(fā)送的消息立即被執(zhí)行,就應(yīng)該調(diào)用SendMessage。 還有一點,就是SendMessage發(fā)送的消息由于不會被加入到消息隊列中(錯:線程向其他線程發(fā)送消息也是追加到其他線程的發(fā)送消息隊列的,即使這兩個線程在同一個進程也是如此),所以通過PeekMessage或GetMessage是不能獲取到由SendMessage發(fā)送的消息。 另外,有些消息用PostMessage不會成功,比如wm_settext。所以不是所有的消息都能夠用PostMessage的。 還有一些其它的發(fā)送消息API函數(shù),如PostThreadMessage,SendMessageCallback,SendMessageTimeout,SendNotifyMessage等。 消息循環(huán)與窗體過程消息循環(huán)是應(yīng)用程序能夠持續(xù)存在的根本原因。如果循環(huán)退出,則應(yīng)用程序就結(jié)束了。 我們來看一看Delphi中封裝的消息循環(huán)是怎樣的: 第一步:程序開始運行(Run) Application.Initialize; //初始化 Application.CreateForm(TForm1, Form1); //創(chuàng)建主窗體 Application.Run; //開始運行,準備進行消息循環(huán) 如果不創(chuàng)建主窗體,應(yīng)用程序同樣可以存在和運行。 第二步:開始調(diào)用消息循環(huán)(HandleMessage) procedure TApplication.Run; begin FRunning := True; try AddExitProc(DoneApplication); if FMainForm <> nil then begin case CmdShow of SW_SHOWMINNOACTIVE: FMainForm.FWindowState := wsMinimized; SW_SHOWMAXIMIZED: MainForm.WindowState := wsMaximized; end; if FShowMainForm then if FMainForm.FWindowState = wsMinimized then Minimize else FMainForm.Visible := True; Repeat //注:循環(huán)開始 try HandleMessage; except HandleException(Self); end; until Terminated; //循環(huán)結(jié)束條件 end; finally FRunning := False; end; end; 第三步:消息循環(huán)中對消息的處理。 procedure TApplication.HandleMessage; var Msg: TMsg; begin if not ProcessMessage(Msg) then Idle(Msg); end; function TApplication.ProcessMessage(var Msg: TMsg): Boolean; var Handled: Boolean; begin Result := False; if PeekMessage(Msg, 0, 0, 0, PM_REMOVE) then begin Result := True; if Msg.Message <> WM_QUIT then begin Handled := False; if Assigned(FOnMessage) then FOnMessage(Msg, Handled); if not IsHintMsg(Msg) and not Handled and not IsMDIMsg(Msg) and not IsKeyMsg(Msg) and not IsDlgMsg(Msg) then begin TranslateMessage(Msg); DispatchMessage(Msg); end; end else FTerminate := True; end; end; 窗體過程實際上是一個回調(diào)函數(shù)。所謂的回調(diào)函數(shù),實際上就是由Windows操作系統(tǒng)或外部程序調(diào)用的函數(shù)?;卣{(diào)函數(shù)一般都有規(guī)定的參數(shù)格式,以地址方式傳遞給調(diào)用者。窗口過程中是Windows操作系統(tǒng)調(diào)用了,在一個窗口創(chuàng)建的時候,在分配窗體句柄的時候就需要傳入回調(diào)函數(shù)地址。那為什么我們平時編程看不到這個回調(diào)函數(shù)呢?這是由于我們的編程工具已經(jīng)為我們生成了默認的窗體過程,這個過程的要做的事情就是判斷不同的消息類型,然后做出不同的處理。例如可以為鍵盤或鼠標輸入生成事件等。 消息與事件事件本質(zhì)上是對消息的封裝,是IDE編程環(huán)境為了簡化編程而提供的有用的工具。這個封裝是在窗體過程中實現(xiàn)的。每種IDE封裝了許多Windows的消息,[1] |
|