在windows應用程序中,窗口是其非常重要的一個元素。並且窗口是通過窗口句柄來標識的。句柄(HANDLE)是windows程序中一個重要的概念,其標識各種資源,包括圖標句柄(HICON)、光標句柄(HCURSOR)和畫刷句柄(HBRUSH)。
下面以一個帶有自定義的畫刷、光標和圖標的windows窗口為例,講解win32窗口的創建過程。
windows消息機制
windows程序是基於事件驅動方式的程序設計模式,主要是基於消息的。比如當用戶在窗口中畫圖的時候,按下鼠標左鍵,此時os會感知到這一事件,於是將此事件包裝成一個消息,投遞到應用程序的消息隊列中,然后應用程序從消息隊列中取出消息,經過transltor翻譯、分發消息,然后交由os調用
窗口過程函數(應用程序注冊的回調函數)或(DefWindowProc系統默認的回調處理函數)進行處理。
創建一個窗口的基本流程
設計窗口類
窗口的特征是由WNDCLASS結構體來定義的,其定義了這個窗口的基本屬性,所以我們只需填充結構體各成員信息即可。

typedef struct tagWNDCLASSW { UINT style; WNDPROC lpfnWndProc; int cbClsExtra; int cbWndExtra; HINSTANCE hInstance; HICON hIcon; HCURSOR hCursor; HBRUSH hbrBackground; LPCWSTR lpszMenuName; LPCWSTR lpszClassName; } WNDCLASSW typedef WNDCLASSW WNDCLASS;//WNDCLASS是WNDCLASSW的別名
這里要首先說明下各類型定義
//LRESULT :long // UINT:unsigned int //WPARAM:unsigned int //LPARAM:unsigned int //LPCWSTR:const w_chart_t *; // typedef WORD ATOM; //BUGBUG - might want to remove this from minwin // typedef unsigned short WORD; // typedef unsigned long DWORD; //LPCWSTR const w_char_t * 寬字符 //LPCSTR const char *
wchar_t szAppclassName[] = _T("FirstWin32"); WNDCLASS wc; wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;//窗口類的樣式(要讓窗口在水平和垂直尺寸發生變化時發生重繪,我們可以使用用位或|操作符將其組合起來) //WNDPROC 函數指針類型 wc.lpfnWndProc = WindowProc;//窗口回調函數/窗口處理函數 wc.cbClsExtra = 0;//窗口類的附加內存大小 wc.cbWndExtra = 0;//窗口附加內存大小 wc.hInstance = hInstance;//當前應用程序實例句柄 wc.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON1));//加載自定義的圖標句柄 //wc.hCursor = LoadCursor(NULL,IDC_CROSS);//光標句柄;加載系統光標,也可以采用下面的方式加載自定義的光標 wc.hCursor = LoadCursor(hInstance, MAKEINTRESOURCE(IDC_CURSOR1)); wc.hbrBackground = CreateSolidBrush(RGB(255, 255, 0));//紅綠藍三原色0~255,[0最暗,255最亮] wc.lpszMenuName = NULL;//菜單名 wc.lpszClassName = szAppclassName;//窗口類型名 spy++(vs->工具選項)
其中的第二個成員變量lpfnWndProc是一個函數指針,指向窗口過程函數,窗口過程函數時一個回調函數。該函數簽名如下:
WNDPROC lpfnWndProc;
typedef LRESULT (CALLBACK* WNDPROC)(HWND, UINT, WPARAM, LPARAM);
故窗口函數按照如下簽名定義,實現如下:
//窗口處理函數 //第一個參數:當前窗口句柄 //第二個參數:消息類型 //第三個參數:附加消息、附加操作 //第四個參數:附加消息、附加操作 LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_CLOSE://窗口關閉消息 DestroyWindow(hWnd);//銷毀窗口,干掉界面,不會發出WM_QUIT,會發出一個WM_DESTORY消息 break; case WM_QUIT://窗口銷毀消息 PostQuitMessage(0);//發布WM_QUIT break; default: break; } return DefWindowProc(hWnd, uMsg, wParam, lParam); }
回調函數機制:
注冊窗口類
通過ATOM WINAPI RegisterClass( _In_ CONST WNDCLASS*lpWndClass);進行窗口類注冊。形參為WNDCLASS地址。返回值為ATOM.
創建窗口
創建窗口通過CreateWindow函數來實現,該函數API信息如下:

