错,你已经成为Windows专家了!如果你已经理解了上面讨论过的概念以及事件循环、事件处理程序等等的重要性,那已经成功了90%了。剩下的仅是一些细节问题。 程序清单2.3是一个完整的Windows程序,内容是创建一个窗口,并等候关闭。 程序清单2-3:一个基本的Windows程序 // DEMO2_3.CPP - A complete windows program
// INCLUDES /////////////////////////////////////////////// #define WIN32_LEAN_AND_MEAN // just say no to MFC
#include <windows.h> // include all the windows headers #include <windowsx.h> // include useful macros #include <stdio.h> #include <math.h>
// DEFINES ////////////////////////////////////////////////
// defines for windows #define WINDOW_CLASS_NAME "WINCLASS1"
// GLOBALS ////////////////////////////////////////////////
// FUNCTIONS ////////////////////////////////////////////// LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { // this is the main message handler of the system PAINTSTRUCT ps; // used in WM_PAINT HDC hdc; // handle to a device context
// what is the message switch(msg) { case WM_CREATE: { // do initialization stuff here
// return success return(0); } break;
case WM_PAINT: { // simply validate the window hdc = BeginPaint(hwnd,&ps); // you would do all your painting here EndPaint(hwnd,&ps);
// return success return(0); } break; case WM_DESTROY: { // kill the application, this sends a WM_QUIT message PostQuitMessage(0);
// return success return(0); } break;
default:break;
} // end switch
// process any messages that we didn't take care of return (DefWindowProc(hwnd, msg, wparam, lparam));
} // end WinProc
// WINMAIN //////////////////////////////////////////////// int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hprevinstance, LPSTR lpcmdline, int ncmdshow) {
WNDCLASSEX winclass; // this will hold the class we create HWND hwnd; // generic window handle MSG msg; // generic message
// first fill in the window class structure winclass.cbSize = sizeof(WNDCLASSEX); winclass.style = CS_DBLCLKS | CS_OWNDC | CS_HREDRAW | CS_VREDRAW; winclass.lpfnWndProc = WindowProc; winclass.cbClsExtra = 0; winclass.cbWndExtra = 0; winclass.hInstance = hinstance; winclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); winclass.hCursor = LoadCursor(NULL, IDC_ARROW); winclass.hbrBackground = GetStockObject(BLACK_BRUSH); winclass.lpszMenuName = NULL; winclass.lpszClassName = WINDOW_CLASS_NAME; winclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
// register the window class if (!RegisterClassEx(&winclass)) return(0);
// create the window if (!(hwnd = CreateWindowEx(NULL, // extended style WINDOW_CLASS_NAME, // class "Your Basic Window", // title WS_OVERLAPPEDWINDOW | WS_VISIBLE, 0,0, // initial x,y 400,400, // initial width, height NULL, // handle to parent NULL, // handle to menu hinstance,// instance of this application NULL))) // extra creation parms return(0);
// enter main event loop while(GetMessage(&msg,NULL,0,0)) { // translate any accelerator keys TranslateMessage(&msg);
// send the message to the window proc DispatchMessage(&msg); } // end while
// return to Windows like this return(msg.wParam);
} // end WinMain
/////////////////////////////////////////////////////////// 要编译DEMO2_3.CPP,只需创建一个Win32环境下的.EXE应用程序,并且将DEMO2_3.CPP添加到该工程中即可。假如你喜欢的话,可以直接从CD-ROM上运行预先编译好的程序DEMO2_3.EXE。图2-10显示了该程序运行中的样子。 图2-10:运行中的DEMO2_3.EXE 在进行下一部分内容之前,我还有事情要说。首先,如果你认真阅读了事件循环的话,会发现它看上去并不是个实时程序。也就是说,当程序在等待通过GetMessage()传递的消息的同时,主事件循环基本上是锁定的。这的确是真的;你必须以各种方式来避免这种现象,因为你需要连续地执行你的游戏处理过程,并且在Windows事件出现时处理它们。 产生一个实时事件循环 有一种实时的无等候的事件循环很容易实现。你所需要的就是一种测试在消息序列中是否有消息的方法。如果有,你就处理它;否则,继续处理其他的游戏逻辑并重复进行。运行的测试函数是PeekMessage()。其原型几乎和GetMessage()相同,如下所示: 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 如果有可用消息的话返回值为非零。 区别在于最后一个参数,它控制如何从消息序列中检索消息。对于wRemoveMsg,有效的标志有: •  M_NOREMOVE— PeekMessage()处理之后,消息没有从序列中去除。 •  M_REMOVE— PeekMessage()处理之后,消息已经从序列中去除。 如果将这两种情况考虑进去的话,你可以做出两个选择:如果有消息的话,就使用PeekMessage()和PM_NOREMOVE,调用GetMessage();另一种选择是:使用PM_REMOVE,如果有消息则使用PeekMessage()函数本身来检索消息。一般使用后一种情况。下面是核心逻辑的代码,我们在主事件循环中稍作改动以体现这一新技术: while(TRUE) { // test if there is a message in queue, if so get it if (PeekMessage(&msg,NULL,0,0,PM_REMOVE)) { // test if this is a quit if (msg.message == WM_QUIT) break; // translate any accelerator keys TranslateMessage(&msg);
// send the message to the window proc DispatchMessage(&msg); } // end if
// main game processing goes here Game_Main(); } // end while 我已经将程序中的重要部分用加粗体显示。加粗体的第一部分内容是: if (msg.message == WM_QUIT) break; 下面是如何测试从无限循环体while(true)中退出。请记住,当在WinProc中处理WM_DESTROY消息时,你的工作就是通过调用PostQuitMessage()函数来传递WM_QUIT消息。WM_QUIT就在事件序列中慢慢地移动,你可以检测到它,所以可以跳出主循环。 用加粗体显示的程序最后一部分指出调用主游戏程序代码循环的位置。但是请不要忘记,在运行一幅动画或游戏逻辑之后,调用Game_Main()或者调用任意程序必须返回。否则,Windows主事件循环将不处理消息。 这种新型的实时结构的例子非常适合于游戏逻辑处理程序,请看源程序DEMO2_4.CPP以及CD-ROM上相关的DEMO2_4.EXE。这种结构实际上是本书剩下部分的原型。 打开多个窗口 在完成本章内容之前,我想讨论一个你可能非常关心的更重要的话题——如何打开多个窗口。实际上,这是小事一桩,其实你已经知道如何打开多个窗口。你所需要做的就是多次调用函数CreateWindowEx()来创建这些窗口,事实也的确如此。但是,对此还有一些需要注意的问题。 首先,记住当你创建窗口的时候,它必定是基于某个窗口类的。在所有东西里,是这个窗口类定义了WinProc或者说事件句柄。这点细节至关重要,应次要注意。你可以使用同一个类创建任意数量的窗口,但是这些窗口的所有的消息都会按照WINCLASSEX结构里的lpfnWndProc字段指向的事件句柄所定义的那样,被发往同一个WinProc。图2-11详细示意了这种情况下的消息流程。 图2-11:The message flow for multiple windows with the same Windows class. 这可能是,也可能不是你所想要的。如果你希望每个窗口有自己的WinProc,你必须创建多于一个的窗口类,并用不同的类来创建各自的窗口。于是,对于每一个窗口类,有不同的WinProc发送消息。图2-12体现了这一过程。 图2-12:Multiple Windows classes with multiple windows. 记住了这些,下面就是用同一个类来创建两个窗口的例子: // create the first window if (!(hwnd = CreateWindowEx(NULL, // extended style WINDOW_CLASS_NAME, // class "Window 1 Based on WINCLASS1", // title WS_OVERLAPPEDWINDOW | WS_VISIBLE, 0,0, // initial x,y 400,400, // initial width, height NULL, // handle to parent NULL, // handle to menu hinstance,// instance of this application NULL))) // extra creation parms return(0);
// create the second window if (!(hwnd = CreateWindowEx(NULL, // extended style WINDOW_CLASS_NAME, // class "Window 2 Also Based on WINCLASS1", // title WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100,100, // initial x,y 400,400, // initial width, height NULL, // handle to parent NULL, // handle to menu hinstance,// instance of this application NULL))) // extra creation parms return(0); 当然,你可能希望分别使用不同的变量跟踪各个窗口句柄,就像hwnd那样。举个同时打开两个窗口的例子,请看DEMO2_5.CPP和对应的可执行文件DEMO2_5.EXE。运行.EXE的时候,你会看见类似图2-13的画面。请注意,当你关闭了两个窗口中的任意一个,另一个也随之关闭,应用程序就此结束运行。试试看你是否能想出办法使得可以一次仅关掉一个窗口。(提示:创建两个窗口类,并且仅当两个窗口都关闭以后才发送WM_QUIT消息。) 图2-13:The multiple-window program DEMO2_5.EXE. 总结 虽然我并不认识你,但我很激动,因为在此时此刻,你已经具有了可以更加深入地理解Windows编程的基础。你已经了解了Windows的架构、多任务,你也知道如何创建窗口类、注册类、创建窗口、编写事件循环和句柄,及很多其他知识点!轻轻拍拍自己的后背,对自己诚实地说:你干得真不错! 在下一章里,我们将读到更深入的Windows相关内容,例如使用资源、创建菜单、操作对话框及获取信息等。
|