☆ 创建窗口
好消息,创建窗口你所要做的只是调用一个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( 【有时被命名为WndProc,随便你】 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 structure for message HWND hWnd, // handle to window UINT wMsgFilterMin, // first message UINT wMsgFilterMax, // last message UINT wRemoveMsg // removal flags );
这是一个布尔类型,也就是一个int型,不过只有两个值,TRUE和FALSE,如果有一条消息在队列中等待,函数返回TRUE,否则,返回FALSE。它的参数很简单: ※ LPMSG lpMsg:这是一个MSG类型的指针变量。如果有消息在等待,消息信息将被填入该变量。 ※ HWND hWnd:你所要检查的消息队列的窗口的句柄。 ※ UINT wMsgFilterMin,wMsgFilterMax:索引第一个和最后一个消息,一般你都从第一个消息开始检索,所以把它们都设置为0好了。 ※ UINT wRemoveMsg:一般来说,它有两个指,PM_REMOVE或者PM_NOREMOVE。使用前者会在消息被读取后从队列中移除,后者是继续保留。通常,我们选择前者PM_REMOVE。 真正处理消息时,你需要做两件事,很简单,第一件是TranslateMessage(),第二件是DispatchMessage()。它们的原形很相似:
BOOL TranslateMessage(CONST MSG *lpmsg); LONG DispatchMessage(CONST MSG *lpmsg);
头一个是把消息翻译过来,第二个是从MSG结构中调用相应的信息。你只需要知道这么多。伴随着程序主循环的反复执行,如果有消息出现,你就调用这两个函数,函数MsgHandler()会安排好一切的。下面是个例子:
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))【现在有时用GetMessage(&msg,NULL,0,0)】 { TranslateMessage(&msg); DispatchMessage(&msg); }
没问题,你现在完全可以写一个窗口程序了。不坏吧?在结束本章前,我还有几点要提醒你。还记得我们在谈论☆消息时,说要在后面进一步讨论它吗?你忘了?我可没有忘记。怎样主动向Windows发送消息呢?
☆ 发送消息 有两种办法可以做到。PostMessage()函数或SendMessage()函数。它们的原形很相似:
BOOL PostMessage( HWND hWnd, // handle of destination window UINT Msg, // message to post WPARAM wParam, // first message parameter LPARAM lParam // second message parameter );
LRESULT SendMessage( HWND hWnd, // handle of destination window UINT Msg, // message to post WPARAM wParam, // first message parameter LPARAM lParam // second message parameter );
它们的参数相同,并且和前面讲过的函数MsgHandler()的参数功能相同,就不重复了。我只谈谈它们之间的区别。 PostMessage()被经常用来向队列中加入消息,成功,返回TRUE,否则,返回FALSE。它只是简单的把消息加入到队列中,然后返回。多数情况下,调用它将返回TRUE。 SendMessage()则有些不同,它并不是把消息加入到队列里,而是直接翻译消息和调用消息处理,直到消息处理完成后才返回。所以,SendMessage()比PostMessage()有更高的应急性。你想立刻干的事情,就应该调用它。 消息是DOS和Windows编程之间重要的区别标志。
☆ 程序的流程 在DOS中,我们不必担心消息这种东西,不必担心多个程序同时运行,但在Windows里,你必须考虑这些。在Windows平台上编程,有一些不同于DOS下编程的地方。让我们看看下面这段虚拟的代码:
// main game loop do { // handle messages here
// ...
// update screen if necessary if (new_screen) { FadeOut(); LoadNewMap(); FadeIn(); }
// perform game logic WaitForInput(); UpdateCharacters(); RenderMap();
} while (game_active);
假设FadeOut()函数这样工作:当函数被调用,在一秒内屏幕图象暗淡下来,当屏幕完全黑了,函数返回。LoadNewMap()调用一个新的图象;FadeIn()使屏幕逐渐亮起来,好显示新图象。当有键子按下,调用WaitForInput()函数,再继续调用下去。这在DOS游戏编程里是合情合理的,但在Windows下不行。 为什么呢?让我们看看新画面诞生的过程。画面逐渐变黑,调用图片,逐渐恢复。这大概要2秒钟,用户可以等待,也可能要移动一下窗口,但程序只专心的干调用图片的工作,不会对窗口的移动作出反应。这是很糟糕的,你干了机器不知道的事情,这可能导致系统崩溃,我们必须要让机器对用户的任何操作作出正确的反应。不多说了,总之你要换一换脑筋,如果你从来就没在DOS下编过程序,那正好,你赶上潮流了!
☆ 总结 本章我们讲了Windows编程的基础,虽然只是一个空白的窗口,但包含了最基本的东西。下一章我们将学习创建资源和利用资源,你就可以用有自己风格的光标、图标、声音、菜单等等,还要生成一个EXE文件呢! 待续。。。。。。
[此贴子已经被作者于2004-06-11 11:01:01编辑过]