游戏编程之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窗口算是讲完了吧,还是那句话 有什么不对地地方希望看到地朋友指出来,夕霞谢过!

 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值