|
本文中51黑將向你詳細介紹,在我們天天都要用的windows操作系統里面,一個窗口究竟是怎么產生和工作的.
1. 窗口類參數的意義
要WinMain登記窗口類,首先要填寫一個WNDCLASS結構,其定義如下所示:
typedef struct_WNDCLASSA
{
UINT style ; //窗口類風格
WNDPROC lpfnWndProc ; //指向窗口過程函數的指針
int cbClsExtra ; //窗口類附加數據
int cbWndExtra; //窗口附加數據
HINSTANCE hInstance ; //擁有窗口類的實例句柄
HICON hIcon ; //最小窗口圖標
HCURSOR hCursor ; //窗口內使用的光標
HBRUSH hbrBackground ; //用來著色窗口背景的刷子
LPCSTR lpszMenuName; //指向菜單資源名的指針
LPCSTR lpszClassName ; // 指向窗口類名的指針
}
其中參數的意義如下:
(1)第一個參數:成員style控制窗口的某些重要特性,在WINDOWS.H中定義了一些前綴為CS的常量,在程序中可組合使用這些常量.也可把sytle設為0.本程序中為wc.style=CS_HREDRAW | CS_VREDRAW,它表示當窗口的縱橫坐標發生變化時要重畫整個窗口。你看:無論你怎樣拉動窗口的大小,那行字都會停留在窗口的正中部,而假如把這個參數設為0的話,當改動窗口的大小時,那行字則不一定處于中部了。
(2)第二個參數:lpfnWndProc包括一個指向該窗口類的消息處理函數的指針,此函數稱為窗口過程函數。它將接收Windows發送給窗口的消息,并執行相應的任務。其原型為:
long FAR PASCAL WndProc(HWND,unsigned,WORD,LONG);并且必須在?於x中回調它。WndProc是一個回調函數(見第五節),如果暫時無法理解這個模糊的概念意味著什么,可先放過,等到講消息循環時再詳談。
(3)第三,四個參數:cbWndExtra域指定用本窗口類建立的所有窗口結構分配的額外字節數。當有兩個以上的窗口屬于同一窗口類時,如果想將不同的數據和每個窗口分別相對應。則使用該域很有用。這般來講,你只要把它們設為0就行了,不必過多考慮。
(4)第五個參數:hInstance域標識應用程序的實例hInstance,當然,實例名是可以改變的。wc.hInstance= hInstance ;這一成員可使Windows連接到正確的程序。
(5)第六個參數:成員hIcon被設置成應用程序所使用圖標的句柄,圖標是將應用程序最小化時出現在任務欄里的的圖標,用以表示程序仍駐留在內存中。Windows提供了一些默認圖標,我們也可定義自己的圖標,VC里面專有一個制作圖標的工具。
(6)第七個參數: hCursor域定義該窗口產生的光標形狀。LoadCursor可返回固有光標句柄或者應用程序定義的光標句柄。IDC_ARROW表示箭頭光標.
(7)第八個參數:wc.hbrBackground域決定Windows用于著色窗口背景的刷子顏色,函數GetStockObject返回窗口的顏色,本程序中返回的是白色,你也可以把它改變為紅色等其他顏色.試試看
(8)第九個參數:lpszMenuName用來指定菜單名,本程序中沒有定義菜單,所以為NULL。
(9)第十個參數:lpszClassName指定了本窗口的類名。
注冊窗口類如下:
if (!RegisterClass (&wc)) {
MessageBox (NULL, TEXT ("This program requiresWindows NT!"),
szAppName,MB_ICONERROR) ;
return 0 ;
}
-------------------2.創建顯示更新窗口-------------------------
注冊窗口類后,就可以創建窗口了,本程序中創建窗口的有關語句如下:
hwnd =CreateWindow (szAppName, //window class name
TEXT("歡迎你的到來!"), //window caption
WS_OVERLAPPEDWINDOW, //window style
CW_USEDEFAULT, //initial x position
CW_USEDEFAULT, //initial y position
CW_USEDEFAULT, //initial x size
CW_USEDEFAULT, //initial y size
NULL, //parent window handle
NULL, //window menu handle
hInstance, //program instance handle
NULL); //creation parameters
參數1:登記的窗口類名,這個類名剛才咱們在注冊窗口時已經定義過了。
參數2:用來表明窗口的標題。
參數3: 用來表明窗口的風格,如有無最大化,最小化按紐啊什么的。
參數4,5: 用來表明程序運行后窗口在屏幕中的坐標值。
參數6,7: 用來表明窗口初始化時(即程序初運行時)窗口的大小,即長度與寬度。
參數8: 在創建窗口時可以指定其父窗口,這里沒有父窗口則參數值為0。
參數9: 用以指明窗口的菜單,菜單以后會講,這里暫時為0。
最后一個參數是附加數據,一般都是0。
CreateWindow()的返回值是已經創建的窗口的句柄,應用程序使用這個句柄來引用該窗口。如果返回值為0,就應該終止該程序,因為可能某個地方出錯了。如果一個程序創建了多個窗口,則每個窗口都有各自不同的句柄.
API函數CreateWindow創建完窗口后,要想把它顯示出現,還必須調用另一個API函數ShowWindows.形式為:ShowWindow(hwnd, iCmdShow);
其第一個參數是窗口句柄,告訴ShowWindow()顯示哪一個窗口,而第二個參數則告訴它如何顯示這個窗口:最小化(SW_MINIMIZE),普通(SW_SHOWNORMAL),還是最大化(SW_SHOWMAXIMIZED)。WinMain在創建完窗口后就調用ShowWindow函數,并把iCmdShow參數傳送給這個窗口。你可把iCmdShow改變為這些參數試試。
WinMain()調用完ShowWindow后,還需要調用函數UpdateWindow(hwnd),最終把窗口顯示了出來。調用函數UpdateWindow將產生一個WM_PAINT消息,這個消息將使窗口重畫,即使窗口得到更新.
-----------------3.消息循環------------------------
主窗口顯示出來了,WinMain就開始處理消息了,怎么做的呢?
Windows為每個正在運行的應用程序都保持一個消息隊列。當你按下鼠標或者鍵盤時,Windows并不是把這個輸入事件直接送給應用程序,而是將輸入的事件先翻譯成一個消息,然后把這個消息放入到這個應用程序的消息隊列中去。應用程序又是怎么來接收這個消息呢?這就講講消息循環了。
應用程序的WinMain函數通過執行一段代碼從她的隊列中來檢索Windows送往她的消息。然后WinMain就把這些消息分配給相應的窗口函數以便處理它們,這段代碼是一段循環代碼,故稱為"消息循環"。這段循環代碼是什么呢?好,往下看:
在咱們的第二只小板凳中,這段代碼就是:
......
MSG msg; //定義消息名
while(GetMessage (&msg, NULL, 0,0))
{
TranslateMessage(&msg) ; //翻譯消息
DispatchMessage(&msg) ; //撤去消息
}
returnmsg.wParam ;
MSG結構在頭文件中定義如下:
typedef structtagMSG
{
HWND hwnd;
UINTmessage;
WPARAMwParam;
LPARAMlParam;
DWORD time;
POINT pt;
} MSG,*PMSG;
MSG數據成員意義如下:
參數1:hwnd是消息要發送到的那個窗口的句柄,這個窗口就是咱們用CreateWindows函數創建的那一個。如果是在一個有多個窗口的應用程序中,用這個參數就可決定讓哪個窗口接收消息。
參數2:message是一個數字,它唯一標識了一種消息類型。每種消息類型都在Windows文件中定義了,這些常量都以WM_開始后面帶一些描述了消息特性的名稱。比如說當應用程序退出時,Windows就向應用程序發送一條WM_QUIT消息。
參數3:一個32位的消息參數,這個值的確切意義取決于消息本身。
參數4:同上。
參數5:消息放入消息隊列中的時間,在這個域中寫入的并不是日期,而是從Windows啟動后所測量的時間值。Windows用這個域來使用消息保持正確的順序。
參數6:消息放入消息隊列時的鼠標坐標.
消息循環以GetMessage調用開始,它從消息隊列中取出一個消息:
GetMessage(&msg,NULL,0,0),第一個參數是要接收消息的MSG結構的地址,第二個參數表示窗口句柄,NULL則表示要獲取該應用程序創建的所有窗口的消息;第三,四參數指定消息范圍。后面三個參數被設置為默認值,這就是說你打算接收發送到屬于這個應用程序的任何一個窗口的所有消息。在接收到除WM_QUIT之外的任何一個消息后,GetMessage()都返回TRUE。如果GetMessage收到一個WM_QUIT消息,則返回FALSE,如收到其他消息,則返回TRUE。因此,在接收到WM_QUIT之前,帶有GetMessage()的消息循環可以一直循環下去。只有當收到的消息是WM_QUIT時,GetMessage才返回FALSE,結束消息循環,從而終止應用程序。均為NULL時就表示獲取所有消息。
消息用GetMessage讀入后(注意這個消息可不是WM_QUIT消息),它首先要經過函數TranslateMessage()進行翻譯,這個函數會轉換成一些鍵盤消息,它檢索匹配的WM_KEYDOWN和WM_KEYUP消息,并為窗口產生相應的ASCII字符消息(WM_CHAR),它包含指定鍵的ANSI字符.但對大多數消息來說它并不起什么作用,所以現在沒有必要考慮它。
下一個函數調用DispatchMessage()要求Windows將消息傳送給在MSG結構中為窗口所指定的窗口過程。我們在講到登記窗口類時曾提到過,登記窗口類時,我們曾指定Windows把函數WindosProc作為咱們這個窗口的窗口過程(就是指處理這個消息的東東)。就是說,Windows會調用函數WindowsProc()來處理這個消息。在WindowProc()處理完消息后,代碼又循環到開始去接收另一個消息,這樣就完成了一個消息循環。
Windows是一種非剝奪式多任務操作系統。只有的應用程序交出CPU控制權后,Windows才能把控制權交給其他應用程序。當GetMessage函數找不到等待應用程序處理的消息時,自動交出控制權,Windows把CPU的控制權交給其他等待控制權的應用程序。由于每個應用程序都有一個消息循環,這種隱式交出控制權的方式保證合并各個應用程序共享控制權。一旦發往該應用程序的消息到達應用程序隊列,即開始執行GetMessage語句的下一條語句。
當WinMain函數把控制返回到Windows時,應用程序就終止了。應用程序的啟動消息循環前要檢查引導出消息循環的每一步,以確保每個窗口已注冊,每個窗口都已創建。如存在一個錯誤,應用程序應返回控制權,并顯示一條消息。
但是,一旦WinMain函數進入消息循環,終止應用程序的唯一辦法就是使用PostQuitMessage把消息WM_QUIT發送到應用程序隊列。當GetMessage函數檢索到WM_QUIT消息,它就返回NULL,并退出消息外循環。通常,當主窗口正在刪除時(即窗口已接收到一條WM_DESTROY消息),應用程序主窗口的窗口函數就發送一條WM_QUIT消息。
雖然WinMain指定了返回值的數據類型,但Windows并不使用返回值。不過,在調試一應用程序時,返回值地有用的。通常,可使用與標準C程序相同的返回值約定:0表示成功,非0表示出錯。PostQuitMessage函數允許窗口函數指定返回值,這個值復制到WM_QUIT消息的wParam參數中。為了的結束消息循環之后返回這個值,我們的第二只小板凳中使用了以下語句:
return msg.wParam ; //表示從PostQuitMessage返回的值
例如:當Windows自身終止時,它會撤消每個窗口,但不把控制返回給應用程序的消息循環,這意味著消息循環將永遠不會檢索到WM_QUIT消息,并且的循環之后的語句也不能再執行。Windows的終止前的確發送一消息給每個應用程序,因而標準C程序通常會的結束前清理現場并釋放資源,但Windows應用程序必須隨每個窗口的撤消而被清除,否則會丟失一些數據。
---------------------4.窗口過程,窗口過程函數-------------------------------------
如前所述,函數GetMessage負責從應用程序的消息隊列中取出消息,而函數DispatchMessage()要求Windows將消息傳送給在MSG結構中為窗口所指定的窗口過程。然后出臺的就是這個窗口過程了,這個窗口過程的任務是干什么呢?就是最終用來處理消息的,就是消息的處理器而已,那么這個函數就是WindowProc,在VisualC++6.0中按F1啟動MSDN,按下面這個路徑走下來:
PlatFormSDK-->UserInterface services-->Windowsuser Interface-->Windowing-->WindowProcedures-->WindowProcedureReference-->WindowsProcedureFunctions-->WindowProc
啊,太累了,不過我們終于的MSDN中找到了這個函數,前幾次我講解這些API函數的時候,都是的知道的情況下搜索出來的,所以沒有詳細給出每個函數的具體位置,而這次我卻是一點點去找的,還好,沒被累死,體會到MSDN的龐大了吧,不過我用的是MSDN2000,是D版的,三張光盤裝。你用的MSDN如果按這個路徑走下去的話,可能會找不到,不過我想大致也是在這個位置了,找找看!!!
LRESULT CALLBACKWindowProc
(
HWNDhwnd, // handle towindow
UINTuMsg, // messageidentifier
WPARAMwParam, // first messageparameter
LPARAMlParam // second messageparameter
);
這個函數這里被我們稱為WndProc.
下面講解:
不知你注意到了沒有,這個函數的參數與剛剛提到的GetMessage調用把返回的MSG結構的前四個成員相同。如果消息處理成功,WindowProc的返回值為0.
Windows的啟動應用程序時,先調用WinMain函數,然后調用窗口過程,注意:在我們的這個程序中,只有一個窗口過程,實際上,也許有不止一個的窗口過程。例如,每一個不同的窗口類都有一個與之相對應的窗口過程。無論Windows何時想傳遞一個消息到一窗口,都將調用相應的窗口過程。當Windows從環境,或從另一個應用程序,或從用戶的應用程序中得到消息時,它將調用窗口過程并將信息傳給此函數?傊翱谶^程函數處理所有傳送到由此窗口類創建的窗口所得到的消息。并且窗口過程有義務處理Windows扔給它的任何消息。我們在學習Windows程序設計的時候,最主要的就是學習這些消息是什么以及是什么意思,它們是怎么工作的。
令我們不解的是,在程序中我們看不出來是哪一個函數在調用窗口過程。它其實是一個回調函數.前面已經提到,Windows把發生的輸入事件轉換成輸入消息放到消息隊列中,而消息循環將它們發送到相應的窗口過程函數,真正的處理是在窗口過程函數中執行的,在Windows中就使用了回調函數來進行這種通信。
回調函數是輸出函數中特殊的一種,它是指那些在Windows環境下直接調用的函數。一個應用程序至少有一個回調函數,因為在應用程序處理消息時,Windows調用回調函數。這種回調函數就是我們前面提到的窗口過程,它對對應于一個活動的窗口,回調函數必須向Windows注冊,Windows實施相應操作即行回調。
每個窗口必須有一個窗口過程與之對應,且Windows直接調用本函數,因此,窗口函數必須采用FARPASCAL調用約定。在我們的第二只小板凳中,我們的窗口函數為WndProc,必須注意這里的函數名必須是前面注冊的窗口類時,向域wc.lpfnWndProc所賦的WndProc。函數WndProc就是前面定義的窗口類所生成的所有窗口的窗口函數。
在我們的這個窗口函數中,WndProc處理了共有兩條消息:WM_PAINT和WM_DESTROY.
窗口函數從Windows中接收消息,這些消息或者是由WinMain函數發送的輸入消息,或者是直接來自Windows的窗口管理消息。窗口過程檢查一條消息,然后根據這些消息執行特定的動作未被處理的消息通過DefWindowProc函數傳回給Windows作缺海上處理。
可以發送窗口函數的消息約有220種,所有窗口消息都以WM_開頭,這些消息在頭文件中被定義為常量。引起Windows調用窗口函數的原因有很多,,如改變窗口大小啊,改變窗口在屏幕上的位置啊什么的。
----------------5.處理消息----------------------------------
窗口過程處理消息通常以switch語句開始,對于它要處理的每一條消息ID都跟有一條case語句。大多數windowsproc都有具有下面形式的內部結構
switch(uMsgId)
{
caseWM_(something):
//這里此消息的處理過程
return0;
caseWM_(something else):
//這里是此消息的處理過程
ruturn0;
default:
//其他消息由這個默認處理函數來處理
returnDefWindowProc(hwnd,uMsgId,wParam,lParam);
}
在處理完消息后,要返回0,這很重要-----它會告訴Windows不必再重試了。對于那些在程序中不準備處理的消息,窗口過程會把它們都扔給DefWindowProc進行缺省處理,而且還要返回那個函數的返回值。在消息傳遞層次中,可以認為DefWindowProc函數是最頂層的函數。這個函數發出WM_SYSCOMMAND消息,由系統執行Windows環境中多數窗口所公用的各種通用操作,例如,畫窗口的非用戶區,更新窗口的正文標題等等等等。
再提示一下,以WM_的消息在Windows頭文件中都被定義成了常量,如WM_QUIT=XXXXXXXXXXX,但我們沒有必要記住這個數值,也不可能記得住,我們只要知道WM_QUIT就OK了。
在第二只小板凳中我們只讓窗口過程處理了兩個消息:一個是WM_PAINT,另一個是WM_DESTROY,先說說第一個消息---WM_PAINT.
關于WM_PAINT: 無論何時Windows要求重畫當前窗口時,都會發該消息。也可以這樣說:無論何時窗口非法,都必須進行重畫。哎呀,什么又是"非法窗口"?什么又是重畫。
在MSDN里面找有關這個方面的內容
Platform SDK-->Graphics and Multimedia Services-->Windows GDI-->Painting and Drawing-->Using the WM_PAINTMessage-----終于找到了。
下面是一大套理論:
讓我們把Windows的屏幕想像成一個桌面,把一個窗口想像成一張紙。當我們把一張紙放到桌面上時,它會蓋住其他的紙,這樣被蓋住的其他紙上的內容都看不到了。但我們只要把這張紙移開,被蓋住的其他紙上的內容就會顯示出來了---這是一個很簡單的道理,誰都明白。
對于我們的屏幕來說,當一個窗口被另一窗口蓋住時,被蓋住的窗口的某些部分就看不到了,我們要想看到被蓋住的窗口的全部面貌,就要把另一個窗口移開,但是當我們移開后,事情卻起了變化-----很可能這個被蓋住的窗口上的信息被擦除了或是丟失了。當窗口中的數據丟失或過期時,窗口就變成非法的了---或者稱為"無效"。于是我們的任務就來了,我們必須考慮怎樣在窗口的信息丟失時"重畫窗口"--使窗口恢復成以前的那個樣子。這也就是我們在這第二只小板凳中調用UpdateWindow的原因。
你忘記了嗎?剛才我們在(三)顯示和更新窗口 中有下面的一些文字:
WinMain()調用完ShowWindow后,還需要調用函數UpdateWindow,最終把窗口顯示了出來。調用函數UpdateWindow將產生一個WM_PAINT消息,這個消息將使窗口重畫,即使窗口得到更新.---這是程序第一次調用了這條消息。
為重新顯示非法區域,Windows就發送WM_PAINT消息實現。要求Windows發送WM_PAINT的情況有改變窗口大小,對話框關閉,使用了UpdateWindows和ScrollWindow函數等。這里注意,Windows并非是消息WM_PAINT的唯一來源,使用InvalidateRect或InvalidateRgn函數也可以產生繪圖窗口的WM_PAINT消息......
通常情況下用BeginPaint()來響應WM_PAINT消息。如果要在沒有WM_PAINT的情況下重畫窗口,必須使用GetDC函數得到顯示緩沖區的句柄。這里面不再擴展。詳細見MDSN。
這個BeginPaint函數會執行準備繪畫所需的所有步驟,包括返回你用于輸入的句柄。結束則是以EndPaint();
在調用完BeginPaint之后,WndProc接著調用GetClientRect:
GetClientRect(hwnd,&rect);
第一個參數是程序窗口的句柄。第二個參數是一個指針,指向一個RECT類型的結構。查MSDN,可看到這個結構有四個成員。
WndProc做了一件事,他把這個RECT結構的指針傳送給了DrawText的第四個參數。函數DrawText的目的就是在窗口上顯示一行字----"你好,歡迎你來到VC之路!",有關這個函數的具體用法這里也沒必要說了吧。
關于WM_DESTROY 這個消息要比WM_PAINT消息容易處理得多:只要用戶關閉窗口,就會發送WM_DESTROY消息(在窗口從屏幕上移去后)。
程序通過調用PostQuitMessage以標準方式響應WM_DESTROY消息:
PostQuitMessage (0) ;
這個函數在程序的消息隊列中插入一個WM_QUIT消息。在(四)創建消息循環中我們曾有這么一段話:
消息循環以GetMessage調用開始,它從消息隊列中取出一個消息:
.......
在接收到除WM_QUIT之外的任何一個消息后,GetMessage()都返回TRUE。如果GetMessage收到一個WM_QUIT消息,則返回FALSE,如收到其他消息,則返回TRUE。因此,在接收到WM_QUIT之前,帶有GetMessage()的消息循環可以一直循環下去。只有當收到的消息是WM_QUIT時,GetMessage才返回FALSE,結束消息循環,從而終止應用程序。
---------------------還有一個對本文的總結請看51黑2樓----------------------------------------
|
|