對窗口創建的詳細解釋
==================
回顧示例中創建窗口的代碼:

1 #include <windows.h> 2 3 LRESULT CALLBACK WndProc( HWND, UINT, WPARAM, LPARAM ) ; //聲明用來處理消息的函數 4 5 int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow ) 6 { 7 static TCHAR szAppName[] = TEXT("MyWindow") ; 8 HWND hwnd ; 9 MSG msg ; 10 WNDCLASS wndclass ; //聲明一個窗口類對象 11 12 //以下為窗口類對象wndclass的屬性 13 wndclass.style = CS_HREDRAW | CS_VREDRAW ; //窗口樣式 14 wndclass.lpszClassName = szAppName ; //窗口類名 15 wndclass.lpszMenuName = NULL ; //窗口菜單:無 16 wndclass.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH) ; //窗口背景顏色 17 wndclass.lpfnWndProc = WndProc ; //窗口處理函數 18 wndclass.cbWndExtra = 0 ; //窗口實例擴展:無 19 wndclass.cbClsExtra = 0 ; //窗口類擴展:無 20 wndclass.hInstance = hInstance ; //窗口實例句柄 21 wndclass.hIcon = LoadIcon( NULL, IDI_APPLICATION ) ; //窗口最小化圖標:使用缺省圖標 22 wndclass.hCursor = LoadCursor( NULL, IDC_ARROW ) ; //窗口采用箭頭光標 23 24 if( !RegisterClass( &wndclass ) ) 25 { //注冊窗口類, 如果注冊失敗彈出錯誤提示 26 MessageBox( NULL, TEXT("窗口注冊失敗!"), TEXT("錯誤"), MB_OK | MB_ICONERROR ) ; 27 return 0 ; 28 } 29 30 hwnd = CreateWindow( //創建窗口 31 szAppName, //窗口類名 32 TEXT("我的窗口"), //窗口標題 33 WS_OVERLAPPEDWINDOW, //窗口的風格 34 CW_USEDEFAULT, //窗口初始顯示位置x:使用缺省值 35 CW_USEDEFAULT, //窗口初始顯示位置y:使用缺省值 36 CW_USEDEFAULT, //窗口的寬度:使用缺省值 37 CW_USEDEFAULT, //窗口的高度:使用缺省值 38 NULL, //父窗口:無 39 NULL, //子菜單:無 40 hInstance, //該窗口應用程序的實例句柄 41 NULL // 42 ) ; 43 44 ShowWindow( hwnd, iCmdShow ) ; //顯示窗口 45 UpdateWindow( &msg ); //更新窗口 46 47 while( GetMessage( &msg, NULL, 0, 0 ) ) //從消息隊列中獲取消息 48 { 49 TranslateMessage( &msg ) ; //將虛擬鍵消息轉換為字符消息 50 DispatchMessage( &msg ) ; //分發到回調函數(過程函數) 51 } 52 return msg.wParam ; 53 } 54 55 LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam ) 56 { 57 HDC hdc ; //設備環境句柄 58 PAINTSTRUCT ps ; //繪制結構 59 RECT rect; //矩形結構 60 61 switch( message ) //處理得到的消息 62 { 63 case WM_CREATE: //窗口創建完成時發來的消息 64 MessageBox( hwnd, TEXT("窗口已創建完成!"), TEXT("我的窗口"), MB_OK | MB_ICONINFORMATION ) ; 65 return 0; 66 67 case WM_PAINT: //處理窗口區域無效時發來的消息 68 hdc = BeginPaint( hwnd, &ps ) ; 69 GetClientRect( hwnd, &rect ) ; 70 DrawText( hdc, TEXT( "Hello, 這是我自己的窗口!" ), -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER ) ; 71 EndPaint( hwnd, &ps ) ; 72 return 0 ; 73 74 case WM_LBUTTONDOWN: //處理鼠標左鍵被按下的消息 75 MessageBox( hwnd, TEXT("鼠標左鍵被按下。"), TEXT("單擊"), MB_OK | MB_ICONINFORMATION ) ; 76 return 0; 77 78 case WM_DESTROY: //處理窗口關閉時的消息 79 MessageBox( hwnd, TEXT("關閉程序!"), TEXT("結束"), MB_OK | MB_ICONINFORMATION ) ; 80 PostQuitMessage( 0 ) ; 81 return 0; 82 } 83 return DefWindowProc( hwnd, message, wParam, lParam ) ; //DefWindowProc處理我們自定義的消息處理函數沒有處理到的消息 84 }
·消息的循環
"消息", 在Windows程序設計中是個十分重要的概念, 是消息驅動着Windows系統的運行。通過昨天的學習, 我們已經知道一個窗口是如何被創建並且顯示在屏幕上, 當一個窗口被創建之后, Windows就開始向窗口源源不斷的發送一些消息, 消息來自Windows的為該程序創建的一個消息隊列中, 然后窗口過程根據不同的消息作出相應的處理。
Windows中的"消息"(MSG)是一個結構類型, 該結構定義在WINUSER.H頭文件中, MSG結構的定義如下:
/* * Message structure */ typedef struct tagMSG{ HWND hwnd; //窗口句柄, 代表消息所屬的窗口 UINT message; //消息的編號 WPARAM wParam; //附加信息 LPARAM lParam; //附加信息 DWORD time; //消息的時間 POINT pt; //鼠標的位置 } MSG, *PMSG;
成員HWND為消息所指向的窗口句柄, 代表消息所屬的窗口; UINT為消息的編號, 為一個unsigned int型數據; WPARAM和LPARAM用於指定消息的附加信息, 具體含義取決於具體的消息; DWORD為unsigned long型, 為消息進入消息隊列時的時間; POINT也是一種結構類型, 在WINDEF.H中的定義如下:
typedef struct tagPOINT { LONG x; LONG y; } POINT, *PPOINT;
在這里用來記錄消息進入消息隊列時的鼠標位置。
當消息產生后會存在於Windows為該程序維護的一個消息隊列里, 要得到這些消息我們需要使用GetMessage函數, 此函數的作用是消息隊列里取得一個消息並將該消息存放在指定的一個MSG結構的一個對象中, 函數的原型如下:
BOOL GetMessage( LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax ) ;
參數一LPMSG lpMsg為指向一個MSG結構型的指針, 用於從消息隊列中接收消息; 參數二為待檢索消息的窗體句柄, 通常設為NULL, 第三、四個參數UINT wMsgFilterMin, UINT wMsgFilterMax分別代表允許被檢索的最小消息值的整數值和最大消息值的整數值, 當成功取得消息時, 該消息就會被從消息隊列中刪除(WM.PAINT消息是個例外)。
函數的返回值, 成功取得除WM_QUIT之外的消息返回值為非零, 取得WM_QUIT消息時, 返回值是零, 如果出現了錯誤, 返回值為-1。
在GetMessage后我們還看到使用了個TranslateMessage函數, 該函數用來將虛擬鍵消息轉換為字符消息(暫時先了解下), 完成對消息的翻譯轉換后調用DispatchMessage函數將得到的消息再傳遞給Windows, 然后由Windows去調用我們的窗口過程(回調函數)。
窗口過程完成對消息的相關處理后再將控制權轉回給Windows, 繼續下一輪的消息處理。
·對消息的處理
在以上的敘述中我們已經大致了解了一個窗口創建的工作流程, 其中我們多次提到了窗口過程, 也就是我們自定義的消息處理函數, 窗口過程決定對得到的消息以何種方式進行處理, 在我們創建窗口的示例中, 自定義的窗口過程名為WndProc, 窗口過程總是以以下形式聲明:
LRESULT CALLBACK WndProc( HWND, UINT, WPARAM, LPARAM ) ;
窗口過程有四個參數, 分別對應這個MSG結構中的前四個成員。
關於窗口過程的使用, 是通過窗口類
wndclass.lpfnWndProc = WndProc ;
的這種方式引用的, 一個窗口過程總是與一個特定的窗口類進行關聯。
例如在我們創建窗口的示例中分別對得到的四種消息進行處理, WM_CREATE、WM_PAINT、WM_LBUTTONDOWN和WM_DESTROY。在Windows中, 消息總是以WM為前綴作為消息類型的標示, 這些標識符在WINUSER.H頭文件中有相關的定義, 關於這些的消息詳細情況可以查閱MSDN Library。
接下來我們使用了個switch語句對得到的這些消息進行處理, 通常是以這樣的形式出現:
switch( message ) { case 消息1: [對消息1進行處理] ; return 0 ; case 消息2: [對消息1進行處理] ; return 0 ; case 消息3: [對消息3進行處理] ; return 0 ; ...... case 消息n: [對消息n進行處理]; return 0; } return DefWindowProc( hwnd, message, wParam, lParam ) ;
這里需要注意2點, 一是在case語句對消息處理完畢后一般使用return 0;結束窗口過程, 對於如果使用break;語句函數將再次調用默認的消息處理函數DefWindowProc進行處理; 二是
DefWindowProc( hwnd, message, wParam, lParam );
有這條語句是十分必要的, 對於我們沒有處理到的消息, 必須進行調用默認的消息處理函數DefWindowProc來確保得到的每個消息進行處理, DefWindowProc函數的原型如下:
LRESULT DefWindowProc( HWND hWnd, UINT Msg, WPARAM wParam, LPARAM IParam ) ;
參數一HWND hWnd為接收消息的窗口句柄;
參數二UINT Msg,為需要處理的消息類型;
參數三wParam與參數四LPARAM IParam為額外的消息信息。該參數的內容與msg參數值有關。
返回值為消息處理的結果, 根據不同的消息返回值可能不同。
如果沒有對其他消息使用默認的消息處理, 那么窗口的其他正常行為將無法進行。
現在, 我們來看下創建示例窗口中是如何對這4種消息進行處理的。
1>. 處理WM_CREATE 消息
WM_CREATE消息是窗口在成功創建時進入消息隊列, 但值得一提的是: WM_CREATE在CreateWindow函數返回之前就被送到了消息隊列當中。
我們對於WM_CREATE消息作出的響應是彈出一個對話框告訴用戶窗口已被成功創建:
MessageBox( hwnd, TEXT("窗口已創建完成!"), TEXT("我的窗口"), MB_OK | MB_ICONINFORMATION ) ;
2>. 處理WM_PAINT消息
WM_PAINT消息是當窗口客戶區無效並且需要重新更新時接收到的消息, 何時窗口客戶區會變得無效且需要更新?例如以下情況:
1. 首次被創建時, 這時整個客戶區都是無效的;
2. 調整窗口的尺寸時, 這時客戶區也會變得無效;
3. 最小化窗口后再將其恢復顯示時;
4. 拖拽調整窗口位置時;
當客戶區變得無效時我們就需要對其進行重繪, 一般總是調用BeginPaint函數作為重繪的開始, BeginPaint函數的原型如下:
HDC BeginPaint( HWND hwnd, LPPAINTSTRUCT lpPaint );
參數一為程序被重繪的窗口句柄, 參數二為用來指向接收繪畫信息的一個PAINTSTRUCT結構;
當函數調用成功, 返回值是指定窗口的“顯示設備描述表”句柄; 如果函數失敗,返回值是NULL。
通過設備環境句柄我們就可以在窗口的客戶區進行繪制一些信息, 例如繪制圖形或一些文本, 取得設備環境句柄:
hdc = BeginPaint( hwnd, &ps ) ;
成功獲取到設備環境句柄后就可以開始對客戶區進行重繪工作了, 首先使用GetClientRect函數獲取該窗口客戶區左上角(0, 0)和右下角(x, y)的坐標, 函數原型。
BOOL GetClientRect( HWND hWnd, // 窗口句柄 LPRECT lpRect //指向一個矩形結構用來接收客戶區坐標 );
函數調用成功時,返回一個非零值; 調用失敗, 返回零。
隨后使用了DrawText向矩形結構里輸入一些文字, DrawText函數的原型如下:
int DrawText( HDC hDC, // 設備描述表句柄 LPCTSTR lpString, // 將要繪制的字符串 int nCount, // 字符串的長度 LPRECT lpRect, // 指向矩形結構RECT的指針 UINT uFormat // 繪制選項 );
第一個參數為BeginPaint函數所返回的設備環境句柄;
第二個參數為要繪制的字符串,;
第三個參數為字符串的長度, 如果參數為-1, 則表示一個以0結尾的字符串, 讓DrawText自己計算字符個數;
第四個參數為一組通過組合的標識符, 這些標識符用來標明文本的繪制方式, 例如較常用的一些有:
DT_BOTTOM //將正文調整到矩形底部; DT_CENTER //使正文在矩形中水平居中; DT_LEFT //正文左對齊; DT_RIGHT //正文右對齊; DT_TOP //正文頂端對齊; DT_VCENTER //使正文在矩形中垂直居中; DT_WORD_ELLIPSIS //截短不符合矩形的正文,並增加省略號;
此外還有更多的繪制方式, 這些標識符定義在WINUSER.H頭文件中, 可以到MSDN Library查詢更多詳細的介紹。
繪制完成后使用EndPaint( hwnd, &ps ) ;釋放設備環境句柄, 這樣窗口的一遍重繪就完成了, 窗口客戶區再次變得有效。
3>. 處理WM_LBUTTONDOWN 消息
WM_LBUTTONDOWN 消息是當鼠標左鍵在客戶區被按下時產生的消息, 處理的過程為簡單的彈出一個對話框提示鼠標左鍵被按下了。
4>.處理 WM_DESTROY 消息
當用戶點擊窗口上的關閉按鈕時, WM_DESTROY 消息產生, 我們彈出一個"關閉程序!"的對話框后使用PostQuitMessage 函數向消息隊列插入一個WM_QUIT, 當GetMessage獲取到WM_QUIT消息時便返回一個0, 使while退出循環執行
return msg.wParam ; //msg.wParam為PostQuitMessage( 0 ); 函數中的參數, 通常為0
從而結束程序運行。
------------------
上一篇: C語言Windows程序設計->第四天->詳解我的窗口(中)