盡管Windows應(yīng)用程序千變?nèi)f化,令人眼花繚亂,但,消息機(jī)制和窗口過程卻始終它們的基礎(chǔ),掌握了這兩項(xiàng)技術(shù),也就相當(dāng)于把握住了問題的關(guān)鍵。
如果你以前是C程序員或是MFC的忠實(shí)用戶,只要你學(xué)習(xí)過C語言的語法,自己親手編過一些簡短的C程序,理解以下的Win32編程基礎(chǔ)也不是一件困難的事。
一個(gè)最簡單的Win32程序
在以前的C語言編程中,一個(gè)最簡單的程序可以只有兩行。
void main(void) { printf "Hello World!"; } |
而要實(shí)現(xiàn)同樣功能的Windows程序卻最少也要寫幾十行,這并不是說明Windows應(yīng)用程序效率低下,難于掌握,只是說明程序在Windows環(huán)境下有更豐富的內(nèi)涵。Windows程序的效率其實(shí)不低,在所有的Windows應(yīng)用程序中,都有一個(gè)程序初始化的過程,這得用上幾十條語句,這段初始化的代碼對(duì)于任何Windows應(yīng)用程序而言,都是大同小異的。下面以一個(gè)實(shí)現(xiàn)最簡單功能的程序EasyWin為例,說明Windows程序的基本框架。
打開Visual C++ 6.0。
選擇File菜單的New,在出現(xiàn)的對(duì)話框中,選擇Projects欄目(新建工程),并點(diǎn)取其下的Win32 Application項(xiàng),表示使用Win32環(huán)境創(chuàng)建應(yīng)用程序。先在Locatin(路徑)中填入“c:\”,然后在Project Name(項(xiàng)目名稱)中填入“EasyWin”,其它按照缺省設(shè)置)。單擊OK按鈕。
再次選擇File菜單的New,在出現(xiàn)的對(duì)話框中,選擇Files欄目(新建文件),并點(diǎn)取其下的C++ Source File項(xiàng),表示新建一個(gè)C++源文件。在右邊的File欄中輸入“EasyWin”,最后確定讓Add to project檢查框打上勾 )。單擊OK按鈕。
在EasyWin.cpp文件中輸入以下源程序代碼。
//******************************************************************* // 工程:easywin // 文件:easywin.cpp // 內(nèi)容:一個(gè)基本的Win32程序//******************************************************************* #include <windows.h> #include <windowsx.h> //函數(shù)聲明 BOOL InitWindow( HINSTANCE hInstance, int nCmdShow ); LRESULT CALLBACK WinProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam ); //******************************************************************* //函數(shù):WinMain() //功能:Win32應(yīng)用程序入口函數(shù)。創(chuàng)建主窗口,處理消息循環(huán) //******************************************************************* int PASCAL WinMain( HINSTANCE hInstance, //當(dāng)前實(shí)例句柄 HINSTANCE hPrevInstance, //前一個(gè)實(shí)例句柄 LPSTR lpCmdLine, //命令行字符 int nCmdShow) //窗口顯示方式 { MSG msg; //創(chuàng)建主窗口 if ( !InitWindow( hInstance, nCmdShow ) ) return FALSE; //進(jìn)入消息循環(huán): //從該應(yīng)用程序的消息隊(duì)列中檢取消息,送到消息處理過程, //當(dāng)檢取到WM_QUIT消息時(shí),退出消息循環(huán)。 while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } //程序結(jié)束 return msg.wParam; } //****************************************************************** //函數(shù):InitWindow() //功能:創(chuàng)建窗口。 //****************************************************************** static BOOL InitWindow( HINSTANCE hInstance, int nCmdShow ) { HWND hwnd; //窗口句柄 WNDCLASS wc; //窗口類結(jié)構(gòu) //填充窗口類結(jié)構(gòu) wc.style = CS_VREDRAW | CS_HREDRAW; wc.lpfnWndProc = (WNDPROC)WinProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon( hInstance, IDI_APPLICATION ); wc.hCursor = LoadCursor( NULL, IDC_ARROW ); wc.hbrBackground = GetStockObject(WHITE_BRUSH); wc.lpszMenuName = NULL; wc.lpszClassName = "EasyWin"; //注冊(cè)窗口類 RegisterClass( &wc ); //創(chuàng)建主窗口 hwnd = CreateWindow( "EasyWin", //窗口類名稱 "一個(gè)基本的Win32程序", //窗口標(biāo)題 WS_OVERLAPPEDWINDOW, //窗口風(fēng)格,定義為普通型 100, //窗口位置的x坐標(biāo) 100, //窗口位置的y坐標(biāo) 400, //窗口的寬度 300, //窗口的高度 NULL, //父窗口句柄 NULL, //菜單句柄 hInstance, //應(yīng)用程序實(shí)例句柄 NULL ); //窗口創(chuàng)建數(shù)據(jù)指針 if( !hwnd ) return FALSE; //顯示并更新窗口 ShowWindow( hwnd, nCmdShow ); UpdateWindow( hwnd ); return TRUE; } //****************************************************************** //函數(shù):WinProc() //功能:處理主窗口消息 //****************************************************************** LRESULT CALLBACK WinProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam ) { switch( message ) { case WM_KEYDOWN://擊鍵消息 switch( wParam ) { case VK_ESCAPE: MessageBox(hWnd,"ESC鍵按下了!","Keyboard",MB_OK); break; } break; case WM_RBUTTONDOWN://鼠標(biāo)消息 { MessageBox(hWnd,"鼠標(biāo)右鍵按下了!","Mouse",MB_OK); break; } case WM_PAINT://窗口重畫消息 { char hello[]="你好,我是EasyWin !"; HDC hdc; PAINTSTRUCT ps; hdc=BeginPaint( hWnd,&ps ); //取得設(shè)備環(huán)境句柄 SetTextColor(hdc, RGB(0,0,255)); //設(shè)置文字顏色 TextOut( hdc, 20, 10, hello, strlen(hello) );//輸出文字 EndPaint( hWnd, &ps ); //釋放資源 break; } case WM_DESTROY://退出消息 PostQuitMessage( 0 );//調(diào)用退出函數(shù) break; } //調(diào)用缺省消息處理過程 return DefWindowProc(hWnd, message, wParam, lParam); } |
程序輸入完畢,即可編譯執(zhí)行。在窗口中擊鼠標(biāo)鍵或按ESC鍵時(shí),會(huì)彈出一個(gè)對(duì)話框以表示你的操作。
其實(shí),這個(gè)程序可以看成是所有Win32應(yīng)用程序的框架,在以后所有的程序中,你會(huì)發(fā)現(xiàn)它們都是在這個(gè)程序的基礎(chǔ)之上再添加代碼。
WinMain()函數(shù)
WinMain()函數(shù)是應(yīng)用程序開始執(zhí)行時(shí)的入口點(diǎn),通常也是應(yīng)用程序結(jié)束任務(wù)退出時(shí)的出口點(diǎn)。它與DOS程序的main()函數(shù)起同樣的作用,有一點(diǎn)不同的是,WinMain()函數(shù)必須帶有四個(gè)參數(shù),它們是系統(tǒng)傳遞給它的。WinMain()函數(shù)的原型如下:
int PASCAL WinMain( HINSTANCE hInstance, //當(dāng)前實(shí)例句柄 HINSTANCE hPrevInstance, //前一個(gè)實(shí)例句柄 LPSTR lpCmdLine, //命令行字符 int nCmdShow) //窗口顯示方式 |
第一個(gè)參數(shù)hInstance,是標(biāo)識(shí)該應(yīng)用程序當(dāng)前的實(shí)例的句柄。它是HINSTANCE類型,HINSTANCE是Handle of Instance的縮寫,表示實(shí)例的句柄。hInstance是一個(gè)很關(guān)鍵的數(shù)據(jù),它唯一的代表該應(yīng)用程序,在后面初始化程序主窗口的過程中需要用到這個(gè)參數(shù)。
這里有兩個(gè)概念,一個(gè)是實(shí)例,一個(gè)是句柄。實(shí)例代表的是應(yīng)用程序執(zhí)行的整個(gè)過程和方法,一個(gè)應(yīng)用程序如果沒有被執(zhí)行,只是存在于磁盤上,那么就說它是沒有被實(shí)例化的;只要一執(zhí)行,則說該程序的一個(gè)實(shí)例在運(yùn)行。句柄,顧名思義,指的是一個(gè)對(duì)象的把柄。在Windows中,有各種各樣的句柄,它們都是32位的指針變量,用來指向該對(duì)象所占據(jù)的內(nèi)存區(qū)。句柄的使用,可以極大的方便Windows管理其內(nèi)存中的各種對(duì)象。
第二個(gè)參數(shù)是hPrevInstance,它是用來標(biāo)識(shí)該應(yīng)用程序的前一個(gè)實(shí)例句柄。對(duì)于基于Win32的應(yīng)用程序來說,這個(gè)參數(shù)總是NULL。這是因?yàn)樵赪in95操作系統(tǒng)中,應(yīng)用程序的每個(gè)實(shí)例都有各自獨(dú)立的地址空間,即使同一個(gè)應(yīng)用程序被執(zhí)行了兩次,在內(nèi)存中也會(huì)為它們的每一個(gè)實(shí)例分配新的內(nèi)存空間,所以一個(gè)應(yīng)用程序被執(zhí)行后,不會(huì)有前一個(gè)實(shí)例存在的可能。也就是說,hPrevInstance這個(gè)參數(shù)是完全沒有必要的,只是為了提供與16位Windows的應(yīng)用程序形式上的兼容性,才保留了這個(gè)參數(shù)。在以前的16位Windows環(huán)境下(如Windows3.2),hPrevInstance用來標(biāo)識(shí)與hInstance相關(guān)的應(yīng)用程序的前一個(gè)句柄。
第三個(gè)參數(shù)是lpCmdLine,是指向應(yīng)用程序命令行參數(shù)字符串的指針。如在Win95的“開始”菜單中單擊“運(yùn)行”,輸入“easywin hello”,則此參數(shù)指向的字符串為“hello”。
最后一個(gè)參數(shù)是nCmdShow,是一個(gè)用來指定窗口顯示方式的整數(shù)。這個(gè)整數(shù)值可以是SW_SHOW、SW_HIDE、SW_SHOWMAXIMIZED、SW_SHOWMINIMIZED等,關(guān)于這些值的含義,將在下一節(jié)說明。
注冊(cè)窗口類
一個(gè)應(yīng)用程序可以有許多窗口,但只有一個(gè)是主窗口,它是與該應(yīng)用程序的實(shí)例句柄唯一關(guān)聯(lián)的。上面的例程中,創(chuàng)建主窗口的函數(shù)是InitWindow()。
通常要對(duì)填充一個(gè)窗口類結(jié)構(gòu)WNDCLASS,然后調(diào)用RegisterClass()對(duì)該窗口類進(jìn)行注冊(cè)。每個(gè)窗口都有一些基本的屬性,如窗口邊框、窗口標(biāo)題欄文字、窗口大小和位置、鼠標(biāo)、背景色、處理窗口消息函數(shù)的名稱等等。注冊(cè)的過程也就是將這些屬性告訴系統(tǒng),然后再調(diào)用CreateWindow()函數(shù)創(chuàng)建出窗口。這也就象你去裁縫店訂做一件衣服,先要告訴店老板你的身材尺寸、布料顏色、以及你想要的款式,然后他才能為你做出一件讓你滿意的衣服。
在VC的幫助中,可以看到WNDCLASS結(jié)構(gòu)是這樣定義的:
typedef struct _WNDCLASS { UINT style; //窗口的風(fēng)格* WNDPROC lpfnWndProc; //指定窗口的消息處理函數(shù)的遠(yuǎn)指針* int cbClsExtra; //指定分配給窗口類結(jié)構(gòu)之后的額外字節(jié)數(shù)* int cbWndExtra; //指定分配給窗口實(shí)例之后的額外字節(jié)數(shù) HANDLE hInstance; //指定窗口過程所對(duì)應(yīng)的實(shí)例句柄* HICON hIcon; //指定窗口的圖標(biāo) HCURSOR hCursor; //指定窗口的鼠標(biāo) HBRUSH hbrBackground; //指定窗口的背景畫刷 LPCTSTR lpszMenuName; //窗口的菜單資源名稱 LPCTSTR lpszClassName; //該窗口類的名稱* } WNDCLASS; |
在Win95和WinNT的具有新界面特性的系統(tǒng)中,為了支持新的窗口界面特性,還有一種擴(kuò)展的窗口類型WNDCLASSEX,它的定義如下:
typedef struct _WNDCLASSEX { UINT cbSize; //指定WNDCLASSEX結(jié)構(gòu)的大小 UINT style; WNDPROC lpfnWndProc; int cbClsExtra; int cbWndExtra; HANDLE hInstance; HICON hIcon; HCURSOR hCursor; HBRUSH hbrBackground; LPCTSTR lpszMenuName; LPCTSTR lpszClassName; HICON hIconSm; //窗口的小圖標(biāo) } WNDCLASSEX; |
WNDCLASS和WNDCLASSEX這兩個(gè)結(jié)構(gòu)基本上是一致的,只是WNDCLASSEX結(jié)構(gòu)中多了cbSize和hIconSm這兩個(gè)成員。WNDCLASS結(jié)構(gòu)的各成員中,其注釋后打了星號(hào)的表示該項(xiàng)應(yīng)特別注意。
WNDCLASS結(jié)構(gòu)的第一個(gè)成員style表示窗口類的風(fēng)格,它往往是由一些基本的風(fēng)格通過位的“或”操作(操作符位“|”)組合而成。下表列出了一些常用的基本窗口風(fēng)格:
風(fēng)格 | 含義 |
CS_HREDRAW | 如果窗口客戶區(qū)寬度發(fā)生改變,重繪整個(gè)窗口 |
CS_VREDRAW | 如果窗口客戶區(qū)高度發(fā)生改變,重繪整個(gè)窗口 |
CS_DBLCLKS | 能感受用戶在窗口中的雙擊消息 |
CS_NOCLOSE | 禁用系統(tǒng)菜單中的“關(guān)閉”命令 |
CS_OWNDC | 為該窗口類的各窗口分配各自獨(dú)立的設(shè)備環(huán)境 |
CS_CLASSDC | 為該窗口類的各窗口分配一個(gè)共享的設(shè)備環(huán)境 |
CS_PARENTDC | 指定子窗口繼承其父窗口的設(shè)備環(huán)境 |
CS_SAVEBITS | 把被窗口遮掩的屏幕圖象部分作為位圖保存起來。當(dāng)該窗口被移動(dòng)時(shí),Windows使用被保存的位圖來重建屏幕圖象 |
在EasyWin應(yīng)用程序中,是按如下方式對(duì)WNDCLASS結(jié)構(gòu)進(jìn)行填充和注冊(cè)的:
wc.style = CS_VREDRAW | CS_HREDRAW; wc.lpfnWndProc = (WNDPROC)WinProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon( hInstance, IDI_APPLICATION ); wc.hCursor = LoadCursor( NULL, IDC_ARROW ); wc.hbrBackground = GetStockObject(WHITE_BRUSH); wc.lpszMenuName = NULL; wc.lpszClassName = "EasyWin"; |
可以看到,wc.style被設(shè)為CS_VREDRAW | CS_HREDRAW,表示只要窗口的高度或?qū)挾劝l(fā)生變化,都會(huì)重畫整個(gè)窗口。
第二個(gè)成員lpfnWndProc的值為(WNDPROC)WinProc。表明該窗口類的消息處理函數(shù)是WinProc()函數(shù)。這里,要指定窗口的消息處理函數(shù)的遠(yuǎn)指針,輸入消息處理函數(shù)的函數(shù)名稱即可,必要時(shí)應(yīng)該進(jìn)行強(qiáng)制類型轉(zhuǎn)換,將其轉(zhuǎn)換成WNDPROC型。
接下來的cbClsExtra和wc.cbWndExtra在大多數(shù)情況下都會(huì)設(shè)為0。
然后的hInstance成員,給它的值是由WinMain()傳來的應(yīng)用程序的實(shí)例句柄,表明該窗口與該實(shí)例是相關(guān)聯(lián)的。事實(shí)上,只要是注冊(cè)窗口類,該成員的值始終是該程序的實(shí)例句柄,你應(yīng)該象背書一樣記住它。
下面的hIcon,是讓你給這個(gè)窗口指定一個(gè)圖標(biāo),調(diào)用 LoadIcon( hInstance, IDI_APPLICATION ),可以調(diào)用系統(tǒng)內(nèi)部預(yù)先定義好的標(biāo)志符為IDC_APPLICATION的圖標(biāo)作為該窗口的圖標(biāo)。
同樣,調(diào)用LoadCursor( NULL, IDC_ARROW )為該窗口調(diào)用系統(tǒng)內(nèi)部預(yù)先定義好的箭頭型鼠標(biāo)。
hbrBackground成員用來定義窗口的背景畫刷顏色,也就是該窗口的背景色。調(diào)用GetStockObject(WHITE_BRUSH)可以獲得系統(tǒng)內(nèi)部預(yù)先定義好的白色畫刷作為窗口的背景色。
上面的LoadIcon()、LoadCursor()、GetStockObject()都是Windows的API函數(shù),它們的用法可以參看VC的幫助,這里就不多介紹了。
lpszMenuName成員的值我們給它NULL,表示該窗口將沒有菜單。如果你想讓你的窗口擁有菜單,就把lpszMenuName成員賦值為標(biāo)志菜單資源的字符串。
WNDCLASS結(jié)構(gòu)的最后一個(gè)成員lpszClassName是讓你給這個(gè)窗口類起一個(gè)唯一的名稱,因?yàn)閃indows操作系統(tǒng)中有許許多多的窗口類,必須用一個(gè)獨(dú)一無二的名稱來代表它們。通常,你可以用你的程序名來命名這個(gè)窗口類的名稱。這個(gè)名稱將在創(chuàng)建窗口的CreateWindow()函數(shù)中用到。
填充完畢后,對(duì)于WNDCLASS結(jié)構(gòu),調(diào)用RegisterClass()函數(shù)進(jìn)行注冊(cè);對(duì)于WNDCLASSEX結(jié)構(gòu),調(diào)用RegisterClassEx()函數(shù)進(jìn)行注冊(cè),它們的原型分別如下:
ATOM RegisterClass( CONST WNDCLASS *lpWndClass ); ATOM RegisterClassEx( CONST WNDCLASSEX *lpwcx ); |
該函數(shù)如調(diào)用成功,則返回一個(gè)非0值,表明系統(tǒng)中已經(jīng)注冊(cè)了一個(gè)名為EasyWin的窗口類。如果失敗,則返回0。
創(chuàng)建窗口
當(dāng)窗口類注冊(cè)完畢之后,并不會(huì)有窗口顯示出來,因?yàn)樽?cè)的過程僅僅是為創(chuàng)建窗口所做的準(zhǔn)備工作。實(shí)際創(chuàng)建一個(gè)窗口的是通過調(diào)用CreateWindow()函數(shù)完成的。窗口類中已經(jīng)預(yù)先定義了窗口的一般屬性,而CreateWindow()中的參數(shù)可以進(jìn)一步指定一個(gè)窗口的更具體的屬性,在EasyWin程序中,是如下調(diào)用CreateWindow()函數(shù)來創(chuàng)建窗口的:
hwnd = CreateWindow( "EasyWin", //創(chuàng)建窗口所用的窗口類的名稱* "一個(gè)基本的Win32程序", //窗口標(biāo)題 WS_OVERLAPPEDWINDOW, //窗口風(fēng)格,定義為普通型* 100, //窗口位置的x坐標(biāo) 100, //窗口位置的y坐標(biāo) 400, //窗口的寬度 300, //窗口的高度 NULL, //父窗口句柄 NULL, //菜單句柄 hInstance, //應(yīng)用程序實(shí)例句柄* NULL ); //一般都為NULL |
CreateWindow()函數(shù)的參數(shù)的含義在上面的注釋中已有介紹,注釋后打了星號(hào)標(biāo)記的參數(shù)應(yīng)該著重注意,其它的參數(shù)都很簡單,不多做介紹,可參看VC的幫助。
第一個(gè)參數(shù)是創(chuàng)建該窗口所使用的窗口類的名稱,注意這個(gè)名稱應(yīng)與前面所注冊(cè)的窗口類的名稱一致。
第三個(gè)參數(shù)為創(chuàng)建的窗口的風(fēng)格,下表列出了常用的窗口風(fēng)格:
風(fēng)格 | 含義 |
WS_OVERLAPPEDWINDOW | 創(chuàng)建一個(gè)層疊式窗口,有邊框、標(biāo)題欄、系統(tǒng)菜單、最大最小化按鈕,是以下幾種風(fēng)格的集合:WS_OVERLAPPED, WS_CAPTION, WS_SYSMENU, WS_THICKFRAME, WS_MINIMIZEBOX, WS_MAXIMIZEBOX |
WS_POPUPWINDOW | 創(chuàng)建一個(gè)彈出式窗口,是以下幾種風(fēng)格的集合: WS_BORDER,WS_POPUP,WS_SYSMENU。WS_CAPTION與WS_POPUPWINDOW風(fēng)格必須一起使用才能使窗口菜單可見 |
WS_OVERLAPPED | 創(chuàng)建一個(gè)層疊式窗口,它有標(biāo)題欄和邊框,與WS_TILED風(fēng)格一樣 |
WS_POPUP | 該窗口為彈出式窗口,不能與WS_CHILD同時(shí)使用 |
WS_BORDER | 窗口有單線邊框 |
WS_CAPTION | 窗口有標(biāo)題欄 |
WS_CHILD | 該窗口為子窗口,不能與WS_POPUP同時(shí)使用 |
WS_DISABLED | 該窗口為無效,即對(duì)用戶操作不產(chǎn)生任何反應(yīng) |
WS_HSCROLL | 窗口有水平滾動(dòng)條 |
WS_ICONIC | 窗口初始化為最小化 |
WS_MAXIMIZE | 窗口初始化為最大化 |
WS_MAXIMIZEBOX | 窗口有最大化按鈕 |
WS_MINIMIZE | 與WS_MAXIMIZE一樣 |
WS_MINIMIZEBOX | 窗口有最小化按鈕 |
WS_SIZEBOX | 邊框可進(jìn)行大小控制的窗口 |
WS_SYSMENU | 創(chuàng)建一個(gè)有系統(tǒng)菜單的窗口,必須與WS_CAPTION風(fēng)格同時(shí)使用 |
WS_THICKFRAME | 創(chuàng)建一個(gè)大小可控制的窗口,與WS_SIZEBOX 風(fēng)格一樣. |
WS_TILED | 創(chuàng)建一個(gè)層疊式窗口,有標(biāo)題欄 |
WS_VISIBLE | 窗口為可見 |
WS_VSCROLL | 窗口有垂直滾動(dòng)條 |
程序中使用了WS_OVERLAPPEDWINDOW標(biāo)志,它是創(chuàng)建一個(gè)普通窗口常用的標(biāo)志。而在DirectX編程中,我們常用的是WS_POPUP,用這個(gè)標(biāo)志創(chuàng)建的窗口沒有標(biāo)題欄和系統(tǒng)菜單,如果設(shè)定窗口為最大化,客戶區(qū)可以占滿整個(gè)屏幕,以滿足DirectX編程的需要。
CreateWindow()函數(shù)后面的參數(shù)中,仍用到了該應(yīng)用程序的實(shí)例句柄hInstance。
如果窗口創(chuàng)建成功,返回值是新窗口的句柄,否則返回NULL。
顯示和更新窗口
窗口創(chuàng)建后,并不會(huì)在屏幕上顯示出來,要真正把窗口顯示在屏幕上,還得使用ShowWindow()函數(shù),其原型如下:
BOOL ShowWindow( HWND hWnd, int nCmdShow ); |
參數(shù)hWnd指定要顯示得窗口的句柄,nCmdShow表示窗口的顯示方式,這里指定為從WinMain()函數(shù)的nCmdShow所傳遞而來的值。
由于ShowWindow()函數(shù)的執(zhí)行優(yōu)先級(jí)不高,所以當(dāng)系統(tǒng)正忙著執(zhí)行其它的任務(wù)時(shí),窗口不會(huì)立即顯示出來,此時(shí),調(diào)用UpdateWindow()函數(shù)以可以立即顯示窗口。其函數(shù)原型如下:
BOOL UpdateWindow( HWND hWnd ); |
消息循環(huán)
在Win32編程中,消息循環(huán)是相當(dāng)重要的一個(gè)概念,看似很難,但是使用起來卻是非常簡單。在WinMain()函數(shù)中,調(diào)用InitWindow()函數(shù)成功的創(chuàng)建了應(yīng)用程序主窗口之后,就要啟動(dòng)消息循環(huán),其代碼如下:
while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } |
Windows應(yīng)用程序可以接收以各種形式輸入的信息,這包括鍵盤、鼠標(biāo)動(dòng)作 、記時(shí)器產(chǎn)生的消息,也可以是其它應(yīng)用程序發(fā)來的消息等等。Windows系統(tǒng)自動(dòng)監(jiān)控所有的輸入設(shè)備,并將其消息放入該應(yīng)用程序的消息隊(duì)列中。
GetMessage()函數(shù)則是用來從應(yīng)用程序的消息隊(duì)列中按照先進(jìn)先出的原則將這些消息一個(gè)個(gè)的取出來,放進(jìn)一個(gè)MSG結(jié)構(gòu)中去。GetMessage()函數(shù)原型如下:
BOOL GetMessage( LPMSG lpMsg, //指向一個(gè)MSG結(jié)構(gòu)的指針,用來保存消息 HWND hWnd, //指定哪個(gè)窗口的消息將被獲取 UINT wMsgFilterMin, //指定獲取的主消息值的最小值 UINT wMsgFilterMax //指定獲取的主消息值的最大值 ); |
GetMessage()將獲取的消息復(fù)制到一個(gè)MSG結(jié)構(gòu)中。如果隊(duì)列中沒有任何消息,GetMessage()函數(shù)將一直空閑直到隊(duì)列中又有消息時(shí)再返回。如果隊(duì)列中已有消息,它將取出一個(gè)后返回。MSG結(jié)構(gòu)包含了一條Windows消息的完整信息,其定義如下:
typedef struct tagMSG { HWND hwnd; //接收消息的窗口句柄 UINT message; //主消息值 WPARAM wParam; //副消息值,其具體含義依賴于主消息值 LPARAM lParam; //副消息值,其具體含義依賴于主消息值 DWORD time; //消息被投遞的時(shí)間 POINT pt; //鼠標(biāo)的位置 } MSG; |
該結(jié)構(gòu)中的主消息表明了消息的類型,例如是鍵盤消息還是鼠標(biāo)消息等,副消息的含義則依賴于主消息值,例如:如果主消息是鍵盤消息,那么副消息中則存儲(chǔ)了是鍵盤的哪個(gè)具體鍵的信息。
GetMessage()函數(shù)還可以過濾消息,它的第二個(gè)參數(shù)是用來指定從哪個(gè)窗口的消息隊(duì)列中獲取消息,其它窗口的消息將被過濾掉。如果該參數(shù)為NULL,則GetMessage()從該應(yīng)用程序線程的所有窗口的消息隊(duì)列中獲取消息。
第三個(gè)和第四個(gè)參數(shù)是用來過濾MSG結(jié)構(gòu)中主消息值的,主消息值在wMsgFilterMin和wMsgFilterMax之外的消息將被過濾掉。如果這兩個(gè)參數(shù)為0,則表示接收所有消息。
當(dāng)且僅當(dāng)GetMessage()函數(shù)在獲取到WM_QUIT消息后,將返回0值,于是程序退出消息循環(huán)。
TranslateMessage()函數(shù)的作用是把虛擬鍵消息轉(zhuǎn)換到字符消息,以滿足鍵盤輸入的需要。DispatchMessage()函數(shù)所完成的工作是把當(dāng)前的消息發(fā)送到對(duì)應(yīng)的窗口過程中去。
開啟消息循環(huán)其實(shí)是很簡單的一個(gè)步驟,幾乎所有的程序都是按照EasyWin的這個(gè)方法。你完全不必去深究這些函數(shù)的作用,只是簡單的照抄就可以了。
消息處理函數(shù)
消息處理函數(shù)又叫窗口過程,在這個(gè)函數(shù)中,不同的消息將用switch語句分配到不同的處理程序中去。Windows的消息處理函數(shù)都有一個(gè)確定的樣式,即這種函數(shù)的參數(shù)個(gè)數(shù)和類型以及其返回值的類型都有明確的規(guī)定。在VC的說明書中,消息處理函數(shù)的原型是這樣定義的:
LRESULT CALLBACK WindowProc( HWND hwnd, //接收消息窗口的句柄 UINT uMsg, //主消息值 WPARAM wParam, //副消息值 LPARAM lParam //副消息值 ); |
如果你的程序中還有其它的消息處理函數(shù),也都必須按照上面的這個(gè)樣式來定義,但函數(shù)名稱可以隨便取。EasyWin中的WinProc()函數(shù)就是這樣一個(gè)典型的消息處理函數(shù)。
消息處理函數(shù)的四個(gè)參數(shù)是由GetMessage()函數(shù)從消息隊(duì)列中獲得MSG結(jié)構(gòu),然后分解后得到的。第二個(gè)參數(shù)uMsg和MSG結(jié)構(gòu)中的message值是一致的,代表了主消息值。程序中用switch語句來將不同類型的消息分配到不同的處理程序中去。
WinProc()函數(shù)明確的處理了4個(gè)消息,分別是WM_KEYDOWN(擊鍵消息)、WM_RBUTTONDOWN(鼠標(biāo)右鍵按下消息)、WM_PAINT(窗口重畫消息)、WM_DESTROY(銷毀窗口消息)。
值得注意的是,應(yīng)用程序發(fā)送到窗口的消息遠(yuǎn)遠(yuǎn)不止以上這幾條,象WM_SIZE、WM_MINIMIZE、WM_CREATE、WM_MOVE等這樣頻頻使用的消息就有幾十條。為了減輕編程的負(fù)擔(dān),Windows的API提供了DefWindowProc()函數(shù)來處理這些最常用的消息,調(diào)用了這個(gè)函數(shù)后,這些消息將按照系統(tǒng)默認(rèn)的方式得到處理。
因此,在switch_case語句中,只須明確的處理那些有必要進(jìn)行特別響應(yīng)的消息,把其余的消息交給DefWindowProc()函數(shù)來處理,是一種明智的選擇,也是你必須做的一件事。
結(jié)束消息循環(huán)
當(dāng)用戶按Alt+F4或單擊窗口右上角的退出按鈕,系統(tǒng)就向應(yīng)用程序發(fā)送一條WM_DESTROY的消息。在處理此消息時(shí),調(diào)用了PostQuitMessage()函數(shù),該函數(shù)會(huì)給窗口的消息隊(duì)列中發(fā)送一條WM_QUIT的消息。在消息循環(huán)中,GetMessage()函數(shù)一旦檢索到這條消息,就會(huì)返回FALSE,從而結(jié)束消息循環(huán),隨后,程序也結(jié)束。
小結(jié)
本章介紹的是Win32編程的基礎(chǔ)知識(shí),在進(jìn)行DirectX編程之前,掌握它們是十分必要的。
通過本文的學(xué)習(xí),你應(yīng)該學(xué)到以下知識(shí):
如何創(chuàng)建一個(gè)Win32應(yīng)用程序工程
用RegisterClass()函數(shù)注冊(cè)一個(gè)窗口類,再立即調(diào)用CreateWindow()函數(shù)創(chuàng)建一個(gè)窗口的實(shí)例
設(shè)置窗口的類型以及將一個(gè)消息處理函數(shù)與窗口聯(lián)系上
用一固定的模式開啟消息循環(huán)
了解消息處理函數(shù)的定義規(guī)則,如何自己定義一個(gè)窗口消息處理函數(shù)
在消息處理函數(shù)中,最后必須調(diào)用DefWindowProc()函數(shù)以處理那些缺省的消息
調(diào)用PostQuitMessage()函數(shù)以結(jié)束消息循環(huán)