1. 什么是窗口 MSDN: In a graphical Win32-based application, a window is a rectangular area of the screen where the application displays output and receives input from the user. Therefore, one of the first tasks of a graphical Win32-based application is to create a window. 大意:窗口就是一個矩形區(qū)域,應(yīng)用程序可以用它來顯示輸出,或者從user來獲得輸入(鍵盤鼠標(biāo))。在windows中,“一切皆為窗口”。雖然不是很貼切,但是說明了窗口的普遍性和其重要性。比如,你現(xiàn)在看到的QQ聊天窗口,就是由多個窗口組成的! Windows程序是由一系列的窗口構(gòu)成的,每個窗口都有自己的窗口過程。 2. 什么是消息
MSDN: A data packet used for communicating information or a request. Messages can be passed between the operating system and an application, different applications, threads within an application, and windows within an application. 大意:消息就是一組數(shù)據(jù)包(結(jié)構(gòu)體),用于傳遞信息。消息可以在操作系統(tǒng)和一個進程之間傳遞,也可以在兩個不同的進程間傳遞,也可以在同一個進程的不同線程間傳遞或同一個進程的不同窗口間傳遞。 比如,你在QQ聊天窗口中點一下鼠標(biāo),打字等,都會產(chǎn)生消息。在代碼中消息是以消息結(jié)構(gòu)體MSG來保存的,里面的成員包括處理該消息的窗口句柄,消息代碼,消息產(chǎn)生的時間和光標(biāo)的位置等。 消息分類: <1>.隊列消息和非隊列消息:從消息的發(fā)送途徑上看,消息分兩種:隊列消息和非隊列消息。 隊列消息送到系統(tǒng)消息隊列,然后到線程消息隊列;非隊列消息直接送給目的窗口過程。這里,對消息隊列闡述如下: Windows維護一個系統(tǒng)消息隊列(System message queue),每個GUI線程有一個線程消息隊列(Thread message queue)。鼠標(biāo)、鍵盤事件由鼠標(biāo)或鍵盤驅(qū)動程序轉(zhuǎn)換成輸入消息并把消息放進系統(tǒng)消息隊列,例如WM_MOUSEMOVE、WM_LBUTTONUP、WM_KEYDOWN、WM_CHAR等等。Windows每次從系統(tǒng)消息隊列移走一個消息,確定它是送給哪個窗口的和這個窗口是由哪個線程創(chuàng)建的,然后,把它放進窗口創(chuàng)建線程的線程消息隊列。線程消息隊列接收送給該線程所創(chuàng)建窗口的消息。線程從消息隊列取出消息,通過Windows把它送給適當(dāng)?shù)拇翱谶^程來處理。 除了鍵盤、鼠標(biāo)消息以外,隊列消息還有WM_PAINT、WM_TIMER和WM_QUIT。這些隊列消息以外的絕大多數(shù)消息是非隊列消息。 <2>.系統(tǒng)消息和應(yīng)用程序消息: 從消息的來源來看,可以分為:系統(tǒng)定義的消息和應(yīng)用程序定義的消息。(控件改變時自己也會向系統(tǒng)發(fā)送消息,如invalidate) 系統(tǒng)消息ID的范圍是從0到WM_USER-1,或0X80000到0XBFFFF;應(yīng)用程序消息從WM_USER(0X0400)到0X7FFF,或0XC000到0XFFFF;WM_USER到0X7FFF范圍的消息由應(yīng)用程序自己使用;0XC000到0XFFFF范圍的消息用來和其他應(yīng)用程序通信,為了ID的唯一性,使用::RegisterWindowMessage來得到該范圍的消息ID。
<3>.窗口消息,命令消息,控件通知消息:根據(jù)處理過程的不同,可以分為三類:窗口消息,命令消息,控件通知消息。 (1).窗口消息 一般以WM_開頭,如WM_CREATE, WM_SIZE, WM_MOUSEMOVE等標(biāo)準(zhǔn)的Windows消息, 用于窗口相關(guān)的事件通知,窗口消息將由系統(tǒng)分配到該窗口的窗口過程處理。 (2).命令消息 (WM_COMMAND) 一種特殊的窗口消息,它從一個窗口發(fā)送到另一個窗口以處理來自用戶的請求,通常是從子窗口發(fā)送到父窗口,例如,點擊按鈕時,按鈕的父窗口會收到WM_COMMAND消息,用以通知父窗口按鈕被點擊,經(jīng)測試:子窗口向父窗口發(fā)送WM_COMMAND消息,或者稱為父窗口會收到WM_COMMAND消息,操作系統(tǒng)并不是通過將WM_COMMAND消息放入到父窗口的消息隊列中去,而是直接調(diào)用了父窗口的窗口過程,以 WM_COMMAND 為消息標(biāo)識參數(shù)(UINT uMsg),實現(xiàn)這個功能的API函數(shù)正是: LRESULT DispatchMessage(const MSG *lpmsg); (3).控件通知消息 WM_NOTIFY消息,當(dāng)用戶與控件交互(Edit, Button...)時,通知消息會從控件窗口發(fā)送到父窗口,這種消息的目的不是為了處理用戶命令,而是為了讓父窗 口能夠適時的改變控件。 3. 什么是窗口過程函數(shù) 我們鼠標(biāo)、鍵盤消息是在窗口上產(chǎn)生的,windows會把消息傳給該窗口所屬線程的消息隊列中。然后這個消息在某個時間被這個窗口對應(yīng)的處理函數(shù)來“搞定”。 這個和窗口綁定的“處理消息的函數(shù)”就是“窗口過程函數(shù)”。 一般的,一個窗口類綁定一個窗口過程函數(shù)。 4. 怎么創(chuàng)建窗口
首先,對于windows系統(tǒng)來講,不管你是C#還是C++還是JAVA,要創(chuàng)建一個標(biāo)準(zhǔn)的窗口,都是調(diào)用相同的API的~ 只是在VC、C# .NET或用JAVA開發(fā)windows時,開發(fā)工具都幫你把創(chuàng)建窗口“通用的部分”封裝了起來,你是看不到的。 在我們C#的代碼中,在Main函數(shù)里已經(jīng)給我們創(chuàng)建好了第一個窗口Fomr1。為我們免去了N多初始化的麻煩,但是我們也就不知道窗口是怎么被創(chuàng)造出來的了。 因此,這里的示例代碼使用的是最原始,最明顯的方法來創(chuàng)建一個窗口,顯示出來并處理你發(fā)出的消息。我們用C語言來調(diào)用API創(chuàng)建窗口。 創(chuàng)建窗口的4個步驟: 1) 初始化窗口類(實際上是一個結(jié)構(gòu)體) WNDCLASS wndclass; 2) 注冊這個類 RegisterClass(……); 3) 創(chuàng)建窗口 hwnd = CreateWindow(……); 4) 顯示 ShowWindow(hwnd, iCmdShow); 精簡后的創(chuàng)建窗口程序主干: WinMain(……) { …… HWND hwnd;//用來存放窗口句柄 MSG msg;//用來存放消息的結(jié)構(gòu)體 WNDCLASS wndclass;// 1)窗口結(jié)構(gòu)體 RegisterClass(……);// 2)注冊窗口類 hwnd = CreateWindow(……);// 3)創(chuàng)建窗口 ShowWindow(hwnd, iCmdShow);// 4)顯示 UpdateWindow(hwnd); //下面是消息循環(huán) while(GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; }
說明: 1) WNDCLASS wndclass; 這個結(jié)構(gòu)體里存放了窗口的各種初始數(shù)據(jù)。如窗體類的風(fēng)格,窗體上顯示的圖標(biāo),鼠標(biāo)的圖標(biāo)等。 2) RegisterClass(……); 調(diào)用該函數(shù)通知系統(tǒng)被注冊類的窗口的消息,windows為后面創(chuàng)建窗口準(zhǔn)備一系列資源。 3) hwnd = CreateWindow(……); 調(diào)用該函數(shù)創(chuàng)建較迭的、彈出的或子窗口。它指定窗口類、窗口標(biāo)題、窗口風(fēng)格和窗口的初始大小與位置等。 創(chuàng)建出來的窗口以屬于“活動”狀態(tài),已經(jīng)開始進行消息處理。只是沒有被“顯示”出來。 4) ShowWindow(hwnd, iCmdShow); 該函數(shù)用來設(shè)置指定窗口的“顯示狀態(tài)”,如大小、位置、是否隱藏等。 5. 消息是怎么傳遞并處理的 消息(Message)在窗口中產(chǎn)生后,系統(tǒng)把它放入到該窗口所屬線程的消息隊列中,等待處理。 線程將隊列中的消息取得后,在翻譯后,將它“投放”到相應(yīng)的窗口過程函數(shù)中進行處理。
消息處理機制的核心代碼: while(GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } 根據(jù)MSDN,上面幾個函數(shù)的作用: 1.GetMessage只獲取自己所屬線程的消息隊列中的消息,包括windows消息和通過PostThreadMessage發(fā)送的線程消息。 2.TranslateMessage是為了將虛擬鍵消息“翻譯”成字符消息,然后把字符消息放回到線程的消息隊列中,在下一次被GetMessage取得。(具體內(nèi)容見MSDN) 3.DispatchMessage的作用是將從GetMessage獲得的消息分發(fā)給相應(yīng)的窗口處理函數(shù)(WndProc),然后窗口過程函數(shù)對消息進行處理。
上面代碼中使用while循環(huán)的意義是:循環(huán)是應(yīng)用程序能夠持續(xù)存在的根本原因。如果循環(huán)退出,則應(yīng)用程序就結(jié)束了。
消息是根據(jù)窗口進行分發(fā)的,而不考慮該窗口的從屬關(guān)系。也就是說在子窗口中產(chǎn)生的消息只在子窗口函數(shù)中處理,處理完后不會再把消息傳遞給父窗口(除非你自己做反射)。 對窗口、線程、消息的關(guān)系總結(jié): 1.每一個窗口都是屬于某一個線程的,注意是線程 2.每一個窗口都“綁定”了一個“窗口函數(shù)”!也就是在每個窗口上產(chǎn)生的消息,會發(fā)往對應(yīng)的函數(shù)來處理! 3.每一個線程都有一個屬于自己的“消息隊列” 圖示:

|