遊戲程式設計之DirectX的修煉:二(建立屬於自己的windows視窗程式:下)
上一節給我們寫了一個非常小的win32程式,雖然也是一個完整的win32程式,但是美中不足的是,是什麼那?就是我們使用的視窗是系統給我設計好的,所以我們現在要來設計一個自己的視窗,來裝載你的美麗的遊戲夢。
視窗這東西吧,說難也難,說簡單也不簡單,畢竟是鄙人花時間想出來的。但幸運的是,事實上理解起來並不困難,這世界難道還有比愛情更難理解的嘛?(開個玩笑)
在講怎麼去設計一個視窗的時候,我們先做個設想,假設,現在我們要去修一座房子。那麼首先要做什麼?所謂按圖索驥,老馬識圖(途),我們的有張圖紙,或者說模型吧,讓我們知道房子大概的框架對吧。同樣的,我們在設計視窗的時候自然也一樣,我們也得有一個樣式,或者說
說了這麼多我們來總結一下建立視窗地過程
1.要有一個設計的“模型”:我們使用的是WNDCLASSEX這個結構體,它包含了許多視窗需要的資訊,所謂設計,就是我們要為它賦值,它還有幾個兄弟姐妹像什麼WNDCLASS,但是這個已經是很久以前的了,往事隨風就由他去吧。(在這個WNDCALSSEX中有一個引數與後面要說的視窗資訊處理函式有關,先提一下)
2.找政府“註冊”:這個非常簡單,只需要呼叫一下RegisterClass()這個函式就ok啦。
3.開始建立視窗:呼叫一下CreateWindow,也是非常簡單的。
4.程式需要的一個主事件迴圈。
5.建立一個視窗資訊處理函式,與視窗關聯:WndProc函式,這個函式的名字是可以自己取的,但是記住它與前面WNDCLASSEX相關聯。
下面放一張圖,讓大家稍微對這個程式有點印象
以上就是一個視窗程式建立的大概過程,裡面有許多細節沒說到,畢竟我不想一上來說一大堆把自己都說蒙圈了,先讓大家有個底心裡,然後,我們就先把完整的程式放上來把。
//======================================================================================================//
//————————————————————————程式說明———————————————————————//
//程式名稱:FirstDemo
//2017.8.28 淡一抹夕霞
//======================================================================================================//
//==========================================//
//——————標頭檔案部分——————————//
#include<windows.h>
//==========================================//
//==========================================//
//——————函式宣告—————————--—//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);//視窗處理函式
//==========================================//
//===========================================================================================//
//—————--------------------—程式的主函式WinMain———-----------------------------——-//
//===========================================================================================//
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
///////////////////////////////////////
// //
//-----------設計視窗部分------------//
// //
///////////////////////////////////////
WNDCLASSEX wndclassex = {0};//建立一個視窗類,並且記得初始化
wndclassex.cbSize = sizeof(wndclassex);//節數大小
wndclassex.style = CS_VREDRAW | CS_HREDRAW;//樣式標記
wndclassex.lpfnWndProc = WndProc;//指向視窗事件處理函式的指標
wndclassex.hInstance = hInstance;//應用程式例項控制代碼
wndclassex.cbClsExtra = 0;//額外的類資訊
wndclassex.cbWndExtra = 0;//額外的視窗資訊
wndclassex.hIcon = LoadIcon(NULL,IDI_APPLICATION);//載入ico圖示
wndclassex.hCursor = ::LoadCursor(NULL, IDC_ARROW);//指定視窗類的游標控制代碼
wndclassex.hbrBackground = (HBRUSH)GetStockObject(GRAY_BRUSH);//為成員指定一個灰色畫刷
wndclassex.lpszMenuName = NULL;//要加入視窗的選單名
wndclassex.lpszClassName = L"WNDCALSS1";//視窗類名
///////////////////////////////////////
// //
//-----------註冊視窗部分------------//
// //
///////////////////////////////////////
RegisterClassEx(&wndclassex);//向我們的"政府申請註冊"
///////////////////////////////////////
// //
//-----------建立視窗部分------------//
// //
///////////////////////////////////////
HWND hWnd = CreateWindowEx(NULL, L"WNDCALSS1",L"MyWin32Window",//直接呼叫建立函式就好了
WS_OVERLAPPEDWINDOW|WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT,
800, 600, NULL, NULL, hInstance, NULL);
////////////////////////////////////////////////////////////////////////////////
ShowWindow(hWnd, nCmdShow);//顯示視窗
UpdateWindow(hWnd);//更新視窗
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////
// //
//------------主迴圈部分-------------//
// //
///////////////////////////////////////
MSG msg = {0};//定義meg
while(GetMessage(&msg, NULL, 0, 0)) //使用getmessage獲得訊息
{
TranslateMessage(&msg);//轉換訊息
DispatchMessage(&msg);//發訊息給程式,然後交給os呼叫視窗處理函式
}
return msg.wParam;
}
/////////////////////////////////////////////////
// //
//------------視窗訊息處理函式部分-------------//
// //
/////////////////////////////////////////////////
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
PAINTSTRUCT paintStruct;//定義一個paintstruct記錄一些繪製資訊
HDC hdc;//裝置環境控制代碼
switch (message)//開始處理
{
case WM_PAINT://重繪訊息 更新客戶區
hdc = BeginPaint(hWnd, &paintStruct);//指定視窗進行繪圖準備,並在ps結構中儲存相關資訊
TextOut(hdc,340,280,L"你的win32程式",9);//在螢幕上繪製一句話
EndPaint(hWnd, &paintStruct);//視窗繪圖過程結束
break;
case WM_KEYDOWN://鍵盤按下訊息
if (wParam == VK_ESCAPE)//如果是esc
DestroyWindow(hWnd);//銷燬視窗,傳送WM_DESTROY訊息
break;
case WM_DESTROY://銷燬訊息
PostQuitMessage(0);//向os申請終止請求。
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);//預設的視窗過程處理函式
}
return 0;
}
相信經過了上面一大堆的鋪墊,第一次看到這個程式的人也不會太不明白吧。我們就來詳細地分析下這個程式.
首先是設計部分:說白了我們就是在為WNDCLASSEX這個結構體賦值。
typedef struct tagWNDCLASSEX {
UINT cbSize;
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HINSTANCE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCTSTR lpszMenuName;
LPCTSTR lpszClassName;
HICON hIconSm;
}
大家看到這個結構體,各個引數的意義都在上面的程式碼中了。我們詳細講下前三個引數。
cbSize:為什麼明明是一個結構體,還需要整個引數記錄自己的大小?這是為了方便其他函式在呼叫時候不必計算這個結構的大小,直接跳到末尾。
style:這個引數用來描述視窗的常規屬性。它的值有很多,比如CS_HREDRAW:移動或者改變視窗寬度的時重繪整個視窗,
CS_VREDRAW:移動或者改變視窗高度的時重繪整個視窗,等等等等,詳細的可以去查閱。
lpdnWndProc:這個引數就非常重要了,這裡填寫的就是我們的視窗資訊處理函式的名字了。這是一個函式指標,指向我們的函式。而這個函式中可以寫許多我們自己想要實現的東西,比如在螢幕上畫個圈圈啊什麼的,它的工作方式就放到後面說吧。關於設計部分就說這麼多,
接下來是註冊部分和建立部分:就像我說的註冊就是呼叫一個函式我就不多說了,介紹下CreateWindowEX的引數
HWND WINAPI CreateWindowEx(
_In_ DWORD dwExStyle,//擴充套件視窗樣式
_In_opt_ LPCTSTR lpClassName,//類名指標
_In_opt_ LPCTSTR lpWindowName,//視窗指標
_In_ DWORD dwStyle,//視窗樣式
_In_ int x,//水平位置
_In_ int y,//垂直位置
_In_ int nWidth,//寬度
_In_ int nHeight,//高度
_In_opt_ HWND hWndParent,//父視窗控制代碼,一般不管填NULL
_In_opt_ HMENU hMenu,//選單控制代碼
_In_opt_ HINSTANCE hInstance,//應用程式實力控制代碼,就是winmain傳進來那個
_In_opt_ LPVOID lpParam//指向視窗建立資料的指標
);
嗯,CreateWindowEX建立視窗成功以後,會返回一個視窗控制代碼,型別是HWND,我們需要建立一個HWND的變數來儲存一下。這個變數很重要,視窗控制代碼再很多地方都需要使用!
大家也許發現了。在主事件迴圈與註冊視窗之間有兩句函式。
ShowWindow(hWnd,nCmdShow);//顯示視窗
UpdateWindow(hWnd);//更新視窗
這兩句話都用到了建立視窗返回的控制代碼hWnd,他們是什麼意思哪?字如其名了顯示和更新視窗,注意看show window的第二個引數,沒錯他就是winmain的第四個引數,告訴你如何顯示視窗。而為了強制Windows更新視窗我們需要呼叫updatewindow函式。
終於來到了我們的主事件迴圈部分
MSG msg = {0};//定義meg
while(GetMessage(&msg, NULL, 0, 0)) //使用getmessage獲得訊息
{
TranslateMessage(&msg);//轉換訊息
DispatchMessage(&msg);//發訊息給程式,然後交給os呼叫視窗處理函式
}
這部分的程式碼非常少但是都很重要,記筆記劃重點了(我記得在看《我的青春戀愛物語果然有問題》的時候彈幕全是這個,我很喜歡大老師......)
首先是這個MSG的結構體,傳遞訊息的結構體
typedef struct tagMSG {
HWND hwnd;//標識發生事件的視窗
UINT message;//訊息的id
WPARAM wParam;//詳細的訊息資訊,不同的訊息不同的解釋
LPARAM lParam;//詳細的訊息資訊
DWORD time;//發生事件的時間
POINT pt;//滑鼠的位置
}
大家記住這個結構體,
通過它來實現winmain函式和我們的wndproc函式之間的訊息傳遞。接下來就是在迴圈條件中這句GetMessage。在講它的功能之前,需要告訴大家的是,每個程式執行的時候,系統都會給我們生成一個訊息佇列,這個訊息佇列會用來存放許多事件訊息,比如點選滑鼠,按下鍵盤等等等等。然後,我們的GetMessage函式的作用就是從中取出訊息填入MSG結構體中。所以GetMessage函式是什麼大家應該明白了吧。如果訊息佇列中有WM_QUIT訊息的話,就代表視窗被關閉了,這時候函式會返回一個負值,自然迴圈就結束了。再看迴圈體部分,第一句的意思是轉換我們在訊息佇列中取得的鍵碼。它的具體工作細節我們不需要知道,呼叫就好了。然後是第二句,這句話的作用是通過DispatchMessage()函式來呼叫我們的視窗資訊處理函式WndProc。並使用MSG給WndProc函式傳遞適當的引數,我們再貼一下WndProc函式
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
PAINTSTRUCT paintStruct;//定義一個paintstruct記錄一些繪製資訊
HDC hdc;//裝置環境控制代碼
switch (message)//開始處理
{
case WM_PAINT://重繪訊息 更新客戶區
hdc = BeginPaint(hWnd, &paintStruct);//指定視窗進行繪圖準備,並在ps結構中儲存相關資訊
TextOut(hdc,340,280,L"你的win32程式",9);//在螢幕上繪製一句話
EndPaint(hWnd, &paintStruct);//視窗繪圖過程結束
break;
case WM_KEYDOWN://鍵盤按下訊息
if (wParam == VK_ESCAPE)//如果是esc
DestroyWindow(hWnd);//銷燬視窗,傳送WM_DESTROY訊息
break;
case WM_DESTROY://銷燬訊息
PostQuitMessage(0);//向os申請終止請求。
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);//預設的視窗過程處理函式
}
return 0;
}
對比一下WndProc的引數列表和MSG的定義,細心的朋友一定發現了,WndProc的引數列表和MSG的前幾個資料是一樣的。是的,世界就是這麼奇妙。這個迴圈就基本講完了。最後就是我們的WndProc函數了,這個函式的作用就是為了讓我們能在視窗中做一些自己的事情,因為這個函式是由你自己編寫的。多數的情況下,我們是使用一個switch語句來判斷msg,併為其相應的情況編寫程式碼,也就是msg中的訊息id,比如當訊息佇列中有WM_PAINT:視窗重繪訊息,我們要做什麼,當WM_MOUSEMOVE:滑鼠移動時候我們要幹什麼等等等等。。。而這裡我們就專門為WM_PAINT, WM_KEYDOWN:鍵盤按下,WM_DESTROY:銷燬視窗,這三種訊息編寫了相應的處理程式碼。比如在有視窗重繪WM_PAINT訊息時,我們呼叫了一個API函式在視窗上繪製了一句話。
TextOut(hdc,340,280,L"你的win32程式",9);//在螢幕上繪製一句話
至於其他的詳細的資訊這裡我不想在過多的深入,畢竟我不想再重複去將GDI了,而說到這裡基本上從巨集觀上吧這個程式脈絡講清楚了,關於更加詳細的內容,比如WndProc與WinMain和OS之間的詳細關係,大家有意願的可以去閱讀《windows程式設計第二版》的第三章,裡面詳細地講述了一個視窗程式地枝枝葉葉,相信看完這篇文章再去閱讀肯定能讀懂。
寫到這裡 ,建立一個win32視窗算是講完了吧,還是那句話 有什麼不對地地方希望看到地朋友指出來,夕霞謝過!