表3.2
风格 | 含义 |
WS_OVERLAPPEDWINDOW | 创建一个层叠式窗口,有边框、标题栏、系统菜单、最大最小化按钮,是以下几种风格的集合:WS_OVERLAPPED, WS_CAPTION, WS_SYSMENU, WS_THICKFRAME, WS_MINIMIZEBOX, WS_MAXIMIZEBOX |
WS_POPUPWINDOW | 创建一个弹出式窗口,是以下几种风格的集合: WS_BORDER, WS_POPUP, WS_SYSMENU。必须再加上WS_CAPTION与才能使窗口菜单可见。 |
WS_OVERLAPPED & WS_TILED | 创建一个层叠式窗口,它有标题栏和边框。 |
WS_POPUP | 该窗口为弹出式窗口,不能与WS_CHILD同时使用。 |
WS_BORDER | 窗口有单线边框。 |
WS_CAPTION | 窗口有标题栏。 |
WS_CHILD | 该窗口为子窗口,不能与WS_POPUP同时使用。 |
WS_DISABLED | 该窗口为无效,即对用户操作不产生任何反应。 |
WS_HSCROLL / WS_VSCROLL | 窗口有水平滚动条 / 垂直滚动条。 |
WS_MAXIMIZE / WS_MINIMIZE | 窗口初始化为最大化 / 最小化。 |
WS_MAXIMIZEBOX / WS_MINIMIZEBOX | 窗口有最大化按钮 / 最小化按钮 |
WS_SIZEBOX & WS_THICKFRAME | 边框可进行大小控制的窗口 |
WS_SYSMENU | 创建一个有系统菜单的窗口,必须与WS_CAPTION风格同时使用 |
WS_TILED | 创建一个层叠式窗口,有标题栏 |
WS_VISIBLE | 窗口为可见 |
在DirectX编程中,我们一般使用的是WS_POPUP | WS_MAXIMIZE,用这个标志创建的窗口没有标题栏和系统菜单且窗口为最大化,可以充分满足DirectX编程的需要。
如果窗口创建成功,CreateWindow( )返回新窗口的句柄,否则返回NULL。
3.2.4 显示和更新窗口
窗口创建后,并不会在屏幕上显示出来,要真正把窗口显示在屏幕上,还得使用ShowWindow( )函数,其原型如下:
BOOL ShowWindow( HWND hWnd, int nCmdShow );
参数hWnd就是要显示的窗口的句柄。
nCmdShow是窗口的显示方式,一般给它WinMain( )函数得到的nCmdShow的值就可以了。常用的窗口显示方式有:
表3.3
方式 | 含义 |
SW_HIDE | 隐藏窗口 |
SW_MINIMIZE | 最小化窗口 |
SW_RESTORE | 恢复并激活窗口 |
SW_SHOW | 显示并激活窗口 |
SW_SHOWMAXIMIZED | 最大化并激活窗口 |
SW_SHOWMINIMIZED | 最小化并激活窗口 |
ShowWindow( )函数的执行优先级不高,当系统正忙着执行其它的任务时窗口不会立即显示出来。所以我们使用ShowWindow( )函数后还要再调用UpdateWindow(HWND hWnd); 函数以保证立即显示窗口。
3.2.5 消息循环
在WinMain( )函数中,调用InitWindow( )函数成功地创建了应用程序主窗口之后,就要启动消息循环,其代码如下:
for(;;)
{
if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
if ( msg.message==WM_QUIT) break;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
Windows应用程序可以接收各种形式的信息,这包括键盘和鼠标的动作、记时器消息,其它应用程序发来的消息等等。Windows系统会自动将这些消息放入应用程序的消息队列中。
PeekMessage( )函数就是用来从应用程序的消息队列中按照先进先出的原则将这些消息一个个的取出来,放进一个MSG结构中去。如果队列中没有任何消息,PeekMessage( )函数将立即返回。如果队列中有消息,它将取出一个后返回。
MSG结构包含了一条Windows消息的完整信息,它由下面的几部分组成:
HWND hwnd; //接收消息的窗口句柄
UINT message; //主消息值
WPARAM wParam; //副消息值1,其具体含义依赖于主消息值
LPARAM lParam; //副消息值2,其具体含义依赖于主消息值
DWORD time; //消息被投递的时间
POINT pt; //鼠标的位置
该结构中的主消息表明了消息的类型,例如是键盘消息还是鼠标消息等。副消息的含义则依赖于主消息值,比如说如果主消息是键盘消息,那么wParam中存储了是键盘的哪个具体键;如果主消息是鼠标消息,那么LOWORD(lParam)和HIWORD(lParam)分别为鼠标位置的x和y坐标;如果主消息是WM_ACTIVATE,wParam就表示了程序是否处于激活状态。这里顺便说一下,定义一个POINT类型的变量curpos后,在程序的任意位置使用GetCursorPos(&curpos)都可以将鼠标坐标存储在curpos.x和curpos.y中。
PeekMessage( )函数的原型如下:
BOOL PeekMessage (
LPMSG lpMsg, //指向一个MSG结构的指针,用来保存消息
HWND hWnd, //指定哪个窗口的消息将被获取
UINT wMsgFilterMin, //指定获取的主消息值的最小值
UINT wMsgFilterMax, //指定获取的主消息值的最大值
UINT wRemoveMsg //得到消息后是否移除消息
);
PeekMessage( )的第一个参数的意义上面已解释。
第二个参数是用来指定从哪个窗口的消息队列中获取消息,其它窗口的消息将被过滤掉。如果该参数为NULL,则PeekMessage( )从该应用程序所有窗口的消息队列中获取消息。
第三个和第四个参数是用来过滤MSG结构中主消息值的,主消息值在wMsgFilterMin和wMsgFilterMax之外的消息将被过滤掉。如果这两个参数均为0,表示接收所有消息。
第五个参数用来设置分发完消息后是否将消息从队列中移除,一般设为PM_REMOVE即移除。
TranslateMessage( )函数的作用是把虚拟键消息转换到字符消息,以满足键盘输入的需要。DispatchMessage( )函数所完成的工作是把当前的消息发送到对应的窗口过程中去。
开启消息循环其实是很简单的一个步骤,几乎所有的程序都是按照Test的这个方法。我们完全不必去深究这些函数的作用,只是简单的照抄就可以了。
另外,这里介绍的消息循环开启方法比某些书上所介绍的用GetMessage( )的方法要好一些,因为GetMessage( )如果得不到消息会一直等待,结果就耗费了许多宝贵的时间,使游戏不能及时刷新。
3.3 消息处理函数
消息处理函数又叫窗口过程,在这个函数中,不同的消息将被switch语句分配到不同的处理程序中去。Windows的消息处理函数的原型是这样定义的:
LRESULT CALLBACK WindowProc(
HWND hwnd, //接收消息窗口的句柄
UINT uMsg, //主消息值
WPARAM wParam, //副消息值1
LPARAM lParam //副消息值2
);
消息处理函数必须按照上面的这个样式来定义,当然函数名称可以随便取。
Test中的WinProc( )函数就是一个典型的消息处理函数。在这个函数中明确的处理了3个消息,分别是WM_KEYDOWN(击键)、WM_RBUTTONDOWN(鼠标右键按下)、WM_CLOSE(关闭窗口)、WM_DESTROY(销毁窗口)。值得注意的是,应用程序发送到窗口的消息远远不止以上这几条,象WM_SIZE、WM_MINIMIZE、WM_CREATE、WM_MOVE等频繁使用的消息就有几十条。在附录中可以查到Windows常见消息列表。
为了减轻编程的负担,Windows提供了DefWindowProc( )函数来处理这些最常用的消息,调用了这个函数后,这些消息将按照系统默认的方式得到处理。因此,在消息处理函数中,只须处理那些有必要进行特别响应的消息,其余的消息都可交给DefWindowProc( )函数来处理。
3.4 常用Windows函数
3.4.1 显示对话框
MessageBox函数可以用来显示对话框,它的原形是:
int MessageBox(HWND hwndParent, LPCSTR lpszText, LPCSTR lpszTitle, UINT fuStyle);
其中的四个参数依次为:窗口句柄,文字内容,标题,风格。常用风格有:MB_OK、MB_OKCANCEL、MB_RETRYCANCEL、MB_YESNO、MB_YESNOCANCEL,代表对话框有哪些按钮。常用返回值有IDCANCEL、IDNO、IDOK、IDRETRY、IDYES,代表哪个按钮被按下。
3.4.2 定时器
定时器可以使程序每隔一段时间执行一个函数。用法如下:
SetTimer(HWND hwnd, UINT ID, UINT Elapse, TIMERPROC TimerFunc);
四个参数依次为窗口句柄、定时器标识(同一程序内各个定时器的标识应不相同,一般从1、2、3...一直排下去)、每隔多少毫秒(千分之一秒)执行一次程序,要执行的过程。
这个要执行的过程应这样定义:
void CALLBACK MyTimer(HWND hwnd,UINT uMsg,UINT idEvent,DWORD dwTime);
这几个规定的参数都没什么用,我们在过程里作自己的事就行了,不用理这几个给我们的参数。
注意:定时器的优先级不高,当处理器很忙时我们需要定时执行的程序常常不能按时地执行;无论你把定时器的Elapse设得多小,它实际上最小只能是55ms;有的Windows函数在TimerFunc中用不了,而且在TimerFunc里不要做一些费时间的东西。
3.4.3 得到时间
我们经常需要在程序中得到当前的准确时间来完成测试速度等工作。这时我们可以使用GetTickCount( ),因为该函数可以返回Windows已经运行了多少毫秒。然而有时我们需要得到更准确的时间,这时可使用这种方法:
__int64 time2, freq; //时间,计时器频率
double time; //以秒为单位的时间
QueryPerformanceCounter((LARGE_INTEGER*)&time2); //得到计时开始的时间
QueryPerformanceFrequency((LARGE_INTEGER*)&freq); //得到计时器频率
time = (double)(time2) / (double)freq; //将时间转为以秒为单位
3.4.4 播放声音
我们可以使用MCI来简易地实现在程序中播放MIDI和WAV等声音。使用它需要预先声明,我们需要在文件头#include <mmsystem.h>,并在工程中加入"winmm.lib"
下面先让我们看看播放MIDI的过程。首先我们要打开设备:
MCI_OPEN_PARMS OpenParms;
OpenParms.lpstrDeviceType =
(LPCSTR) MCI_DEVTYPE_SEQUENCER; //是MIDI类型文件
OpenParms.lpstrElementName = (LPCSTR) filename; //文件名
OpenParms.wDeviceID = 0; //打开的设备的标识,后面需要使用
mciSendCommand (NULL, MCI_OPEN,
MCI_WAIT | MCI_OPEN_TYPE |
MCI_OPEN_TYPE_ID | MCI_OPEN_ELEMENT,
(DWORD)(LPVOID) &OpenParms); //打开设备
接着就可以播放MIDI了:
MCI_PLAY_PARMS PlayParms;
PlayParms.dwFrom = 0; //从什么时间位置播放,单位为毫秒
mciSendCommand (DeviceID, MCI_PLAY, //DeviceID需等于上面的设备标识
MCI_FROM, (DWORD)(LPVOID)&PlayParms); //播放MIDI
停止播放:
mciSendCommand (DeviceID, MCI_STOP, NULL, NULL);
最后要关闭设备:
mciSendCommand (DeviceID, MCI_CLOSE, NULL, NULL);
打开WAV文件与打开MIDI文件的方法几乎完全相同,只是需要将MCI_DEVTYPE_SEQUENCER 改为MCI_DEVTYPE_WAVEFORM_AUDIO。