您的位置:首页 > 编程语言

游戏编程之DirectX的修炼:二(创建属于自己的windows窗口程序:下)

2017-08-31 21:35 399 查看
 
     上一节给我们写了一个非常小的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);

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

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: