三、创建窗口
好消息,创建窗口你所要做的只是调用一个CreateWindowEx()函数。坏消息是,这个函数有好多的参数。以下便是函数原形:
HWND CreateWindowEx(
DWORD dwExStyle, // extended window style
LPCTSTR lpClassName, // pointer to registered class name
LPCTSTR lpWindowName, // pointer to window name
DWORD dwStyle, // window style
int x, // horizontal position of window
int y, // vertical position of window
int nWidth, // window width
int nHeight, // window height
HWND hWndParent, // handle to parent or owner window
HMENU hMenu, // handle to menu, or child-window identifier
HINSTANCE hInstance, // handle to application instance
LPVOID lpParam // pointer to window-creation data
);
首先:函数的返回值。也就是函数的类型。是不是所有创建窗口用的函数的类型的讨厌样子都感觉亲切了一点儿?不要紧,你会习惯的,肯定比你想象的速度要快。这里返回的类型是HWND,是一个窗口的句柄(句柄就是窗口的标识符)。你将把CreateWindowEx()的返回值传递给一个窗口的句柄,就像一个参数一样。现在,我们来琢磨一下这些参数,很多根据名字就知道它是干什么的了。
※ DWORD dwExStyle:扩充的窗口风格。你将很少使用扩充的窗口风格,所以多数时间你会把它设置为NULL。如果有兴趣,查一下帮助文件,可以一试由WS_EX_打头的扩充风格。
※ LPCTSTR lpClassName:还记得你的窗口类的名称吗?再用一次。
※ LPCTSTR lpWindowName:将显示在窗口的标题栏里的简短文字。
※ DWORD dwStyle:窗口的风格。它将允许你详细的描绘你所要创建的窗口的风格。有很多风格你可以利用哦,都是以WS_打头的,你可以利用(|)符号组合利用它们。我将在这儿介绍几个常用的。
◎ WS_POPUP 指定一个弹出的窗口。
◎ WS_OVERLAPPED 指定一个具有标题栏和边界的重叠窗口。
◎ WS_OVERLAPPEDWINDOW 指定一个具有所有标准控件的窗口。
◎ WS_VISIBLE 指定一个初始时可见的窗口。
看得出,WS_OVERLAPPEDWINDOW是一个组合体。简单的说,你可以按照如下规律:如果你要创建一个可以最大化、最小化、随意改变大小等等地窗口,就选择WS_OVERLAPPEDWINDOW;如果你只想要一个具有标题栏、可改变大小的窗口,就选择WS_OVERLAPPED;如果你只想要一个光秃秃的窗口,就选择WS_POPUP;如果你只想显示一个黑色的大方框,可能你要用它写一个全屏的游戏,选择WS_VISIBLE是没错的。
※ int x,y:你所要创建的窗口的左上角的坐标。
※ int nWidth,nHeight:猜也猜到了,是窗口的长和高。
※ HWND hWndParent:指向父窗口的句柄。你若想在窗口下再建立一个窗口,那么第一个窗口就叫父窗口。咱先建立一个主窗口,所以设置为NULL,也就意味着Windows桌面是父窗口。
※ HMENU hMenu:这是用在窗口上的菜单句柄。若你学会建立和使用资源,即建立自己的菜单,你可以用LoadMenu()函数调用自己的菜单资源。目前,先设为NULL。
※ HINSTANCE hInstance:是一个名柄,它指向由Windows传递给WinMain()的实例。
※ LPVOID lpParam:对于游戏编程来说,没有什么用的东西,只有简单的窗口程序用到它。设置为NULL好了。
我们现在万事具备,东风也有了。我先给个示例:
HWND hwnd;
if (!(hwnd = CreateWindowEx(NULL,
// extended style, not needed
"Sample Class",
// class identifier
"Sample Window",
// window title
WS_POPUP | WS_VISIBLE,// parameters
0, 0, 320, 240,
// initial position, size
NULL,
// handle to parent (the desktop)
NULL,
// handle to menu (none)
hinstance,
// application instance handle
NULL)))
// who needs it?
return(0);
你可能会在游戏编程中用上这这段代码,因为它是一个弹出式窗口。注意,我用了if形式,目的是一旦CreateWindowsEX()函数失灵,返回一个NULL,也就意味着如果窗口由于某种原因不能被建立,那么WinMain()就被简单的返回,程序结束。现在我们学会了足够的知识建立一个小有功能的窗口了。还记得我们建立窗口类“sample class”时,一个指向“CALLBACK”类型函数的指针吗?对,是“lpfnWndProc”。要想让你的窗口真正做点事儿,我们还得来处理一下它指向的“窗口过程”函数。
四、显示窗口
CreateWindowEx()从内部创建窗口,但并不显示它。要显示这个窗口,必须调用另外两个函数:ShowWindow()和UpdateWindow()。头一个设置窗口的显示状态,后一个则更新窗口的客户区。对于程序的主窗口,ShowWindow()必须被调用一次,调用代码如下:
ShowWindow(hwnd,nCmdShow);
第一个参数是由CreateWindowEx()函数返回的窗口句柄;第二个参数就是窗口的显示模式参数,在☆WinMain()函数中提到过,就不重复了。UpdateWindow()函数的调用代码如下:
UpdateWindow(hwnd);
参数hwnd同ShowWindow()函数的hwnd一样。
五、消息的处理
我已经说过消息在窗口里的作用了,下面让我们来仔细学习一下它。处理消息的函数结构如下:
LRESULT CALLBACK MsgHandler(
HWND hwnd,
// window handle
UINT msg,
// the message identifier
WPARAM wparam, // message parameters
LPARAM lparam
// more message parameters
);
这个LRESULT类型要求返回一个32位的整数。实际取值依赖于消息,但是这个值很少在应用程序代码中得到应用。以前我们谈到过一点CALLBACK协定,它的参数很简单:
※ HWND hwnd:是接收消息的窗口的句柄,也是由CreateWindowEx()函数返回的句柄。
※ UINT msg:这是一个消息标识符,都是以WM_打头的符号常量,意思是“Windows Message”。很多的,这里只介绍一些常用的:
◎ WM_ACTIVATE:一个新窗口被激活。
◎ WM_CLOSE:一个窗口被关闭。
◎ WM_COMMAND:一个菜单功能被选择。
◎ WM_CREATE:一个窗口被建立。
◎ WM_LBUTTONDBLCLK:鼠标左键被双击。
◎ WM_LBUTTONDOWN:鼠标左键被按下。
◎ WM_MOUSEMOVE:鼠标被移动。
◎ WM_MOVE:一个窗口被移动。
◎ WM_PAINT:窗口的一部分需要重画。
◎ WM_RBUTTONDBLCLK:鼠标的右键被双击。
◎ WM_RBUTTONDOWN:鼠标的右键被按下。
◎ WM_SIZE:窗口的大小被改变。
◎ WM_USER:做你想做的。
※ WPARAM wparam,LPARAM lparam:消息参数。它们提供有关消息的附加信息,这两个值对于每条消息来说都是特定的。
你要把所有要发生的消息都写进程序代码的话,我想你可能已经累疯了。我想我会的。感谢上帝,Windows提供了默认消息处理,如果你没有任何特殊的消息需要处理了,你总是要用DefWindowPorc()函数的,下面给一个最简单的例子,没有任何特定的消息要处理的例子:
LRESULT CALLBACK MsgHandler(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
return(DefWindowProc(hwnd, msg, wparam, lparam));
}
简单吧!但通常你都需要处理一些自己的消息,你要写自己的程序代码,然后返回0,来告诉程序你干完了。下面是一个例子,当窗口建立时,你调用了一个初始化的函数Initialize_Game(),然后返回0,最后告诉程序自己处理那些默认的消息吧:
LRESULT CALLBACK MsgHandler(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
if (msg == WM_CREATE)
{
Initialize_Game();
return(0);
}
return(DefWindowProc(hwnd, msg, wparam, lparam));
}
你很可能需要一个“switch”结构来手动完成你想要控制的消息,然后把剩下的交给DefWindowProc()去做。大功告成前,我不得不提醒您一件事,就是怎样使你的消息控制得到响应呢?
六、读取消息队列
这里先给你一个switch结构的例子吧:
LRESULT CALLBACK MsgHandler(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
switch(msg)
{
case WM_CREAT:
[初始化游戏]
return 0;
case WM_PAINT:
[画一架飞机]
return 0;
case ……………………
……………………
}
return(DefWindowProc(hwnd, msg, wparam, lparam));
}
在进入程序的主循环前,你需要看看你的消息控制(就是你在switch结构里编的那些),尤其是还没有用到的消息控制是否被机器存了起来,以备一旦用到,马上响应。做到正确的响应,你需要做几件事。首先你需要PeekMessage()函数。下面是它的原形:
BOOL PeekMessage(
LPMSG lpMsg,
// pointer to structur