3D游戏编程入门(四)第一个Win32窗口
昨天我们说了WIN编程原本就是一个消息传递的过程,今天我们来制作第一个窗口
首先我们来了解一下典型的WIN32窗口应用程序结构
- 程序入口点(WinMain函数)
- 注册窗口类(RegisterClass/Ex)
- 创建窗口类(CreateWindow/Ex)
- 显示主窗口(Show Window)
- 更新主窗口(Update Window)
- 进入消息循环(GteMessage–TranlateMessage–DispatchMessage–对相应消息的处理)
- 程序出口点(WinMain返回)
按照这个逻辑,我们来设计一个窗口,首先,我们在VC下选择建立一个Win32应用程序,并在应用程序的配置中将”空项目”点选,我们获得一个完全空白的项目,之后打开解决方案资源管理器,即右上一系列小图标的最左边那个,在源代码中,添加新项,选择C++文件,即下缀名为cpp的文件,之后打入相应的代码.源代码如下:
//Code
#include
#include
//声明回调函数
LRESULT CALLBACK WndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam );
//窗口类名和窗口标题
const TCHAR szWindowClass[] = _T("第一个窗口");
const TCHAR szWindowTitle[] = _T("主窗口标题");
//WinMain函数,入口点
int WINAPI _tWinMain
(
HINSTANCE hInstance,
HINSTANCE hPreInstance,
LPTSTR lpCmdLine,
int nCmdShow
)
{
//注册窗口类
WNDCLASSEX wcex = { NULL };
wcex.cbSize = sizeof( WNDCLASSEX );
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = ( WNDPROC )WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = NULL;
wcex.hCursor = LoadCursor( NULL, IDC_ARROW );
wcex.hbrBackground = ( HBRUSH )( COLOR_WINDOW + 1 );
wcex.lpszMenuName = NULL;
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = NULL;
RegisterClassEx( &wcex );
//创建窗口
HWND hWnd = CreateWindowEx( 0, szWindowClass, szWindowClass, WS_OVERLAPPEDWINDOW,
100, 200, 500, 500, HWND_DESKTOP, NULL, hInstance, NULL );
if( !hWnd )
{
return FALSE;
}
//显示并更新窗口
ShowWindow( hWnd, nCmdShow );
UpdateWindow( hWnd );
//进入消息循环
MSG msg;
while( GetMessage( &msg, NULL, 0, 0))
{
TranslateMessage( &msg );
DispatchMessage( &msg );
}
return msg.wParam;
}
//消息回调函数
LRESULT CALLBACK WndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
switch( msg )
{
case WM_DESTROY:
PostQuitMessage( 0 );
return 0;
default:
return DefWindowProc( hWnd, msg, wParam, lParam );
}
}
如果没有错误,应当生成下面所示窗口.
###下面我将对具体的代码进行详细讲解
首先,我们包含了两个头文件,一个是windows.h文件,它是一切WIN32窗口必须包含的头文件,我们为了兼容UniCode字体,也包含了tchar.h头文件.
我们声明了两个TCHAR类型的数组szWindowClass和szWindowTitle分别存储窗口的类名和窗口标题.设置const是因为它们是不需要去改变的常量,常量使用const定义是个好习惯.若你更改这行 const TCHAR szWindowClass[] = _T(“我的游戏”); 此时窗口显示的最上面一栏将是”我的游戏”.
_tWinMain是整个应用程序的入口,其中的参数分别为:HINSTANCE hInstance,是指HINSTANCE类型的应用实例的句柄,实例和句柄我在上面的章节有详细说明,这里不再罗嗦.Windows设置这个参数并将它传递给应用程序,在应用程序中很多部分需要使用它.之后的HINSTANCE hPreInstance,我们现在已经无需管他,它指的是当前应用程序之前的上一个应用程序的句柄,我们在WIN98之后再也无需对这个参数进行任何判断和使用,无需理会之.LPTSTR lpCmdLine,是启动此应用程序可能跟随的命令参数,当我们的程序从DOS命令行进行输入或是从RUN对话框中进行输入时才需理会,我们现在全部从WIN窗口获得输入,所以也无需理会.int nCmdShow,是用来控制窗口显示方式的.我们若设置为SW_SHOWNORMAL则表示默认状态,你可以设置为SW_MAXIMIZE或者SW_MINIMIZE尝试一下:)
窗口创建前需要先注册窗口,WNDCLASSEX wcex;即是声明了一个窗口类结构体的对象,我们在这个对象结构体中设置窗口的各种属性.当然我们这里可以写为WNDCLASS wcex;它和WNDCLASSEX类型的区别仅仅是EX是增强型窗口,它多两个参数而已.以后我们接触的很多类和函数也常有EX结尾和没EX结尾的,区别也都如次,仅仅是一个增强型和非增强型而已.值得注意的是开始我先赋值为NULL,这是一个好习惯,对于声明的结构体,我们尽量将其先声空,这将对其所在的内存地址内容进行晴空,避免了我们遗忘赋值带来的程序错误.我们谁都不知道该块内存原本存放些什么.
结构体参数中,cbSize代表着wcex结构体变量的大小,我们就是通过这个参数来区别到底wcex是EX增强版还是普通版.style是类的风格,假若我们赋值为CS_DEFAULT则代表默认,我们这里写的是CS_HREDRAW | CS_VREDRAW,这代表,当我们窗口宽度或高度发生改变时,窗口将根据窗口大小进行重新绘制.wcex.lpfnWndProc = ( WNDPROC )WndProc;是说明我们的窗口回调函数是哪个,指定我们的窗口回调函数.cbClsExtra代表额外类空间,可以在其中存放窗口类所共有的数据,这个对我们3D网游编程并不重要,可以默认为0即可.cbWndExtra为额外窗口空间,可以选择其中存放的每个窗口所拥有的数据,依旧不重要,默认为0, wcex.hInstance = hInstance;这是标识当前这个窗口类和哪个应用程序的实例有关;hIcon为窗口类的正常图标,也就是我们任何程序最左上角的图标.hCursor为窗口的鼠标光标类型,我们在游戏窗口中很少使用微软默认的光标,而常常使用的自定的手状或剑状图标,我们可以在这里更改.hbrBackGround是缺省的窗口背景颜色,我们现在设置的是白色的.lpszMenuName是缺省的菜单名,用它可以关联一个默认的菜单, wcex.lpszClassName = szWindowClass;是这个窗口类的名字,我们创建窗口时是依靠窗口类的名字来指定窗口的.hIconSm是当窗口被最小化时,WIN任务栏显示的程序图标.
当我们获得了这些窗口资讯之后,我们将它传参给RegisterClassEx来注册我们的窗口 注册完毕后,我们开始窗口的创建,在我的程序中,我的设置为
HWND hWnd = CreateWindowEx( 0, szWindowClass, szWindowClass, WS_OVERLAPPEDWINDOW, 100, 200, 500, 500, HWND_DESKTOP, NULL, hInstance, NULL );
首先我们先创建一个窗口句柄,并用它来获得我们的窗口.HWND hWnd = 之后我们调用CreateWindowEx来创建我们的窗口,我们下面介绍其参数
HWND CreateWindowEx(
DWORD dwExStyle, // 窗口风格,大多数情况下,可以设为NULL或0
LPCTSTR lpClassName, // 创建的窗口的基础类名
LPCTSTR lpWindowName, // 窗口的名称
DWORD dwStyle, // 窗口的类型
int x, // 该窗口左上角位置X坐标
int y, // 该窗口左上角位置Y坐标
int nWidth, // 窗口的宽度
int nHeight, // 窗口的高度
HWND hWndParent, // 父窗口句柄
HMENU hMenu, // 向附属于该窗口菜单的句柄
HINSTANCE hInstance, // 应用程序句柄
LPVOID lpParam); // 各级特征,一般设为NULL
之后我加了if( !hWnd ){return FALSE;}的判断,可以检查我们的窗口是否创建成功,这是程序健壮性的一种良好编程习惯. 当我们创建窗口成功后,我们的窗口并没有在屏幕上显示出来,仅仅是在内存中将窗口的一切数据全部准备好了而已,此时我们需要调用下面的函数来显示更新我们的窗口.
ShowWindow( hWnd, nCmdShow );
UpdateWindow( hWnd );
首先我们将窗口句柄hWnd做参传过来,方便系统知道我们准备显示哪个窗口,而nCmdShow则是说明窗口显示的方式.
当我们显示完毕程序后,我们有必要加入消息的循环,不然该程序永远陷入无响应的状态. 消息循环我在上一章节已经讲过,这里仅再强调一些东西.
我的代码中使用的是GetMessage,其实我更建议大家去使用PeekMessage函数,这两个函数的区别在于,使用GetMessage的话,则我们应用程序从消息队列中获得消息,分析消息并处理后,将消息原封不动的再次排列在消息队列中,而PeekMessage则是获得消息后,将此消息从消息队列中直接删除掉,当然通常情况下我们并不需要已经处理过的消息,所以这里PeekMessage比GetMeaasge好.下面说下它们的参数:
BOOL PeekMessage(LPMSG lpMsg,HWND hWnd,UINT wMsgFilterMin,UINT wMsgFilterMax,UINT wRemoveMsg)
这五个分别代表,消息的地址指针,需要获检查消息队列的窗口句柄,检索的第一个消息,检索的最后一个消息,消息处理类型.
我们通常在消息队列中是从第一个消息开始检索,所以第三个参数通常为0,第4个参数为0则表示检索全部的消息队列,一般来说这俩参数都设置为0.第5个参数是GetMeaasge没有的,它就是控制消息是否处理后删除的,若为PM_REMOVE则代表删除,若是RM_NOREMOVE则代表不删除,此是PeekMessage和GetMessage就没有区别了.
当我们获得消息后,就会进行消息回调函数也就是WndProc,对消息进行判断和处理. 我们这里代码为
LRESULT CALLBACK WndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
switch( msg )
{
case WM_DESTROY:
PostQuitMessage( 0 );
return 0;
default:
return DefWindowProc( hWnd, msg, wParam, lParam );
}
}
其中的参数我上节有说过,这里不再说了.我们这里的判断仅仅对WM_DESTORY进行了处理,当我们获得其他的消息,则不进行任何处理,返回消息队列进行下一条的消息处理等待,若是符合了WM_DESTORY,我们就会晴空自身的消息队列,并且退出程序.而实际上WM_DESTROY消息也是当我们关闭窗口时,系统才会发出的消息.
基本上一个最基本的WIN32窗口程序流程就是这样,或许很烦琐,当我们熟悉它以后将会变的非常容易.因为时间关系,就说到这里~大家一起加油.