簡介
本文目的是介紹Windows編程基礎。在本文結束時,你應該能夠很好的工作了,雖然可能是簡單的WIindows程式。你需要有C語言的基礎知識,我很少將C++的代碼擴充到程式中。當然,由於Windows本身就是物件導向的,一點類的知識是不會對你有什麼損害的。如果你不熟悉C++,沒有關係,我想你還是能從我這裡學到大部分的東西。所有的程式碼都通過了Microsoft Visual C++6.0的編譯,如果你還沒有合適的編譯器,弄一個同我一樣的好了,它還是很棒的。開動吧!
開始
多數的Windows程式都需要Windows.h和Windowsx.h這兩個標頭檔,要確保使用它們。當然,你還需要其它的標準的C的標頭檔,象stdio.h,conio.h等。除了這些,你還會經常看到在程式的開始有這樣一行代碼:
#define WIN32_LEANAND_MEAN |
它表示Windows的標頭檔中將拒絕接受MFC的東西,這將加速你的build時間。如果你從沒有打算應用MFC在你的遊戲編程中,那就使用它吧。如果你以前從沒有看過這種宣告類型——在#define後,直接加上一個“單詞”,那麼它的作用就是有條件編譯。看看下面的例子:
#ifdef DEBUG_MODE printf("Debug mode is active!"); #endif |
意思是:如果程式的開始包含#define DEBUG_MODE,那麼就printf(),否則退出。這個對於你跟蹤程式的邏輯錯誤是很有協助的。
WinMain()函數
DOS下的C語言從main()開始,Windows下的C語言從WinMain()開始,一個空的WinMain()函數是這樣的:
int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { return(0); } |
一個函數即使什麼也沒做,也應該返回一個值。是的,有好多東西我們不熟悉。首先的首先,WINAPI是個什麼聲明?WINAPI是在windows.h標頭檔中定義的一個宏,它把函數調用翻譯成正確的呼叫慣例。當我們在程式中需要用到組合語言的時候,我們在來深究它好了,記住,如果要用WinMain(),就必須要有WINAPI。
下一步讓我們來看看括弧裡的四個參數:
◎ HINSTANCE hinstance:HINSTANCE是一個控制代碼類型的標識符。變數hinstance是一個整數,用於標識程式執行個體。Windows設定這個參數的值,並把它傳遞給你的程式碼。很多Windows函數都要用到它。
◎ HINSTANCE hPreInstance:你不用擔心這個參數,它已經被廢掉了。它只是為古老的Windows版本服務的。你將還會看到類似的情況。
◎ LPSTR lpCmdLine:是一個指向字串的指標,它僅在程式名是從DOS命令列輸入或是從Run對話方塊中輸入時才起作用。因此,很少被程式碼所用。
◎ int nCmdShow:決定了視窗在初始顯示時的狀態。Windows通常給這個參數分配一個值。通常是SW_打頭的一個常量。例如SW_SHOWNORMAL表示預設狀態,SW_MAXINIZE或SW_MINIMIZE分別表示最大和最小模式等等。
訊息
當你在DOS下編程的時候,你不必擔心其它程式的運行,因為DOS是獨佔模式。但你在Windows平台上編程時,你不得不考慮其它正在啟動並執行程式。鑒於此,Windows通過“訊息”來串連操作申請和具體操作。簡單的說,就是我們指示程式或程式本身向Windows發出諸如移動視窗、放大視窗、關閉視窗等地申請,Windows再根據申請,考察實地情況,拒絕或發出指令,讓程式(電腦)作出相應的動作。再例如,滑鼠隨時向Windows發出訊息,彙報游標位置,左鍵或右鍵是否按下等,Windows再根據訊息作出相應的反應。總之,無論何時,Windows都要隨時掌控所有的訊息,而且,Windows是一直不斷地接收到各種訊息。
這種功能是通過一種被命名為CALLBACK函數類型實現的。不用害怕,訊息的傳遞來,傳遞去都是由Windows自己完成的,你只要聲明一個CALLBACK函數就可以了,就像WINAPI用在WinMain()前一樣。如果還沒有明白,不要緊,往下看你就明白了。現在,我要離開這個話題一會,因為你只有先建立視窗(Windows),傳遞訊息才有可能實現。
視窗類別
現在談論一點C++的知識,因為要想建立一個視窗,你就得先建立一個視窗類別。視窗類別包含所有的有關視窗的資訊,如用什麼樣的滑鼠符號,菜單樣式等等。開發任何一個視窗程序,都離不開視窗類別的建立。為了達到此目的,你必須填寫WNDCLASSEX結構。EX的意思是“擴充”的意思,因為有一個老的結構叫作WNDCLASS,這裡,我們將使用WNDCLASSEX結構,它的樣子如下:
typedef struct _WNDCLASSEX { UINT cbSize; UINT style; WNDPROC lpfnWndProc; int cbClsExtra; int cbWndExtra; HANDLE hInstance; HICON hIcon; HCURSOR hCursor; HBRUSH hbrBackground; LPCTSTR lpszMenuName; LPCTSTR lpszClassName; HICON hIconSm; } WNDCLASSEX; |
這個結構有不少成員,討厭的是,你必須為視窗類別設定每一個成員。莫發愁,紙老虎一個。讓我們來個速成。
※ UINT cbSize:指定了以位元組為單位的結構的大小。這個成員是通過sizeof(WNDCLASSEX)實現的。你將會經常看到它,尤其是你使用了DirectX。
※ UINT style:指定了視窗的風格。它經常被以CS_打頭的符號常量定義。兩種或兩種以上的風格可以通過C語言中的“或”(|)運算子加以組合。大多數情況我們只應用四種風格,出於對文章長度的考慮,我們只列出這四種。若你還需要其它的,到MSDN裡找一下好了。別告訴我你用的不是Visual C++啊!
◎ CS_HREDRAW:一旦移動或尺寸調整使客戶區的寬度發生變化,就重新繪製視窗。
◎ CS_VREDRAW:一旦移動或尺寸調整使客戶區的高度發生變化,就重新繪製視窗。
◎ CS_OWNDC:為該類中的每一個視窗分配一個唯一的裝置上下文。
◎ CS_DBLCLKS:當使用者雙擊滑鼠時向視窗過程發送雙擊訊息。
※ WNDPROC lpfnWndProc:是指向視窗過程的指標。一般都指向CALLBACK函數。如果你沒有用過函數指標,簡單理解為函數的地址就是函數的名字,名字後面別帶括弧。
※ int cbClsExtra:它是為類保留的額外資訊 。大多數程式員不用它,你在在寫遊戲程式時也不太可能用它,所以,設為0好了。
※ int cbWndExtra:同上一個差不多,設為0好了。
※ HANDLE hInstance:是指向視窗過程執行個體的控制代碼。同時也是WinMain()函數的參數之一。應該設定為hinstance。
※ HICON hIcon:指向視窗表徵圖的控制代碼,它通常被LoadIcon()函數設定。在你學會如何在你的程式中使用資源前,你先設定成如下樣子:LoadIcon(NULL,IDI_WINLOGO)。當然,還有一些其它的IDI_打頭的符號常量,你自己去協助檔案裡找吧。
※ HCURSOR hCursor:指向視窗游標的控制代碼,它通常被LoadCursor()函數設定,在你學會如何在你的程式中使用資源前,你先用Windows預設的吧,LoadCursor(NULL,IDC_ARROW)。
※ HBRUSH hbrBackground:當你的視窗過程得到訊息,要求重新整理(或重畫)視窗時,至少要用一種純色或“brush”(畫刷)重畫視窗地區,畫刷是由參數確定的。你可以使用GetStockObject()函數調用幾種常備的畫刷,如BLACK_BRUSH, WHITE_BRUSH, GRAY_BRUSH等。現在,你就用GetStockObject(BLACK_BRUSH)吧。對不起,你可能覺得我說的太簡單了,但我不想把開始弄得太複雜。我在以後的篇幅裡會詳細講的,我保證。
※ LPCTSTR lpszMenuName:如果你想建立一個有下拉式功能表的視窗,你得給這個參數賦一個菜單名稱(這涉及到資源),由於你還不知道怎麼建立菜單,你就先用NULL設定成一個沒有菜單的視窗吧。
※ LPCSTR lpszClassName:很顯然,你需要給類起個名字,隨你便,如“**”。要用雙引號引上啊!
※ HICON hIconSm:指向小表徵圖的控制代碼。小表徵圖用來顯示在視窗的標題列裡。要用到LoadIcon()函數,現在,你就用Windows預設的吧,LoadIcon(NULL,IDI_WINLOGO)。
好了,你關於WNDCLASSEX結構知道的差不多了,你可以自己設定它了。下面是一個例子:
WNDCLASSEX sampleClass; // declare structure variable sampleClass.cbSize = sizeof(WNDCLASSEX); // always use this! sampleClass.style = CS_DBLCLKS | CS_OWNDC | CS_HREDRAW | CS_VREDRAW; // standard settings sampleClass.lpfnWndProc = MsgHandler; // we need to write this! sampleClass.cbClsExtra = 0; // extra class info, not used sampleClass.cbWndExtra = 0; // extra window info, not used sampleClass.hInstance = hinstance; // parameter passed to WinMain() sampleClass.hIcon = LoadIcon(NULL, IDI_WINLOGO); // Windows logo sampleClass.hCursor = LoadCursor(NULL, IDC_ARROW); // standard cursor sampleClass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); // a simple black brush sampleClass.lpszMenuName = NULL; // no menu sampleClass.lpszClassName = "Sample Class" // class name sampleClass.hIconSm = LoadIcon(NULL, IDI_WINLOGO); // Windows logo again ...... |
我想,你已經有點兒不太崇拜Windows程式員了。言歸正傳,有一點我得提醒你,注意函數GetStockObject()前的(HBRUSH)類型配置,這是因為GetStockObject()可以調用其它的對象,不僅僅是“brush”,所以你需要一個HBRUSH類型配置。在Visual C++舊版本裡不用配置,但新的6.0版本需要它,否則會編譯出錯。
下一件事是註冊這個視窗類別,只有這樣,你才能建立新的視窗。十分簡單,你只需要調用一個RegisterClassEX()函數,它只有一個參數,就是你的視窗類別的地址(名字),根據我上面給的例子,這裡應該這樣:
RegisterClassEx(&sampleClass); |
嗨,我們的視窗類別建立完了,我們可以用它建立一個視窗了。只是時間問題嘍!
建立視窗
好訊息,建立視窗你所要做的只是調用一個CreateWindowEx()函數。壞訊息是,這個函數有好多的參數。嘿!把刀放下,有話好說嗎!真的不難,做事情總得走走形式嘛!以下是函數原形:
HWND CreateWindowEx( DWORD dwExStyle, // extended window style LPCTSTR lpClassName, // pointer to registered class name LPCTSTR lpWindowName, // pointer to window name DWORD dwStyle, // window style int x, // horizontal position of window int y, // vertical position of window int nWidth, // window width int nHeight, // window height HWND hWndParent, // handle to parent or owner window HMENU hMenu, // handle to menu, or child-window identifier HINSTANCE hInstance, // handle to application instance LPVOID lpParam // pointer to window-creation data ); |
首先的首先:函數的傳回值。也就是函數的類型。是不是所有建立視窗用的函數的類型的討厭樣子都感覺親切了一點兒?還沒有?不要緊,你會習慣的,肯定比你想象的速度要快。這裡返回的類型是HWND,是一個視窗的控制代碼(控制代碼就是視窗的標識符)。你將把CreateWindowEx()的傳回值傳遞給一個視窗的控制代碼,就像一個參數一樣。現在,我們來琢磨一下這些參數,很多根據名字就知道它是幹什麼的了。
※ DWORD dwExStyle:擴充的視窗風格。你將很少使用擴充的視窗風格,所以多數時間你會把它設定為NULL。如果有興趣,查一下協助檔案,可以一試由WS_EX_打頭的擴充風格。
※ LPCTSTR lpClassName:還記得你的視窗類別的名稱嗎?再用一次。
※ LPCTSTR lpWindowName:將顯示在視窗的標題列裡的簡短文字。
※ DWORD dwStyle:視窗的風格。它將允許你詳細的描繪你所要建立的視窗的風格。有很多風格你可以利用哦,都是以WS_打頭的,你可以利用(|)符號組合利用它們。我將在這兒介紹幾個常用的。
◎ WS_POPUP 指定一個彈出的視窗。
◎ WS_OVERLAPPED 指定一個具有標題列和邊界的重疊的視窗。
◎ WS_OVERLAPPEDWINDOW 指定一個具有所有標準控制項的視窗。
◎ WS_VISIBLE 指定一個初始時可見的視窗。
看得出,WS_OVERLAPPEDWINDOW是一個組合體。簡單的說,你可以按照如下規律:如果你要建立一個可以最大化、最小化、隨意改變大小等等地視窗,就選擇WS_OVERLAPPEDWINDOW;如果你只想要一個具有標題列、可改變大小的視窗,就選擇WS_OVERLAPPED;如果你只想要一個光禿禿的視窗,就選擇WS_POPUP;如果你只想顯示一個黑色的大方框,可能你要用它寫一個全屏的遊戲,選擇WS_VISIBLE是沒錯的。
※ int x,y:你所要建立的視窗的左上方的座標。
※ int nWidth,nHeight:猜也猜到了,視窗的長和高,單位是『象素』。
※ HWND hWndParent:指向父視窗的控制代碼。你若想在視窗下再建立一個視窗,那麼第一個視窗就叫父視窗。咱先建立一個主視窗,所以設定為NULL,也就意味著Windows案頭是父視窗。
※ HMENU hMenu:這是用在視窗上的菜單控制代碼。若你學會建立和使用資源,即建立自己的菜單,你可以用LoadMenu()函數調用自己的菜單資源。目前,咱先設為NULL。
※ HINSTANCE hInstance:是一個名柄,它指向由Windows傳遞給WinMain()的執行個體。
※ LPVOID lpParam:對於遊戲編程來說,沒有什麼用的東西,只有簡單的視窗程序用到它。設定為NULL好了。
同志們,我們現在萬事具備,東風也有了。我先給個樣本:
HWND hwnd; if (!(hwnd = CreateWindowEx(NULL, // extended style, not needed "Sample Class", // class identifier "Sample Window", // window title WS_POPUP | WS_VISIBLE,// parameters 0, 0, 320, 240, // initial position, size NULL, // handle to parent (the desktop) NULL, // handle to menu (none) hinstance, // application instance handle NULL))) // who needs it? return(0); |
你可能會在遊戲編程中用上這這段代碼,因為它是一個彈出式視窗。注意,我用了if形式,目的是一旦CreateWindowsEX()函數失靈,返回一個NULL,也就意味著如果視窗由於某種原因不能被建立,那麼WinMain()就被簡單的返回,程式結束。
現在我們學會了足夠的知識建立一個小有功能的視窗了。還記得我們建立視窗類別“sample class”時,一個指向“CALLBACK”類型函數的指標嗎?對,是“lpfnWndProc”。要想讓你的視窗真正做點事兒,我們還得來處理一下它指向的“視窗過程”函數。
顯示視窗
CreateWindowEx()從內部建立視窗,但並不顯示它。要顯示這個視窗,必須調用另外兩個函數:ShowWindow()和UpdateWindow()。頭一個設定視窗的顯示狀態,後一個則更新視窗的客戶區。對於程式的主視窗,ShowWindow()必須被調用一次,調用代碼如下:
ShowWindow(hwnd,nCmdShow); |
第一個參數是由CreateWindowEx()函數返回的視窗控制代碼;第二個參數就是視窗的顯示模式參數,在☆WinMain()函數中提到過,就不重複了。UpdateWindow()函數的調用代碼如下:
參數hwnd同ShowWindow()函數的hwnd一樣。