知道如何使用Windows计时器之后,可以看看一些有用的计时器应用程序了。时钟是计时器最明显的应用,我们来看两个例子:一个是数字时钟,另一个是模拟时钟。
本节必须掌握的知识点:
第45练:7段数码管数字时钟
第46练:模拟时钟
7.3.1 第45练:7段数码管数字时钟
/*------------------------------------------------------------------
045 WIN32 API 每日一练
第45个例子DIGCLOCK.C:使用计时器---实现7段数码管数字时钟
查表法
OffsetWindowOrgEx函数
GetLocaleInfo函数
(c) www.bcdaren.com, 2020
----------------------------------------------------------------*/
#include <windows.h>
#define ID_TIMER 1
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT("DigClock");
…(略)
return msg.wParam;
}
//显示数字
void DisplayDigit (HDC hdc, int iNumber)
{
static BOOL fSevenSegment [10][7] = { //7段数码管表
1, 1, 1, 0, 1, 1, 1, // 0
0, 0, 1, 0, 0, 1, 0, // 1
1, 0, 1, 1, 1, 0, 1, // 2
1, 0, 1, 1, 0, 1, 1, // 3
0, 1, 1, 1, 0, 1, 0, // 4
1, 1, 0, 1, 0, 1, 1, // 5
1, 1, 0, 1, 1, 1, 1, // 6
1, 0, 1, 0, 0, 1, 0, // 7
1, 1, 1, 1, 1, 1, 1, // 8
1, 1, 1, 1, 0, 1, 1 } ; // 9
static POINT ptSegment [7][6] = { //每根数码管六边形的顶点坐标
7, 6, 11, 2, 31, 2, 35, 6, 31, 10, 11, 10,
6, 7, 10, 11, 10, 31, 6, 35, 2, 31, 2, 11,
36, 7, 40, 11, 40, 31, 36, 35, 32, 31, 32, 11,
7 , 36, 11, 32, 31, 32, 35, 36, 31, 40, 11, 40,
6 , 37, 10, 41, 10, 61, 6, 65, 2, 61, 2, 41,
36, 37, 40, 41, 40, 61, 36, 65, 32, 61, 32, 41,
7 , 66, 11, 62, 31, 62, 35, 66, 31, 70, 11, 70 } ;
int iSeg ;
for (iSeg = 0 ; iSeg < 7 ; iSeg++) //绘制数字
if (fSevenSegment [iNumber][iSeg])
Polygon (hdc, ptSegment [iSeg], 6) ; //绘制6边形
}
//显示2个数字,fSuppress---前导0标记值
void DisplayTwoDigits (HDC hdc, int iNumber, BOOL fSuppress)
{
if (!fSuppress || (iNumber / 10 != 0))
DisplayDigit(hdc, iNumber / 10);//不为0绘制第1位数
OffsetWindowOrgEx(hdc, -42, 0, NULL);//原点坐标水平右移42单位宽
DisplayDigit(hdc, iNumber % 10); //绘制第2位数
OffsetWindowOrgEx(hdc, -42, 0, NULL);//再次将原点坐标右移42个单位宽
}
void DisplayColon (HDC hdc) //显示冒号
{
POINT ptColon[2][4] = { 2, 21, 6, 17, 10, 21, 6,
25, 2, 51, 6, 47, 10, 51, 6, 55 };
//绘制多边形-冒号
Polygon(hdc, ptColon[0], 4);
Polygon(hdc, ptColon[1], 4);
OffsetWindowOrgEx(hdc, -12, 0, NULL);//右移原点坐标--冒号12单位宽
}
//显示时间
void DisplayTime (HDC hdc, BOOL f24Hour, BOOL fSuppress)
{
SYSTEMTIME st;
GetLocalTime(&st);//获取当前时间
if (f24Hour)//24小时显示格式
DisplayTwoDigits(hdc, st.wHour, fSuppress);//时
else //12小时显示格式
DisplayTwoDigits(hdc, (st.wHour %= 12) ? st.wHour : 12, fSuppress);//时
DisplayColon(hdc);//冒号
DisplayTwoDigits(hdc, st.wMinute, FALSE);//分
DisplayColon(hdc);//冒号
DisplayTwoDigits(hdc, st.wSecond, FALSE);//秒
}
//窗口过程
LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam)
{
static BOOL f24Hour, fSuppress ;
static HBRUSH hBrushRed ;
static int cxClient, cyClient ;
HDC hdc ;
PAINTSTRUCT ps ;
TCHAR szBuffer [2] ;
switch (message)
{
case WM_CREATE:
hBrushRed = CreateSolidBrush (RGB (255, 0, 0)) ; //创建红色画刷
SetTimer (hwnd, ID_TIMER, 1000, NULL) ;// 设置计时器
//继续往下执行
case WM_SETTINGCHANGE: //检索系统消息
//检索用户或进程的默认语言环境-时间格式
GetLocaleInfo (LOCALE_USER_DEFAULT, LOCALE_ITIME, szBuffer, 2) ;
//时间格式:'1'为24小时格式,'0'为12小时格式
f24Hour = (szBuffer[0] == '1') ;
//LOCALE_ITLZERO是否保留小时数的前导0
GetLocaleInfo (LOCALE_USER_DEFAULT, LOCALE_ITLZERO, szBuffer, 2) ;
fSuppress = (szBuffer[0] == '0') ; //小时数-TRUE前导0
InvalidateRect (hwnd, NULL, TRUE) ; //重绘窗口
return 0 ;
case WM_SIZE:
cxClient = LOWORD(lParam);
cyClient = HIWORD(lParam);
return 0;
case WM_TIMER:
InvalidateRect(hwnd, NULL, TRUE);//重绘窗口工作区
return 0;
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
SetMapMode(hdc, MM_ISOTROPIC);//指定设备上下文的映射模式-等比例缩放
//MM_ISOTROPIC映射模式可以修改窗口或视口范围
SetWindowExtEx(hdc, 276, 72, NULL);//窗口设备上下文的水平和垂直范围
//设置为视口设备上下文的水平和垂直范围
SetViewportExtEx(hdc, cxClient, cyClient, NULL);
SetWindowOrgEx(hdc, 138, 36, NULL);//指定窗口点映射到视区原点(0,0)
//指定设备点映射到窗口原点(0,0)
SetViewportOrgEx(hdc, cxClient / 2, cyClient / 2, NULL);
SelectObject(hdc, GetStockObject(NULL_PEN));//选入设备-空笔,无边框
SelectObject(hdc, hBrushRed);//选入画刷
DisplayTime(hdc, f24Hour, fSuppress);//显示时间
EndPaint(hwnd, &ps);
return 0;
case WM_DESTROY:
KillTimer(hwnd, ID_TIMER);//删除计时器
DeleteObject(hBrushRed);//删除画刷
PostQuitMessage(0);
return 0;
}
return DefWindowProc (hwnd, message, wParam, lParam) ;
}
/***************************************************************************
GetLocaleInfo函数:检索有关标识符指定的语言环境的信息
int GetLocaleInfoA(
LCID Locale, //要检索其信息的语言环境标识符。
LCTYPE LCType, //要检索的语言环境信息。
LPSTR lpLCData, //指向缓冲区指针,该函数在该缓冲区中检索请求的语言环境信息。
int cchData //lpLCData指示的数据缓冲区的大小(以TCHAR值表示)。
);
***************************************************************************
OffsetWindowOrgEx函数:修改为使用指定的水平和垂直偏移的设备上下文窗口原点。
BOOL OffsetWindowOrgEx(
HDC hdc, //设备上下文
int x, //水平偏移量,以逻辑单位表示。
int y, //垂直偏移量,以逻辑单位表示。
LPPOINT lppt //指向POINT结构的指针。先前窗口原点的逻辑坐标位于此结构中。如果lpPoint为NULL,则不返回先前的原点。
);
*/
运行结果:
图7-3 7段数码管数字时钟
总结
实例DIGCLOCK.C的逻辑其实挺简单,之所以看起来稍微复杂是因为使用像素点绘制时间数字。
1.处理WM_CREATE消息是,创建红色画刷和一个计时器。
2.处理WM_SETTINGCHANGE消息,检索系统信息。
//检索用户或进程的默认语言环境-时间格式
GetLocaleInfo (LOCALE_USER_DEFAULT, LOCALE_ITIME, szBuffer, 2) ;
//时间格式:'1'为24小时格式,'0'为12小时格式
f24Hour = (szBuffer[0] == '1') ;
//LOCALE_ITLZERO是否保留小时数的前导0
GetLocaleInfo (LOCALE_USER_DEFAULT, LOCALE_ITLZERO, szBuffer, 2) ;
fSuppress = (szBuffer[0] == '0') ; //小时数-TRUE前导0
最后调用InvalidateRect函数重绘窗口。
3.处理WM_SIZE消息获取客户区的宽和高。
4.处理WM_TIMER消息重绘窗口。
5.处理WM_PAINT消息绘制7段数码管时间。
首先调用SetMapMode函数将映射模式设置为MM_ISOTROPIC等比例拉伸模式。
然后分别设置窗口和视口的原点坐标和范围,将数字显示在客户区的中心位置。
最后选入空画笔和红色画刷,调用DisplayTime函数绘制无边框的红色时间数字。
DisplayTime函数的实现:
依据标记变量f24Hour判断当前系统的时间格式是24小时格式还是12小时格式,绘制两位数字“小时”。接着绘制冒号,然后再绘制两位数字时间“分”和冒号,最后绘制两位数字时间“秒”。单个数字和冒号直接采用像素矩阵的方法,分别由DisplayDigit函数和DisplayColon函数完成。
6.处理WM_DESTROY消息销毁计时器,删除红色画刷。
7.3.2 第46练:模拟时钟
/*------------------------------------------------------------------
046 WIN32 API 每日一练
第46个例子CLOCK.C:使用计时器---模拟时钟
RotatePoint函数-顺时针旋转
SetIsotropic函数---设置视窗参数
DrawClock 函数---画时钟
DrawHands函数---画指针
(c) www.bcdaren.com, 2020
----------------------------------------------------------------*/
#include <windows.h>
#include <math.h>
#define ID_TIMER 1
#define TWOPI (2 * 3.14159)
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain ( HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT("Clock");
…(略)
return msg.wParam;
}
//设置视窗参数
void SetIsotropic (HDC hdc, int cxClient, int cyClient)
{
SetMapMode(hdc, MM_ISOTROPIC);//映射模式-下,x,y轴等比例缩放
//指定的值设置窗口设备上下文的水平和垂直范围。
SetWindowExtEx(hdc, 1000, 1000, NULL);
//使用指定的值设置为视口设备上下文的水平和垂直范围
SetViewportExtEx(hdc, cxClient / 2, -cyClient / 2, NULL);
SetViewportOrgEx(hdc, cxClient / 2, cyClient / 2, NULL);//设置视口原点
}
//顺时针旋转,公式:x = x* cos(α) + y* sin(α) ;y =y* cos(α)-x* sin(α)
void RotatePoint (POINT pt[], int iNum, int iAngle)
{
int i;
POINT ptTemp;
for (i = 0; i < iNum; i++)
{
ptTemp.x = (int)(pt[i].x * cos(TWOPI * iAngle / 360) +
pt[i].y * sin(TWOPI * iAngle / 360));
ptTemp.y = (int)(pt[i].y * cos(TWOPI * iAngle / 360) -
pt[i].x * sin(TWOPI * iAngle / 360));
pt[i] = ptTemp;
}
}
//画时钟的刻度
void DrawClock (HDC hdc)
{
int iAngle;
POINT pt[3];
for (iAngle = 0; iAngle < 360; iAngle += 6)
{
pt[0].x = 0;
pt[0].y = 900;
//顺时针旋转
RotatePoint(pt, 1, iAngle);
pt[2].x = pt[2].y = iAngle % 5 ? 33 : 100;//圆的大小(直径)
//Ellipse左上角的矩形坐标点
pt[0].x -= pt[2].x / 2;
pt[0].y -= pt[2].y / 2;
//Ellipse右下角的矩形坐标点
pt[1].x = pt[0].x + pt[2].x;
pt[1].y = pt[0].y + pt[2].y;
//选人黑色画刷
SelectObject(hdc, GetStockObject(BLACK_BRUSH));
//绘制椭圆
Ellipse(hdc, pt[0].x, pt[0].y, pt[1].x, pt[1].y);
}
}
//画指针
void DrawHands (HDC hdc, SYSTEMTIME * pst, BOOL fChange)
{
static POINT pt[3][5] = { 0, -150, 100, 0, 0, 600, -100, 0, 0, -150,//时针
0, -200, 50, 0, 0, 800, -50, 0, 0, -200, //分针
0,0, 0, 0, 0, 0, 0, 0, 0, 800 }; //秒针
int i, iAngle[3];
POINT ptTemp[3][5];
iAngle[0] = (pst->wHour * 30) % 360 + pst->wMinute / 2;//时针每小时30度。
iAngle[1] = pst->wMinute * 6;//每分钟,分针走6度。
iAngle[2] = pst->wSecond * 6;//每秒,秒针走6度。
memcpy(ptTemp, pt, sizeof(pt));
//判断是否只画秒针0-1-2或2
for (i = fChange ? 0 : 2; i < 3; i++)
{
RotatePoint(ptTemp[i], 5, iAngle[i]);
Polyline(hdc, ptTemp[i], 5);
}
}
LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam,LPARAM
lParam)
{
static int cxClient, cyClient;
static SYSTEMTIME stPrevious;
BOOL fChange;
HDC hdc;
PAINTSTRUCT ps;
SYSTEMTIME st;
switch (message)
{
case WM_CREATE:
//创建计时器,时间间隔1秒,不一定准确,与消息队列相关
SetTimer(hwnd, ID_TIMER, 1000, NULL);
GetLocalTime(&st);//获取本地时间
stPrevious = st;
return 0;
case WM_SIZE:
cxClient = LOWORD(lParam);
cyClient = HIWORD(lParam);
return 0;
case WM_TIMER:
GetLocalTime(&st);
//小时和分钟是否改变
fChange = st.wHour != stPrevious.wHour ||
st.wMinute != stPrevious.wMinute;
hdc = GetDC(hwnd);
//设置窗口参数
SetIsotropic(hdc, cxClient, cyClient);
//选入白色画刷
SelectObject(hdc, GetStockObject(WHITE_PEN));
DrawHands(hdc, &stPrevious, fChange);//画指针--擦除指针
//选入黑色画笔
SelectObject(hdc, GetStockObject(BLACK_PEN));
DrawHands(hdc, &st, TRUE);//画指针--重绘
ReleaseDC(hwnd, hdc);
stPrevious = st;
return 0;
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
//设置窗口参数
SetIsotropic(hdc, cxClient, cyClient);
DrawClock(hdc);//画刻度圆点
DrawHands(hdc, &stPrevious, TRUE);//画时针,分针,秒针
EndPaint(hwnd, &ps);
return 0;
case WM_DESTROY:
KillTimer(hwnd, ID_TIMER);
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
运行结果:
图7-4 模拟时钟
总结
●实例CLOCK.C定义了4个功能函数。
1.RotatePoint函数:顺时针旋转。公式:x = x* cos(α) + y* sin(α) ;y =y* cos(α)-x* sin(α)。
2.SetIsotropic函数:设置视窗参数。映射模式设置为等比例缩放模式,设置窗口和视口范围,并将视口原点坐标设置到客户区中心位置。
3.DrawClock 函数:画时钟。需要绘制12个直经为100像素的大圆和48个直经为33像素的小圆。每旋转6度绘制一个。
4.DrawHands函数:画指针。根据像素矩阵绘制时针、分针和秒针。时针每小时走30度,每分钟,分针走6度,每秒,秒针走6度。此外还需要判断是否只需要绘制秒针:
//判断是否只画秒针0-1-2或2
for (i = fChange ? 0 : 2; i < 3; i++)
{
RotatePoint(ptTemp[i], 5, iAngle[i]);
Polyline(hdc, ptTemp[i], 5);
}
●剩下的是窗口过程相对简单多了。
1.处理WM_CREATE消息,调用SetTimer函数创建一个计时器,并调用GetLocalTime函数获取本地时间。
2.处理WM_SIZE消息,获取客户区的宽和高。
3.处理WM_TIMER消息,先调用SetIsotropic设置窗口参数,然后选入白色画笔,擦除原有的时针、分针和秒针,接着再选入黑色画笔,重新绘制新的时针、分针和秒针。
4.处理WM_PAINT消息,调用SetIsotropic设置窗口参数。调用DrawClock绘制时间刻度圆点。调用DrawHands函数绘制时针、分针和秒针。
5.最后处理WM_DESTROY消息,销毁计时器。