1. 程式人生 > >遊戲程式設計之DirectX的修煉:二(建立屬於自己的windows視窗程式:下)

遊戲程式設計之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。並使用MSGWndProc函式傳遞適當的引數,我們再貼一下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了,而說到這裡基本上從巨集觀上吧這個程式脈絡講清楚了,關於更加詳細的內容,比如WndProcWinMainOS之間的詳細關係,大家有意願的可以去閱讀《windows程式設計第二版》的第三章,裡面詳細地講述了一個視窗程式地枝枝葉葉,相信看完這篇文章再去閱讀肯定能讀懂。


寫到這裡 ,建立一個win32視窗算是講完了吧,還是那句話 有什麼不對地地方希望看到地朋友指出來,夕霞謝過!