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.每一個線程都有一個屬於自己的“訊息佇列”
圖示: