第二課——窗口和消息
一、窗口與消息處理
(1)一個“窗口”就是:屏幕上的一個矩形區域,它接收用戶的輸入,並以文本或圖形方式來顯示內容。
(2)窗口還是用戶操作的區域界面,在編程中除創建等操作外,還要處理用戶輸入、窗口本身事件所產生的“消息”。
二、程序框架代碼——窗口創建和消息處理
#include <windows.h> LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); //窗口過程 int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { HWND hwnd; //窗口句柄 MSG msg; //消息 WNDCLASS wndclass; //窗口類 wndclass.style = CS_HREDRAW | CS_VREDRAW; wndclass.lpfnWndProc = WndProc; wndclass.cbClsExtra = 0; wndclass.cbWndExtra = 0; wndclass.hInstance = hInstance; wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); wndclass.hbrBackground = GetStockObject(WHITE_BRUSH); wndclass.lpszMenuName = NULL; wndclass.lpszClassName = "HelloWin"; //窗口類名 if (!RegisterClass(&wndclass)) { //註冊窗口 MessageBox(NULL, "窗口註冊失敗!", "HelloWin", 0); return 0; } //創建窗口 hwnd = CreateWindow( "HelloWin", //窗口類名 "我的窗口", //窗口標題 WS_OVERLAPPEDWINDOW, //窗口樣式 CW_USEDEFAULT, //窗口最初的x位置 CW_USEDEFAULT, //窗口最初的y位置 480, //窗口最初的x大小 320, //窗口最初的y大小 NULL, //父窗口句柄 NULL, //窗口菜單句柄 hInstance, //應用程序實例句柄 NULL); //創建窗口的參數 //顯示窗口 ShowWindow(hwnd, nCmdShow); //更新窗口,包括窗口的客戶區 UpdateWindow(hwnd); //進入消息循環:當從應用程序消息隊列中檢取的消息是WM_QUIT時,則退出循環 while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); //轉換某些鍵盤消息 DispatchMessage(&msg); //將消息發送給窗口過程,這裏是WndProc } return msg.wParam; } LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps; RECT rc; switch (message) { case WM_CREATE: //窗口創建產生的消息 return 0; case WM_PAINT: hdc = BeginPaint(hwnd, &ps); GetClientRect(hwnd, &rc); //獲取窗口客戶區大小 DrawText(hdc, TEXT("Hello Windows!"), -1, &rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER); EndPaint(hwnd, &ps); return 0; case WM_DESTROY: //當窗口關閉時產生的消息 PostQuitMessage(0); return 0; } //執行默認的消息處理 return DefWindowProc(hwnd, message, wParam, lParam); }
註:上面代碼可以分解成兩個基本函數的程序結構,一個就是前面所討論的WinMain函數,另一個還是用戶定義的窗口過程函數WndProc。窗口過程函數WndProc用來接收和處理各種不同的消息。
三、分析——註冊窗口類
1. 註冊窗口代碼:if(!RegisterClass(&wndclass))
2. 在為程序創建窗口之前,必須首先調用創建RegisterClass註冊應用程序的窗口類。該函數只要一個參數,即一個指向類型為WNDCLASS的結構指針。它(即WNDCLASS結構)包含了一個窗口的基本屬性,如窗口邊框、窗口標題欄文字、窗口大小和位置、鼠標、背景色、處理窗口消息函數的名稱等。
事實上,註冊的過程也就是將這些屬性告訴系統,然後再調用CreateWindow函數創建出窗口。
3. WNDCLASS結構的原型如下:
typedef struct { UINT style; //窗口的風格 WNDPROC lpfnWndProc; //指定窗口的消息處理函數的窗口過程函數 int cbClsExtra; //指定分配給窗口類結構之後的額外字節數 int cbWndExtra; //指定分配給窗口實例之後的額外字節數 HINSTANCE hInstance; //指定窗口過程所對應的實例句柄 HICON hIcon; //指定窗口的圖標 HCURSOR hCursor; //指定窗口的鼠標指針 HBRUSH hbrBackground; //指定窗口的背景畫刷 LPCTSTR lpszMenuName; //窗口的菜單資源名稱 LPCTSTR lpszClassName; //該窗口類的名稱 }WNDCLASS, *PWNDCLASS;
可以看出,該結構有10個域(成員),其中第一個域style表示窗口類的風格,它往往是由一些基本的預定義風格通過位的“或”操作組合而成的。如,在HelloWin.c中,有:
WNDCLASS wndclass; //窗口類 wndclass.style = CS_HREDRAW | CS_VREDRAW; wndclass.lpfnWndProc = WndProc; wndclass.cbClsExtra = 0; wndclass.cbWndExtra = 0; wndclass.hInstance = hInstance; wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); wndclass.hbrBackground = GetStockObject(WHITE_BRUSH); wndclass.lpszMenuName = NULL; wndclass.lpszClassName = "HelloWin"; //窗口類名
(1)可以看到,wndclass.style被設為CS_VREDRAW | CS_HREDRAW,表示只要窗口的高度或寬度發生變化,都會重畫整個窗口。
(2)第二個域lpfnWndProc的值為WndProc,表明該窗口類的消息處理函數是WndProc函數。這裏,可簡單直接地輸入消息處理(窗口過程)函數的函數名即可。
(3)對於hInstance成員,給它的值是由WinMain傳來的應用程序的實例句柄,表明該窗口與該實例是相關聯的。事實上,只要是註冊窗口類,該成員的值始終是該程序的實例句柄。
(4)對於hIcon成員,是要給這個窗口指定一個圖標,LoadIcon(NULL, IDI_APPLICATION)就是調用系統內部預先定義好的標識符為IDC_APPLICATION的圖標作為該窗口的圖標。同樣,LoadCursor(NULL, IDC_ARROW)就是調用預定義的箭形鼠標指針。
(5)對於hbrBackground成員,它是用來定義窗口的背景畫刷顏色,也就是該窗口的背景色。調用GetStockObject(WHITE_BRUSH)可以獲得系統內部預先定義好的白色畫刷作為窗口的背景色。即這裏LoadIcon、LoadCursor、GetStockObject等都是Windows 的API函數,在程序中可直接調用。
(6)lpszMenuName域的值若為NULL,則表示該窗口將沒有菜單。否則,需要指定表示菜單資源的字符串。
(7)最後一個域lpszClassName是要給這個窗口類起一個唯一的名稱,因為Windows操作系統中有許許多多的窗口類,必須用一個獨一無二的名稱來代表它們。
四、分析——創建和顯示窗口
1. 窗口類註冊完畢之後,並不會有窗口顯示出來,因為註冊的過程僅僅是為創建窗口所做的準備工作。
實際創建一個窗口是通過調用CreateWindow函數完成的。窗口類中已經預先定義了窗口的一般屬性,而CreateWindow中的參數可以進一步指定一個窗口的更具體的屬性。
2. 在HelloWin.c程序中,是用下列調用CreateWindow函數的代碼來創建窗口的:
//創建窗口 hwnd = CreateWindow( "HelloWin", //窗口類名 "我的窗口", //窗口標題 WS_OVERLAPPEDWINDOW, //窗口樣式 CW_USEDEFAULT, //窗口最初的x位置 CW_USEDEFAULT, //窗口最初的y位置 480, //窗口最初的x大小 320, //窗口最初的y大小 NULL, //父窗口句柄 NULL, //窗口菜單句柄 hInstance, //應用程序實例句柄 NULL); //創建窗口的參數
(1)第一個參數:創建該窗口所使用的窗口類的名稱,該名稱與前面所註冊的窗口類的名稱一致。
(2)第三個參數:創建的窗口的風格,它們通常是一些預定義風格的“|”組合。其中,WS_OVERLAPPEDWINDOW表示創建一個層疊式窗口,有邊框、標題欄、系統菜單、最大化和最小化按鈕等。
(3)後面的參數中仍用到了該應用程序的實例句柄hInstance。如果窗口創建成功,返回值是新窗口的句柄,否則返回NULL。
3. 窗口創建後,並不會在屏幕上顯示出來。顯示窗口要調用ShowWindow函數。
ShowWindow函數的原型:BOOL ShowWindow(HWND hWnd, int nCmdShow);
其中,參數hWnd指定要顯示的窗口的句柄,nCmdShow表示窗口的顯示方式,這裏指定為從WinMain函數的nCmdShow所傳遞過來的值。
由於ShowWindow函數的執行優先級不高,所以當系統正忙著執行其他的任務時,窗口不會立即顯示出來,此時,調用UpdateWindow函數可以立即顯示窗口。同時,它將會給窗口過程發出WM_PAINT消息。
五、分析——消息和消息處理
1. 消息循環:
//進入消息循環:當從應用程序消息隊列中檢取的消息是WM_QUIT時,則退出循環 while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); //轉換某些鍵盤消息 DispatchMessage(&msg); //將消息發送給窗口過程,這裏是WndProc }
(1)Windows應用程序可以接收以各種形式輸入的信息,這包括鍵盤、鼠標動作、計時器產生的消息,也可以是其他應用程序發來的消息等。Windows系統自動監控所有的輸入設備,並將其消息放入該應用程序的消息隊列中。
(2)GetMessage函數就是用來從應用程序的消息隊列中按照先進先出的原則將這些消息一個個地取出來,放進一個MSG結構中去。
(3)函數GetMessage的原型:
BOOL GetMessage( LPMSG lpMsg; //指向一個MSG結構的指針,用來保存消息 HWND hWnd; //指定哪個窗口的消息將被獲取 UINT wMsgFilterMin; //指定獲取的主消息值的最小值 UINT wMsgFilterMax; //指定獲取的主消息值的最大值 );
(4)GetMessage函數用來將獲取的消息復制到一個MSG結構中。如果隊列中沒有任何消息,該函數將一直空閑直到隊列中又有消息時再返回。如果隊列中已有消息,它將取出一個後返回。
(5)MSG結構包含Windows消息的完整信息,其定義如下:
typedef struct { HWND hwnd; //消息發向的窗口的句柄 UINT message; //主消息的標識值 WPARAM wParam; //附消息值,其具體含義依賴於主消息值 LPARAM lParam; //附消息值,其具體含義依賴於主消息值 DWORD time; //消息放入消息隊列中的時間 POINT pt; //消息放入消息隊列中的鼠標坐標 }MSG, *PMSG;
主消息:表明了消息的類型,例如,是鍵盤消息還是鼠標消息等。
附消息:其含義則依賴於主消息值,例如,如果主消息是鍵盤消息,那麽附消息中則存儲了鍵盤的那個具體鍵的信息。
(6)GetMessage函數還可以過濾消息,它的第二個參數是用來指定從哪個窗口的消息隊列中獲取消息,其他窗口的消息將被過濾掉。如果該參數為NULL,則GetMessage重改應用程序線程的所有窗口的消息隊列中獲取消息。第三個和第四個參數是用來過濾MSG結構中主消息值的,主消息值在wMsgFilterMin和wMsgFilterMax之外的消息將被過濾掉。如果這兩個參數為0,則表示接收所有消息。
(7)特別地,當且僅當GetMessage函數在獲取WM_QUIT消息後,將返回0值,於是程序退出消息循環。
(8)TranslateMessage函數的作用:把虛擬鍵消息轉換到字符消息,以滿足鍵盤輸入的需要。
(9)DispatchMessage函數的作用:把當前的消息發送到對應的窗口過程中去。
2. 消息處理:
(1)窗口過程:用於消息處理的函數。
用於消息處理的函數又叫窗口過程,在這個函數中,不同的消息將用switch語句分配到不同的處理程序中去。Windows的消息處理函數都有一個確定的統一方式,即這種函數的參數個數和類型以及其返回值的類型都有明確的規定。
在HelloWin.c中,WinProc函數明確處理了3個消息,分別是WM_CREATE(創建窗口消息)、WM_PAINT(窗口重畫消息)、WM_DESTROY(銷毀窗口消息)。
事實上,應用程序發送到窗口的消息遠遠不止以上這幾條,像WM_SIZE、WM_MINIMIZE、WM_MOVE等這樣經常使用的消息就有好幾十條。為了減輕編程的負擔,Windows的API提供了DefWindowProc函數來處理這些最常用的消息,調用這個函數後,這些消息將按照系統默認的方式得到處理。因此,在switch語句中,只需明確處理那些有必要進行特別響應的消息,把其余的消息交給DefWindowProc函數來處理,即將消息的控制交由Windows進行默認處理。
3. 結束消息循環:
當用戶關閉窗口時,系統就向應用程序發送一條WM_DESTROY的消息。在處理此消息時,調用了PostQuitMessage函數,該函數會向窗口的消息隊列中發送一條WM_QUIT消息。在消息循環中,GetMessage函數一旦檢索到這條消息,就會返回FALSE,從而結束消息循環,隨後程序也結束。
六、分析——WM_PAINT函數
1. WM_PAINT是Win32的圖形和文本編程中經常使用到的消息。
2. 重繪:當窗口客戶區的一部分或全部變成“無效”時,必須“刷新”重繪,此時將向程序發出此消息(WM_PAINT)。
無效:在最初窗口創建時,整個客戶區都是“無效”的,因為窗口上還沒有繪制任何東西。所以,在創建窗口時,會發出第一個WM_PAINT消息。在HelloWin.c程序中,由於在註冊窗口時,制定了wndclass.style的風格為CS_VREDRAW和CS_HREDRAW,這表明只要窗口的高度或寬度發生變化,就將使整個窗口“無效”,從而發出WM_PAINT消息,使得系統重畫整個窗口。當窗口最小化再恢復為以前的大小時,Windows將令窗口“無效”,並發出WM_PAINT消息使系統重畫整個窗口。當窗口移至與另一窗口有重疊被遮擋時,Windows也將窗口視為“無效”,發出WM_PAINT消息以便刷新窗口。
3. 在窗口過程函數WndProc中,WM_PAINT消息處理通常有下列代碼:
case WM_PAINT: hdc = BeginPaint(hwnd, &ps); GetClientRect(hwnd, &rc); //獲取窗口客戶區大小 DrawText(hdc, TEXT("Hello Windows!"), -1, &rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER); EndPaint(hwnd, &ps); return 0;
(1)它總是從BeginPaint函數開始,而從EndPaint函數結束。BeginPaint函數用來指定窗口句柄的設備描述表句柄,設備描述表用來將程序與計算機外部輸出設備連接起來。
(2)hdc定義的是句柄HDC變量,DrawText等GDI函數都需要通過這樣的HDC句柄來繪制圖形和文本。
(3)EndPaint用來釋放設備描述表句柄,並使先前無效區域變為有效,從而使Windows不再發送WM_PAINT消息。
(4)PAINTSTRUCT是“繪圖信息結構”,BeginPaint和EndPaint函數都需要PAINTSTRUCT結構變量作為自己的參數。需要說明的是,BeginPaint和EndPaint函數必須成對出現,所有GDI函數的調用也應在這兩個函數之間進行。
(5)DrawText函數用來在參考矩形內使用指定的格式來繪制文本,它的函數原型如下:
int DrawText( HDC hDC; //繪制設備的句柄 LPCTSTR lpString; //要繪制的文本 int nCount; //文本的字符個數 LPRECT lpRect; //參考矩形 UINT uFormat; //文本繪制格式 );
其中,當nCount為-1時,表示lpString指定的是以“\0”為結尾的字符串,並自動計算該字符串的字符個數。lpRect是一個指向RECT類型的“矩形”結構指針,該“矩形”結構含有left、top、right和bottom4個LONG域。為了能在窗口客戶區中間繪制文本,該函數的lpRect被填為RECT變量rc的指針,它通過調用GetClientRect函數,獲取hwnd窗口的客戶區大小。同時,指定uFormat格式為DT_SINGLELINE(單行輸出)、DT_CENTER(水平居中)、DT_VCENTER(垂直居中)。
七、Windows基本數據類型
在前面的示例中,有一些“奇怪”的數據類型,如HINSTANCE和LPSTR等,事實上,很多這樣的數據類型只是一些基本數據類型的別名。
1. Windows編程中常用的基本數據類型
Windows所用的數據類型 | 對應的基本數據類型 | 說明 |
BOOL | bool | 布爾值 |
BSTR | unsigned short * | 32位字符指針 |
BYTE | unsigned char | 8位無符號整數 |
COLORREF | unsigned long | 用作顏色值的32位值 |
DWORD | unsigned long | 32位無符號整數,段地址和相關的偏移地址 |
LONG | long | 32位帶符號整數 |
LPARAM | long | 作為參數傳遞給窗口過程或回調函數的32位值 |
LPCSTR | const char * | 指向字符串常量的32位指針 |
LPSTR | char * | 指向字符串的32位指針 |
LPVOID | void * | 指向未定義類型的32位指針 |
LRESULT | long | 來自窗口過程或回調函數的32位返回值 |
UINT | unsigned int | 32位無符號整數 |
WORD | unsigned short | 16位無符號整數 |
WPARAM | unsigned int | 作為參數傳遞給窗口過程或回調函數的32位值 |
2. Windows編程中常用的句柄類型
句柄類型 | 說明 |
HBITMAP | 保存位圖信息的內存域的句柄 |
HBRUSH | 畫刷句柄 |
HCURSOR | 鼠標光標句柄 |
HDC | 設備描述表句柄 |
HFONT | 字體句柄 |
HICON | 圖標句柄 |
HINSTANCE | 應用程序的實例句柄 |
HMENU | 菜單句柄 |
HPALETTE | 顏色調色板句柄 |
HPEN | 在設備上畫圖時用於指明線型的筆的句柄 |
HWND | 窗口句柄 |
補充:
窗口過程:用於消息處理的函數,例如,本例中的窗口過程函數WndProc
回調函數
GDI函數
預定義風格:如CS_VREDRAW、CS_HREDRAW等,它們可以通過“|”組合
API函數:如LoadIcon、LoadCursor、GetStockObject等都是Windows 的API函數,在程序中可直接調用
第二課——窗口和消息