void CreateWindow( lpClassName, lpWindowName, dwStyle, x, y, nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam );
關於返回值:
類型:HWND
如果函數成功,則返回值是新窗口的句柄。
如果函數失敗,則返回值為NULL。要獲取擴展錯誤信息,請調用GetLastError。

//WS:window style HWND hWnd = CreateWindow(szAppclassName, //窗口類型名 _T("這個是我的第一個windows應用程序") //窗口左上角標題 , WS_BORDER | WS_CAPTION | WS_MAXIMIZEBOX | WS_SYSMENU, //窗口的風格 200, 300, //窗口左上角坐標 800, 600, // //窗口寬和高 NULL, //父窗口句柄 NULL, //菜單句柄 hInstance, //應用程序實例句柄 NULL); //創建窗口的附加參數,WM_CREATE消息,lparam來接受這個參數 if (NULL == hWnd) { MessageBox(NULL, _T("創建窗口失敗"), _T("提示"), MB_OK); }
顯示窗口&更新窗口
創建完窗口后,要對窗口進行具體的顯示。
顯示函數API如下:
BOOL ShowWindow( HWND hWnd,//創建窗口后返回的窗口句柄 int nCmdShow //指定窗口顯示的狀態,包括SW_MAXIMIZE SW_MINIMIZE SW_NORMAL SW_HIDE 最大化、最小化、正常、隱藏顯示 );
在調用ShowWindow函數之后,需要調用UpdateWindow來刷新窗口。
UpdateWindow(hWnd);//hWnd是創建成功后的窗口句柄
注意:UpdateWindow函數通過發送一個WM_PAINT消息來刷新窗口,UpdateWindow將WM_PAINT消息直接發送給了窗口過程函數進行處理,而沒有放到消息隊列中。
消息循環
在創建窗口、顯示窗口、更新窗口后,我們需要編寫一個消息循環,調用GetMessage函數不斷從消息隊列中取出消息,並進行相應。
消息結構體:

// typedef struct tagMSG { //HWND hwnd; //消息發向窗口的窗口句柄(指的是這個消息發個哪個窗口了,這里指定這個窗口的句柄) //UINT message; //消息編號 //WPARAM wParam; //附加消息 //LPARAM lParam;//附加消息 //DWORD time;//消息放入消息隊列的時間 //POINT pt;//消息放入消息隊列時鼠標坐標
BOOL GetMessage( LPMSG lpMsg,//消息結構體地址 HWND hWnd,//窗口句柄 UINT wMsgFilterMin,//要獲取的消息的最小值,通常設置為0 UINT wMsgFilterMax//要獲取的消息的最大值。如果FiterMin,Max兩者都為0,則接收所有消息 );
代碼如下:
MSG msg; //GetMessage:何時返回FALSE //當獲取到WM_QUIT消息時,返回FALSE,沒有獲取到這個消息時,返回非0,不會退出循環 while (GetMessage(&msg, NULL, 0, 0)) { //將虛擬鍵消息轉換為字符消息 TranslateMessage(&msg); //將消息分發給窗口處理函數 DispatchMessage(&msg); }
此處的Windows應用消息的消息處理機制如下圖:
(1)os接收到應用消息的窗口消息【比如當用戶在窗口中畫圖的時候,按下鼠標左鍵,此時os會感知到這一事件,於是將此事件包裝成一個消息】,將消息投遞到該應用消息的消息隊列中。
(2)應用程序在消息循環中調用GetMessage函數從消息隊列中取出一條一條的消息。取出消息后,應用程序可以對消息進行一些預處理,比如放棄對某些消息的相應或調用TranslateMessage產生新的消息。
(3)應用程序調用DispatchMessage將消息回傳給os。消息MSG結構體中包含接受消息的窗口的句柄。因此DispatchMessage函數總能進行正確傳遞。
(4)os利用WNDCLASS結構體的lpfnWndProc成員保存的窗口過程函數的指針調用窗口過程,對消息進行處理(即“系統給應用程序發送了消息”)。
附錄完整代碼

#include<Windows.h> #include<tchar.h> #include"resource.h" //LRESULT :long // UINT:unsigned int //WPARAM:unsigned int //LPARAM:unsigned int //LPCWSTR:const w_chart_t *; // typedef WORD ATOM; //BUGBUG - might want to remove this from minwin // typedef unsigned short WORD; // typedef unsigned long DWORD; //LPCWSTR const w_char_t * 寬字符 //LPCSTR const char * //窗口處理函數 //第一個參數:當前窗口句柄 //第二個參數:消息類型 //第三個參數:附加消息、附加操作 //第四個參數:附加消息、附加操作 LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_CLOSE://窗口關閉消息 DestroyWindow(hWnd);//銷毀窗口,干掉界面,不會發出WM_QUIT,會發出一個WM_DESTORY消息 break; case WM_QUIT://窗口銷毀消息 PostQuitMessage(0);//發布WM_QUIT break; default: break; } return DefWindowProc(hWnd, uMsg, wParam, lParam); } int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPreInstance, LPSTR lpCmdLine, int nCmdShow) { //創建一個窗口的流程 //1設計窗口類 /* typedef struct tagWNDCLASSW { UINT style; WNDPROC lpfnWndProc; int cbClsExtra; int cbWndExtra; HINSTANCE hInstance; HICON hIcon; HCURSOR hCursor; HBRUSH hbrBackground; LPCWSTR lpszMenuName; LPCWSTR lpszClassName; } WNDCLASSW,*/ //typedef WNDCLASSW WNDCLASS; //LRESULT CALLBACK WindowProc( // _In_ HWND hwnd, // _In_ UINT uMsg, // _In_ WPARAM wParam, // _In_ LPARAM lParam //); //typedef LRESULT(CALLBACK* WNDPROC)(HWND, UINT, WPARAM, LPARAM); wchar_t szAppclassName[] = _T("FirstWin32"); WNDCLASS wc; wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;//窗口類的風格 //WNDPROC 函數指針類型 wc.lpfnWndProc = WindowProc;//窗口回調函數/窗口處理函數 wc.cbClsExtra = 0;//窗口類的附加內存大小 wc.cbWndExtra = 0;//窗口附加內存大小 wc.hInstance = hInstance;//當前應用程序實例句柄 wc.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON1));//加載自定義的圖標句柄 //wc.hCursor = LoadCursor(NULL,IDC_CROSS);//光標句柄;加載系統光標,也可以采用下面的方式加載自定義的光標 wc.hCursor = LoadCursor(hInstance, MAKEINTRESOURCE(IDC_CURSOR1)); wc.hbrBackground = CreateSolidBrush(RGB(255, 255, 0));//紅綠藍三原色0~255,[0最暗,255最亮] wc.lpszMenuName = NULL;//菜單名 wc.lpszClassName = szAppclassName;//窗口類型名 spy++(vs->工具選項) //2注冊窗口類 //返回值ATOM if (0 == RegisterClass(&wc)) { MessageBox(NULL, _T("此程序不能復制在winNT平台"), _T("提示"), MB_OK); return 0; } //3創建窗口 //WS:window style HWND hWnd = CreateWindow(szAppclassName, //窗口類型名 _T("這個是我的第一個windows應用程序") //窗口左上角標題 , WS_BORDER | WS_CAPTION | WS_MAXIMIZEBOX | WS_SYSMENU, //窗口的風格 200, 300, //窗口左上角坐標 800, 600, // //窗口寬和高 NULL, //父窗口句柄 NULL, //菜單句柄 hInstance, //應用程序實例句柄 NULL); //創建窗口的附加參數,WM_CREATE消息,lparam來接受這個參數 if (NULL == hWnd) { MessageBox(NULL, _T("創建窗口失敗"), _T("提示"), MB_OK); } //4顯示窗口 //SW_SHOW:原來在何處顯示,就在此處顯示 //SW_MAXIMIZE SW_MINIMIZE SW_NORMAL SW_HIDE 最大化、最小化、正常、隱藏顯示 ShowWindow(hWnd, SW_SHOW); //5更新窗口 UpdateWindow(hWnd); //6消息循環 // typedef struct tagMSG { //HWND hwnd; //消息發向窗口的窗口句柄(指的是這個消息發個哪個窗口了,這里指定這個窗口的句柄) //UINT message; //消息編號 //WPARAM wParam; //附加消息 //LPARAM lParam;//附加消息 //DWORD time;//消息放入消息隊列的時間 //POINT pt;//消息放入消息隊列時鼠標坐標 //windows程序都是通過消息機制驅動運行的。 MSG msg; //GetMessage:何時返回FALSE //當獲取到WM_QUIT消息時,返回FALSE,沒有獲取到這個消息時,返回非0,不會退出循環 while (GetMessage(&msg, NULL, 0, 0)) { //將虛擬鍵消息轉換為字符消息 TranslateMessage(&msg); //將消息分發給窗口處理函數 DispatchMessage(&msg); } MessageBox(NULL, _T("這是第一個win32應用程序"), _T("提示"), MB_OK); return 0; }