//本文借鉴万立中老师的一篇博客,增加了些注释,感觉是很好的sdk编程的案例之一。
#include <windows.h>
#include <time.h>
#include <iostream>
using namespace std;
int bX, bY;//记录方块移动的坐标(方块左上角坐标)
int b_Speed;//方块移动的速度(一次移动的数目)
RECT WinRect;//窗口的大小
//声明回调函数
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, 690, 520,
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;
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) //(因为可能会有随机负数,大于0则x移动,否则y轴移动)
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);
}
定时器在实际编程中使用频率比较高,例如一些需要间隔一定时间自动执行的任务,如果任务执行对时间精度要求不是太苛求,使用简单的定时器就是一个较好的选择。当然,由于定时器在系统的优先级较低,有时在执行具体任务时可能会遇到一些意想不到的问题。如果这样的话,可以考虑直接使用线程或调用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类型的任务函数。
为了说明问题,我们就在窗口实现一个随机移动的方块作为我们的定制绘制任务。下面分别对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消息中编写具体的绘制代码。对修改后的代码重新进行编译,然后执行代码,会在窗口中看到一个不断随机移动的红色方块,当方块达到边界后会返回朝相反的方向移动。
我们知道,sdk的方式是调用函数,所以我们可以把处理定时器的一部分封装成函数,如下:
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);
}
最后注意将SetTimer函数的最后一个参数修改为函数入口的地址,如下:
SetTimer(hWnd, 1, 10, (TIMERPROC)TimerFunc);
改变后的代码如下:
#include <windows.h>
#include <time.h>
#include <iostream>
using namespace std;
int bX, bY;//记录方块移动的坐标(方块左上角坐标)
int b_Speed;//方块移动的速度(一次移动的数目)
RECT WinRect;//窗口的大小
//声明回调函数
LONG CALLBACK MyWndProc(HWND, UINT, WPARAM, LPARAM);
void CALLBACK TimerFunc(HWND hWnd,UINT nMsg,UINT nTimerid,DWORD dwTime);
//========================================================
//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, 690, 520,
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;
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)TimerFunc);
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);
}
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);
}