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