定时器在实际编程中使用频率比较高,例如一些需要间隔一定时间自动执行的任务,如果任务执行对时间精度要求不是太苛求,使用简单的定时器就是一个较好的选择。当然,由于定时器在系统的优先级较低,有时在执行具体任务时可能会遇到一些意想不到的问题。如果这样的话,可以考虑直接使用线程或调用GetTickCount函数自己处理。对于一些简单的游戏编程,定时器完全可以胜任。本文以VC6.0为环境说明定时器的使用方法。
在Windows编程中,可以使用SetTimer函数设置并启动定时器。该函数在SDK的API定义中有四个参数,原型定义如下:
UINT SetTimer( HWND hWnd,
UINT nIDEvent,
UINT uElapse,
TIMERPROC lpTimerFunc );
参数含义:
n hWnd
与定时器关联的窗口句柄。该窗口必须被调用的线程所拥有。如果该参数设为NULL,则意味着定时器没有需要关联的窗口,那么第二个参数nIDEvent就会被忽略;
n nIDEvent
指定一个非0的标识。如果hWnd参数为NULL,该参数被忽略;
n uElapse
定时器间隔的时间,以毫秒为单位;
n lpTimerFunc
指定的间隔时间自动执行的回调函数的指针;如果该参数为NULL,系统则自动向英勇程序队列发送WM_TIMER 消息;如果不为NULL,则会自动执行所指定的回调函数。该回调函数的定义如下:
void CALLBACK TimerProc(HWND hwnd,
UINT uMsg,
UINT idEvent,
DWORD dwTime );
回调函数的参数含义:
l hwnd: 要与定时器关联的窗口句柄;
l uMsg: 指定WM_TIMER消息;
l idEvent:定时器的标识;
l dwTime:指定自系统启动后的已经过去的毫秒数,该值由GetTickCount函数获得。
从以上SetTimer函数的定义可以看出,使用定时器时,有两个地方可以用来编写定时器要执行的任务代码:(1)WM _TIMER消息中;(2)自定义的TIMERPROC类型的任务函数。
由于采用SDK方式编成,如果要在Windows窗口中进行绘制,首先必须先生成一个空白窗口(没有菜单、工具栏或状态栏),为此需要创建一个Win32 Application类型的空工程,然后为工程增加一个源文件(.cpp文件),将下面创建空白窗口的代码复制到源文件中:
#include <windows.h>
//声明回调函数
LONG CALLBACK MyWndProc(HWND, UINT, WPARAM, LPARAM);
//========================================================
//WinMain函数
//========================================================
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
MSG msg;
HWND hWnd;
WNDCLASSEX wnd;
wnd.cbSize = sizeof(WNDCLASSEX);
wnd.style = CS_HREDRAW | CS_VREDRAW;
wnd.lpfnWndProc = (WNDPROC)MyWndProc;
wnd.cbClsExtra = 0;
wnd.cbWndExtra = 0;
wnd.hInstance = hInstance;
wnd.hIcon = NULL;
wnd.hCursor = LoadCursor(NULL, IDC_ARROW);
wnd.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wnd.lpszMenuName = NULL;
wnd.lpszClassName = "TimerApp";
wnd.hIconSm = NULL;
RegisterClassEx(&wnd);
hWnd = CreateWindow( "TimerApp", "Timer测试程序",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, 640, 480,
NULL, NULL, hInstance, NULL);
if (!hWnd) return FALSE;
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
//========================================================
//回调函数
//========================================================
LONG CALLBACK MyWndProc (HWND hWnd, UINT message,
WPARAM wParam, LPARAM lParam)
{
HDC hdc;
switch (message) {
case WM_PAINT:
PAINTSTRUCT ps;
hdc = BeginPaint(hWnd, &ps);
EndPaint(hWnd,&ps);
return 0;
case WM_CLOSE:
if(IDOK==MessageBox(NULL,"你确定要退出吗?",
"提示", MB_OKCANCEL|MB_ICONQUESTION)){
DestroyWindow(hWnd);
}
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
以上代码生成一个了可以满足我们需求的空白窗口,窗体的大小为640×480。窗口可以响应三个基本消息WM_PAINT、WM_CLOSE和WM_DESTROY,当关闭窗口时会弹出一个确认对话框。下面我们就以这个框架代码为基础来说明如何增加必要的消息或函数来实现定时的窗口绘制任务。
为了说明问题,我们就在窗口实现一个随机移动的方块作为我们的定制绘制任务。下面分别对SetTimer函数的两种使用方式进行举例说明。
(1) 在WM _TIMER消息中编写任务代码:
首先,由于要用到随机函数以及时间函数,需要在以上代码的include部分增加下面的引用及变量的定义,具体作用见代码注释:
#include <time.h>
#include <iostream>
using namespace std;
int bX, bY;//记录方块移动的坐标
int b_Speed;//方块移动的速度
RECT WinRect;//窗口的大小
接下来,在MyWndProc回调函数中增加WM_CREATE、WM_TIMER消息,其中在WM_CREATE消息中调用SetTimer函数并初始化,需要注意的是,SetTimer函数的第三个参数需要设置为NULL,然后在WM_TIMER消息中改变方块的坐标位置、检测边界并调用InvalidateRect函数发送重绘消息,最后在WM_PAINT消息中编写具体的绘制代码。修改后的MyWndProc回调函数如下:
LONG CALLBACK MyWndProc (HWND hWnd, UINT message,
WPARAM wParam, LPARAM lParam)
{
HDC hdc;
HGDIOBJ mbrush, oldbrush;
int r;
GetClientRect(hWnd, &WinRect);
switch (message) {
case WM_CREATE:
srand((unsigned int)time(NULL));
bX = (WinRect.right-100)/2;
bY = (WinRect.bottom-100)/2;
b_Speed = 5;
//设置定时器,间隔时间为10毫秒
SetTimer(hWnd, 1, 10, (TIMERPROC) NULL);
return 0;
case WM_TIMER:
r = rand()%2;
if(r > 0)
bX = bX + b_Speed;
else
bY = bY + b_Speed;
if(bX>WinRect.right-100 || bY>WinRect.bottom-100 || bX<0 || bY<0)
{
if(bX>WinRect.right-100) bX = WinRect.right-100;
if(bY>WinRect.bottom-100) bY = WinRect.bottom-100;
if(bX<0) bX = 0;
if(bY<0) bY = 0;
b_Speed = -b_Speed;
}
//调用WM_PAINT重绘(并且擦除背景)
InvalidateRect(hWnd, &WinRect, TRUE);
return 0;
case WM_PAINT:
PAINTSTRUCT ps;
hdc = BeginPaint(hWnd, &ps);
mbrush = CreateSolidBrush(RGB(255, 0, 0));
oldbrush = SelectObject(hdc, mbrush);
WinRect.left = bX;
WinRect.right = WinRect.left + 100;
WinRect.top = bY;
WinRect.bottom = WinRect.top+100;
FillRect(hdc, &WinRect, (HBRUSH)mbrush);
SelectObject(hdc, oldbrush);
DeleteObject(mbrush);
EndPaint(hWnd,&ps);
return 0;
case WM_CLOSE:
if(IDOK==MessageBox(NULL,"你确定要退出吗?",
"提示", MB_OKCANCEL|MB_ICONQUESTION))
{
DestroyWindow(hWnd);
}
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
对修改后的代码重新进行编译,然后执行代码,会在窗口中看到一个不断随机移动的红色方块,当方块达到边界后会返回朝相反的方向移动。
(2) 在自定义的TIMERPROC函数中编写任务代码:
如果要使用TIMERPROC函数实现同样的功能,首先需要首先声明一个TIMERPROC类型的回调函数TimerFunc,然后将原来WM_TIMER消息中的代码移到该函数中,并稍作调整,原来的WM_TIMER消息及代码就可以删除。SetTimer函数的第三个参数需要设置为所定义的回调函数名,具体修改如下:
SetTimer(hWnd, 1, 10, (TIMERPROC)TimerFunc);
TimerFunc函数的定义如下(其参数与原型定义一样):
void CALLBACK TimerFunc(HWND hWnd,UINT nMsg,UINT
nTimerid,DWORD dwTime)
{
GetClientRect(hWnd, &WinRect);
int r = rand()%2;
if(r > 0)
bX = bX + b_Speed;
else
bY = bY + b_Speed;
if(bX>WinRect.right-100 || bY>WinRect.bottom-100 || bX<0 || bY<0)
{
if(bX>WinRect.right-100) bX = WinRect.right-100;
if(bY>WinRect.bottom-100) bY = WinRect.bottom-100;
if(bX<0) bX = 0;
if(bY<0) bY = 0;
b_Speed = -b_Speed;
}
//调用WM_PAINT重绘(并且擦除背景)
InvalidateRect(hWnd, &WinRect, TRUE);
}
对修改后的代码重新进行编译,然后执行代码,其运行结果与第一种方式应该是相同的。在以后的时间里将会陆续介绍如何采用MFC方式实现定时器,以及如何在具体的游戏设计中使用定时器等。