?? 見招拆招《windows程序設計》(二) .txt
字號:
注意,窗口消息處理程序的四個參數與MSG結構的前四個字段是相同的。第一個參數hwnd是接收消息的窗口的句柄,它與CreateWindow函數的傳回值相同。對于與HELLOWIN相似的程序(只建立一個窗口),這個參數是程序所知道的唯一窗口句柄。如果程序是依據同一窗口類別(同時也是同一窗口消息處理程序)建立多個窗口,則hwnd標識接收消息的特定窗口。
第二個參數與MSG結構中的message字段相同,它是標識消息的數值。最后兩個參數都是32位的消息參數,提供關于消息的更多信息。這些參數包含每個消息型態的詳細信息。有時消息參數是兩個存放在一起的16位值,而有時消息參數又是一個指向字符串或數據結構的指針。
程序通常不直接呼叫窗口消息處理程序,窗口消息處理程序通常由Windows本身呼叫。通過呼叫SendMessage函數,程序能夠直接呼叫它自己的窗口消息處理程序。我們將在后面的章節討論SendMessage函數。
處理消息
窗口消息處理程序所接受的每個消息均是用一個數值來標識的,也就是傳給窗口消息處理程序的message參數。Windows表頭文件WINUSER.H為每個消息參數定義以「WM」(窗口消息)為前綴開頭的標識符。
一般來說,Windows程序寫作者分支結構來確定窗口消息處理程序接收的是什么消息,以及如何適當地處理它。窗口消息處理程序在處理消息時,必須傳回0。窗口消息處理程序不予處理的所有消息應該被傳給名為DefWindowProc的Windows函數。從DefWindowProc傳回的值必須由窗口消息處理程序傳回。
在HELLOWIN中,WndProc只選擇處理三種消息:WM_CREATE、WM_PAINT和WM_DESTROY。窗口消息處理程序的結構如下:
.if uMsg==WM_CREATE
處理WM_CREATE消息
.elseif uMsg == WM_PAINT
處理WM_PAINT消息
.elseif uMsg == WM_DESTROY
處理WM_DESTROY消息
.else
DefWindowProc (hwnd, iMsg, wParam, lParam) ;
.endif
呼叫DefWindowProc來為窗口消息處理程序不予處理的所有消息提供內定處理,這是很重要的。不然一般動作,如終止程序,將不會正常執行。
播放聲音文件
窗口消息處理程序接收的第一個消息-也是WndProc選擇處理的第一個消息-是WM_CREATE。當Windows在WinMain中處理CreateWindow函數時,WndProc接收這個消息。就是說,在HELLOWIN呼叫CreateWindow時,Windows將做一些它必須做的工作。在這些工作中,Windows呼叫WndProc,將第一個參數設定為窗口句柄,第二個參數設定為WM_CREATE。WndProc處理WM_CREATE消息并將控制傳回給Windows。 Windows然后可以從CreateWindowEx呼叫中傳回到HELLOWIN中,繼續在WinMain中進行下一步的處理。
通常,窗口消息處理程序在WM_CREATE處理期間進行一次窗口初始化。HELLOWIN對這個消息的處理中播放一個名為HELLOWIN.WAV的聲音文件。它使用簡單的PlaySound函數來做到這一點。
PlaySound的第一個參數是聲音文件的名稱(它也可能是在Control Panel的Sounds中定義的一種聲音的別名,或者是一個程序資源)。第二個參數只有當聲音文件是一種資源時才被使用。第三個參數指定一些選項。在這個例子中,我指定第一個參數是一個文件名,并且異步地播放聲音,即PlaySound函數呼叫在聲音文件開始播放時立即傳回,而不會等待它的完成。在這種方法下,程序能夠繼續初始化。
WndProc通過從窗口消息處理程序中傳回0,結束了整個WM_CREATE的處理。
WM_PAINT消息
WndProc處理的第二個消息為WM_PAINT。這個消息在Windows程序設計中是很重要的。當窗口顯示區域的一部分顯示內容或者全部變為「無效」,以致于必須「更新畫面」時,將由這個消息通知程序。
顯示區域的顯示內容怎么會變得無效呢?在最初建立窗口的時候,整個顯示區域都是無效的,因為程序還沒有在窗口上畫什么東西。第一條WM_PAINT消息(通常發生在WinMain中呼叫UpdateWindow時)指示窗口消息處理程序在顯示區域上畫一些東西。
在使用者改變HELLOWIN窗口的大小后,顯示區域的顯示內容重新變得無效。讀者應該還記得,HELLOWIN中wndclass結構的style字段設定為標志CS_HREDRAW和CS_VREDRAW,這樣的格式設定指示Windows,在窗口大小改變后,就把整個窗口顯示內容當成無效。然后,窗口消息處理程序將收到一條WM_PAINT消息。
當使用者將HELLOWIN最小化,然后再次將窗口恢復為以前的大小時,Windows將不會保存顯示區域的內容。在圖形環境下,窗口顯示區域涉及的數據量很大。因此,Windows令窗口無效,窗口消息處理程序接收一條WM_PAINT消息,并自動恢復其窗口的內容。
在移動窗口以致其相互重迭時,Windows不保存一個窗口中被另一個窗口所遮蓋的內容。在這一部分不再被遮蓋之后,它就被標志為無效。窗口消息處理程序接收到一條WM_PAINT消息,以更新窗口的內容。
對WM_PAINT的處理幾乎總是從一個BeginPaint呼叫開始:
invoke BeginPaint,hWin,addr ps
mov hdc,eax
而以一個EndPaint呼叫結束:
invoke EndPaint, hWin,addr ps
在這兩個呼叫中,第一個參數都是程序的窗口句柄,第二個參數是指向型態為PAINTSTRUCT的結構指針。PAINTSTRUCT結構中包含一些窗口消息處理程序,可以用來更新顯示區域的內容。我們將在下一章中討論該結構的各個字段。現在我們只在BeginPaint和EndPaint函數中用到它。
在BeginPaint呼叫中,如果顯示區域的背景還未被刪除,則由Windows來刪除。它使用注冊窗口類別的WNDCLASSEx結構的hbrBackground字段中指定的畫刷來刪除背景。在HELLOWIN中, 這是一個白色備用畫刷。這意味著,Windows將通過把窗口背景設定為白色來刪除窗口背景。BeginPaint呼叫令整個顯示區域有效,并傳回一個「設備內容句柄」。設備內容是指實體輸出設備(如視訊顯示器)及其設備驅動程序。在窗口的顯示區域顯示文字和圖形需要設備內容句柄。但是從BeginPaint傳回的設備內容句柄不能在顯示區域之外繪圖,讀者可以試一試。EndPaint釋放設備內容句柄,使之不再有效。
如果窗口消息處理程序不處理WM_PAINT消息(這是很罕見的),它們必須被傳送給DefWindowProc。DefWindowProc只是依次呼叫BeginPaint和EndPaint,以使顯示區域有效。
呼叫完BeginPaint之后,WndProc接著呼叫GetClientRect:
invoke GetClientRect,hWin,addr rect
第一個參數是程序窗口的句柄。第二個參數是一個指標,指向一個RECT型態的rectangle結構。該結構有四個LONG字段,分別為left、top、right和bottom。GetClientRect將這四個字段設定為窗口顯示區域的尺寸。left和top字段通常設定為0,right和bottom字段設定為顯示區域的寬度和高度(像素點數)。
WndProc除了將該RECT結構指針作為DrawText的第四個參數傳遞外,不再對它做其它處理:
invoke DrawText,hdc,CTEXT("Hello,Windows XP!"),-1,addr rect, DT_SINGLELINE or DT_CENTER or DT_VCENTER
wText可以輸出文字(正如其名字所表明的一樣)。由于該函數要輸出文字,第一個參數是從BeginPaint傳回的設備內容句柄,第二個參數是要輸出的文字,第三個參數是 -1,指示字符串是以字節0終結的。
DrawText最后一個參數是一系列位旗標,它們均在Windows.inc中定義(雖然由于其顯示輸出的效果,使得DrawText像一個GDI函數呼叫,但它確實因為相當高級的畫圖功能而成為User模塊的一部分。)。旗標指示了文字必須顯示在一行上,水平方向和垂直方向都位于第四個參數指定的矩形中央。因此,這個函數呼叫將讓字符串「Hello, Windows XP!」顯示在顯示區域的中央。
一旦顯示區域變得無效(正如在改變大小時所發生的情況一樣),WndProc就接收到一個新的WM_PAINT消息。WndProc通過呼叫GetClientRect取得變化后的窗口大小,并在新窗口的中央顯示文字。
WM_DESTROY消息
WM_DESTROY消息是另一個重要消息。這一個消息指示,Windows正在根據使用者的指示關閉窗口。該消息是使用者單擊Close按鈕或者在程序的系統菜單上選擇 Close時發生的(在本章的后面,我們將詳細討論WM_DESTROY消息是如何生效的)。
HELLOWIN通過呼叫PostQuitMessage以標準方式響應WM_DESTROY消息:
invoke PostQuitMessage,NULL
該函數在程序的消息隊列中插入一個WM_QUIT消息。前面提到過,GetMessage對于除了WM_QUIT之外的從消息隊列中取出的所有消息都傳回非0值。而當GetMessage得到一個WM_QUIT消息時,它傳回0。這將導致WinMain退出消息循環,并終止程序。然后程序執行下面的敘述:
mov eax,msg.wParam
結構的wParam字段是傳遞給PostQuitMessage函數的值(通常是0)。然后return敘述將退出WinMain并終止程序。
Windows 程序設計的難點
即使有了對HELLOWIN的說明,讀者對程序的結構和原理可能仍然覺得神秘。在為傳統環境編寫簡單的C程序時,整個程序可能包含在main函數中。而在HELLOWIN中,WinMain只包含了注冊窗口類別,建立窗口,從消息隊列中取出消息和發送消息所必須的程序代碼。
程序的所有實際動作均在窗口消息處理程序中發生。在HELLOWIN中,這些動作不多,WndProc只是簡單地播放了一個聲音文件并在窗口中顯示一個字符串。但是在后面的章節中,讀者將發現,Windows程序所作的一切,都是響應發送給窗口消息處理程序的消息。這是概念上的主要難點之一,在開始寫作Windows程序之前,必須先搞清楚。
別呼叫我,我會呼叫您
前面我們提到過,程序寫作者已經熟悉了使用操作系統呼叫的做法。例如,C程序寫作者使用fopen函數打開文件。fopen函數最終通過呼叫操作系統來打開文件,這一點問題也沒有。
但是Windows不同,盡管Windows有1000個以上的函數可供程序呼叫,但Windows也呼叫使用者程序,比如前面定義的窗口消息處理程序WndProc。窗口消息處理程序與窗口類別相關,窗口類別是程序呼叫RegisterClass注冊的。依據該類別建立的窗口使用這個窗口消息處理程序來處理窗口的所有消息。Windows通過呼叫窗口消息處理程序對窗口發送消息。
在第一次建立窗口時,Windows呼叫WndProc。在窗口關閉時,Windows也呼叫WndProc。窗口改變大小、移動或者變成圖示時,從菜單中選擇某一項目、挪動滾動條、按下鼠標按鈕或者從鍵盤輸入字符時,以及窗口顯示區域必須被更新時,Windows都要呼叫WndProc。
所有這些WndProc呼叫都以消息的形式進行。在大多數Windows程序中,程序的主要部分都用來處理消息。Windows可以發送給窗口消息處理程序的消息通常都以WM開頭的名字標識,并且都在WINUSER.H表頭文件中定義。
實際上,從程序外呼叫程序內的例程這一種做法,在傳統的程序設計中并非前所未聞。C中的signal函數可以攔截Ctrl-C中斷或操作系統的其它中斷。為MS-DOS編寫的老程序中經常有攔截硬件中斷的程序代碼。
但在Windows中,這種概念擴展為包括一切事件。窗口中發生的一切都以消息的形式傳給窗口消息處理程序。然后,窗口消息處理程序以某種方式響應這個消息,或者將消息傳給DefWindowProc,進行內定處理。
在HELLOWIN中,窗口消息處理程序的wParam和lParam參數除了作為傳遞給DefWindowProc的參數外,不再有其它用處。這些參數給出了關于消息的其它信息,參數的含義與具體消息相關。
讓我們來看一個例子。一旦窗口的顯示區域大小發生了改變,Windows就呼叫窗口的窗口消息處理程序。窗口消息處理程序的hwnd參數是改變大小的窗口的句柄(請記住,一個窗口消息處理程序能處理依據同一個窗口類別建立的多個窗口的消息。參數hwnd讓窗口消息處理程序知道是哪個窗口在接收消息)。參數message是WM_SIZE。消息WM_SIZE的參數wParam的值是SIZE_RESTORED、SIZE_MINIMIZED、SIZE_MAXIMIZED、SIZE_MAXSHOW或SIZE_MAXHIDE (在WINUSER.H表頭文件中分別定義為數字0到4)。也就是說,參數wParam表明窗口是非最小化還是非最大化,是最小化、最大化,還是隱藏。
lParam參數包含了新窗口的大小,新寬度和新高度均為16位值,合在一起成為32位的lParam。
有時候,DefWindowProc處理完消息后會產生其它的消息。例如,假設使用者執行HELLOWIN,并且使用者最終單擊了 Close按鈕,或者假設用鍵盤或鼠標從系統菜單中選擇了 Close, DefWindowProc處理這一鍵盤或者鼠標輸入,在檢測到使用者選擇了Close選項之后,它給窗口消息處理程序發送一條WM_SYSCOMMAND消息。WndProc將這個消息傳給DefWindowProc。DefWindowProc給窗口消息處理程序發送一條WM_CLOSE消息來響應之。WndProc再次將它傳給DefWindowProc。DestroyWindow呼叫DestroyWindow來響應這條WM_CLOSE消息。DestroyWindow導致Windows給窗口消息處理程序發送一條WM_DESTROY消息。WndProc再呼叫PostQuitMessage,將一條WM_QUIT消息放入消息隊列中,以此來響應此消息。這個消息導致WinMain中的消息循環終止,然后程序結束。
隊列化消息與非隊列化消息
我們已經談到過,Windows給窗口發送消息,這意味著Windows呼叫窗口消息處理程序。但是,Windows程序也有一個消息循環,它呼叫GetMessage從消息隊列中取出消息,并且呼叫DispatchMessage將消息發送給窗口消息處理程序。
那么,Windows程序是依次等待消息(類似于普通程序中相同的鍵盤輸入),然后將消息送到某地方去的嗎?或者,它是直接從程序外面接收消息的嗎?實際上,兩種情況都存在。
消息能夠被分為「隊列化的」和「非隊列化的」。隊列化的消息是由Windows放入程序消息隊列中的。在程序的消息循環中,重新傳回并分配給窗口消息處理程序。非隊列化的消息在Windows呼叫窗口時直接送給窗口消息處理程序。也就是說,隊列化的消息被「發送」給消息隊列,而非隊列化的消息則「發送」給窗口消息處理程序。任何情況下,窗口消息處理程序都將獲得窗口所有的消息--包括隊列化的和非隊列化的。窗口消息處理程序是窗口的「消息中心」。
隊列化消息基本上是使用者輸入的結果,以擊鍵(如WM_KEYDOWN和WM_KEYUP消息)、擊鍵產生的字符(WM_CHAR)、鼠標移動(WM_MOUSEMOVE)和鼠標按鈕(WM_LBUTTONDOWN)的形式給出。隊列化消息還包含時鐘消息(WM_TIMER)、更新消息(WM_PAINT)和退出消息(WM_QUIT)。
非隊列化消息則是其它消息。在許多情況下,非隊列化消息來自呼叫特定的Windows函數。例如,當WinMain呼叫CreateWindow時,Windows將建立窗口并在處理中給窗口消息處理程序發送一個WM_CREATE消息。當WinMain呼叫ShowWindow時,Windows將給窗口消息處理程序發送WM_SIZE和WM_SHOWWINDOW消息。當WinMain呼叫UpdateWindow時,Windows將給窗口消息處理程序發送WM_PAINT消息。鍵盤或鼠標輸入時發出的隊列化消息信號,也能在非隊列化消息中出現。例如,用鍵盤或鼠標選擇了一個菜單項時,鍵盤或鼠標消息就是隊列化的,而說明菜單項已選中的WM_COMMAND消息則可能就是非隊列化的。
這一過程顯然很復雜,但幸運的是,其中的大部分是由Windows解決的,不關我們的程序的事。從窗口消息處理程序的角度來看,這些消息是以一種有序的、同步的方式進出的。窗口消息處理程序可以處理它們,也可以不處理。
當我說消息是以一種有序的同步的方式進出時,我是說首先消息與硬件的中斷不同。在一個窗口消息處理程序中處理消息時,程序不會被其它消息突然中斷。
雖然Windows程序可以多線程執行,但每個執行緒的消息隊列只為窗口消息處理程序在該執行緒中執行的窗口處理消息。換句話說,消息循環和窗口消息處理程序不是并發執行的。當一個消息循環從其消息隊列中接收一個消息,然后呼叫DispatchMessage將消息發送給窗口消息處理程序時,直到窗口消息處理程序將控制傳回給Windows,DispatchMessage才能結束執行。
當然,窗口消息處理程序能呼叫給窗口消息處理程序發送另一個消息的函數。這時,窗口消息處理程序必須在函數呼叫傳回之前完成對第二個消息的處理。那時窗口消息處理程序將處理最初的消息。例如,當窗口過程調用UpdateWindow時,Windows將呼叫窗口消息處理程序來處理WM_PAINT消息。窗口消息處理程序處理WM_PAINT消息結束以后,UpdateWindow呼叫將把控制傳回給窗口消息處理程序。
這也就是說窗口消息處理程序必須是可重入。在大多數情況下,這不會帶來問題,但是程序寫作者應該意識到這一點。例如,假設您在窗口消息處理程序中處理一個消息時設置了一個靜態變量,然后呼叫了一個Windows函數。在這個函數傳回時,您還能保證那個變數的值還是原來那個嗎?難說--很可能您呼叫的Windows函數產生了另外一個消息,并且窗口消息處理程序在處理這個消息時改變了該變量的值。這也是在編譯Windows程序時,有些編譯最佳化選項必須關閉的原因之一。
在許多情況下,窗口消息處理程序必須保存它從消息中取得的信息,并在處理另一個消息時使用這些信息。這些信息可以儲存在窗口的靜態(static)變量或整體變量中。
當然,讀者將在下面幾章對此有一個更清楚的了解,因為窗口消息處理程序將處理更多的消息。
行動迅速
Windows 98和Windows NT/XP都是優先權式的多任務環境。這意味著當一個程序在進行一項長時間工作時,Windows可以允許使用者將控制切換到另一個程序中。這是一件好事,也是現在的Windows優越于以前16位Windows的地方。
然而,由于Windows設計的方式,這種優先權式多任務并不總是以您希望的樣子工作。例如,假設您的程序花費一分鐘左右來處理某一個消息。是的,使用者可以將控制切換到另一個程序,但是卻無法對您的程序進行任何動作。使用者無法移動您的程序窗口、縮放它、最小化、關閉它、什么都不能做。這是因為您的窗口消息處理程序正忙于進行一項長時間的作業。表面上并不是窗口消息處理程序在執行它自己的移動和縮放操作,但實際上確實是它在做。這就是DefWindowProc部分的工作,它必
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -