📄 8. 定时器.txt
字号:
return 0 ;
case WM_TIMER:
InvalidateRect (hwnd, NULL, TRUE) ;
return 0 ;
case WM_PAINT:
hdc = BeginPaint (hwnd, &ps) ;
SetMapMode (hdc, MM_ISOTROPIC) ;
SetWindowExtEx (hdc, 276, 72, NULL) ;
SetViewportExtEx (hdc, cxClient, cyClient, NULL) ;
SetWindowOrgEx (hdc, 138, 36, NULL) ;
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) ;
}
DIGCLOCK窗口如图8-1所示。
图8-1 DIGCLOCK的屏幕显示
虽然,在图8-1中您看不到时钟的数字是红色的。DIGCLOCK的窗口消息处理程序在处理WM_CREATE消息处理期间建立了一个红色的画刷并在处理WM_DESTROY消息处理期间清除它。WM_CREATE消息也为DIGCLOCK设定了一个一秒的定时器,该定时器在处理WM_DESTROY消息处理期间被终止(待会将讨论对GetLocaleInfo的呼叫)。
在收到WM_TIMER消息后,DIGCLOCK的窗口过程调用InvalidateRect简单地使整个窗口无效。这不是最佳方法,因为每秒整个窗口都要被擦除和重画,有时会引起显示器的闪烁。依据目前的时间使窗口需要更新的部分无效是最好的解决方法。然而,在逻辑上这样做的确很复杂。
在处理WM_TIMER消息处理期间使窗口无效会迫使所有程序的真正活动转入WM_PAINT。DIGCLOCK在WM_PAINT消息一开始将映像方式设定为MM_ISOTROPIC。这样,DIGCLOCK将使用水平方向和垂直方向相等的轴。这些轴(由SetWindowExtEx呼叫设定)是水平276个单位,垂直72个单位。当然,这些轴定得有点太随意了,但它们是按照时钟数字元的大小和间距安排的。
DIGCLOCK将窗口原点设定为(138,36),这是窗口范围的中心;将视埠原点设定为(cxClient / 2,cyClient / 2)。这意味着时钟的显示位于DIGCLOCK显示区域的中心,但是该DIGCLOCK也可以使用在显示屏左上角的原点(0, 0)的轴。
然后WM_PAINT将目前画刷设定为之前建立的红画刷,将目前画笔设定为NULL_PEN,并呼叫DIGCLOCK中的函数DisplayTime。
取得目前时间
DisplayTime函数开始呼叫Windows函数GetLocalTime,它带有一个的SYSTEMTIME结构的参数,在WINBASE.H中定义为:
typedef struct _SYSTEMTIME
{
WORD wYear ;
WORD wMonth ;
WORD wDayOfWeek ;
WORD wDay ;
WORD wHour ;
WORD wMinute ;
WORD wSecond ;
WORD wMilliseconds ;
}
SYSTEMTIME, * PSYSTEMTIME ;
很明显,SYSTEMTIME结构包含日期和时间。月份由1开始递增(也就是说,一月是1),星期由0开始递增(星期天是0)。wDay成员是本月目前的日子,也是由1开始递增的。
SYSTEMTIME主要用于GetLocalTime和GetSystemTime函数。GetSystemTime函数传回目前的世界时间(Coordinated Universal Time,UTC),大概与英国格林威治时间相同。GetLocalTime函数传回当地时间,依据计算机所在的时区。这些值的精确度完全决定于使用者所调整的时间精确度以及是否指定了正确的时区。可以双击工作列的时间显示来检查计算机上的时区设定。 第二十三章会有一个程序,能够通过Internet精确地设定时间。
Windows还有SetLocalTime和SetSystemTime函数,以及在/Platform SDK/Windows Base Services/General Library/Time中说明的其它与时间有关的函数。
显示数字和冒号
如果DIGCLOCK使用一种仿真7段显示的字体将会简单一些。否则,它就得使用Polygon函数做所有的工作。
DIGCLOCK中的DisplayDigit函数定义了两个数组。fSevenSegment数组有7个BOOL值,用于从0到9的每个十进制数。这些值指出了哪一段需要显示(为1),哪一段不需要显示(为0)。在这个数组中,7段由上到下、由左到右排序。7段中的每个段都是一个6边的多边形。ptSegment数组是一个POINT结构的数组,指出了7个段中每个点的图形坐标。每个数字由下列程序代码画出:
for (iSeg = 0 ; iSeg < 7 ; iSeg++)
if ( fSevenSegment [iNumber][iSeg])
Polygon (hdc, ptSegment [iSeg], 6) ;
类似地(但更简单),DisplayColon函数在小时与分钟、分钟与秒之间画一个冒号。数字是42个单位宽,冒号是12个单位宽,因此6个数字与2个冒号,总宽度是276个单位,SetWindowExtEx呼叫中使用了这个大小。
回到DisplayTime函数,原点位于最左数字位置的左上角。DisplayTime呼叫DisplayTwoDigits,DisplayTwoDigits呼叫DisplayDigit两次,并且在每次呼叫OffsetWindowOrgEx后,将窗口原点向右移动42个单位。类似地,DisplayColon函数在画完冒号后,将窗口原点向右移动12个单位。用这种方法,不管对象出现在窗口内的哪个地方,函数对数字和冒号都使用同样的坐标。
这个程序的其它技巧是以12小时或24小时的格式显示时间以及当最左边的小时数字为0时不显示它。
国际化
尽管像DIGCLOCK这样显示时间是非常简单的,但是要显示复杂的日期和时间还是要依赖Windows的国际化支持。格式化日期和时间的最简单的方法是呼叫GetDateFormat和GetTimeFormat函数。这些函数在/Platform SDK/Windows Base Services/General Library/String Manipulation/String Manipulation Reference/String Manipulation Functions中有记载,但是它们在/Platform SDK/Windows Base Services/International Features/National Language Support中进行了说明。这些函数接受SYSTEMTIME结构并且依据使用者在「控制台」的「区域设定」 程序中所做的选择而将日期和时间格式化。
DIGCLOCK不能使用GetDateFormat函数,因为它只知道显示数字和冒号,然而,DIGCLOCK应该能够根据使用者的参数选择来显示12小时或24小时的格式,并禁止(或不禁止)开头的小时数字。您可以从GetLocaleInfo函数中取得这种信息。虽然GetLocaleInfo在/Platform SDK/Windows Base Services/General Library/String Manipulation/String Manipulation Reference/String Manipulation Functions中有记载,但是这个函数使用的标识符在/Platform SDK/Windows Base Services/International Features/National Language Support/National Language Support Constants中有说明。
DIGCLOCK在处理WM_CREATE消息时,最初呼叫GetLocaleInfo两次,第一次使用LOCALE_ITIME标识符(确定使用的是12小时还是24小时格式),然后使用LOCALE_ITLZERO标识符(在小时显示中禁止前面显示0)。GetLocaleInfo函数在字符串中传回所有的信息,但是在大多数情况下把字符串转变为整数并不是非常容易。DIGCLOCK把字符串储存在两个静态变量中并把它们传递给DisplayTime函数。
如果使用者更改了任何系统设定,则会将WM_SETTINGCHANGE消息传送给所有的应用程序。DIGCLOCK通过再次呼叫GetLocaleInfo处理这个消息。以这种方式,您可以在「控制台」的「区域设定」 程序中进行不同的设定来实验一下。
在理论上,DIGCLOCK也应该使用LOCALE_STIME标识符呼叫GetLocaleInfo。这会传回使用者为时间的小时、分钟和秒等单个部分选择的字符。因为DIGCLOCK被设定为仅显示冒号,所以不管选择了什么,都会得到冒号。要指出时间是A.M.或P.M.,应用程序可以使用带有LOCALE_S1159和LOCALE_S2359标识符的GetLocaleInfo函数。这些标识符使程序获得适合于使用者国家/地区和语言的字符串。
我们也可以让DIGCLOCK处理WM_TIMECHANGE消息,这样它将系统时间与日期发生变化的消息通知应用程序。DIGCLOCK因WM_TIMER消息而每秒更新一次,实际上没有必要这样作,对WM_TIMECHANGE消息的处理使得每分钟更新一次的时钟变得更为合理。
建立模拟时钟
模拟时钟不必关心国际化问题,但是由于图形所引起的复杂性却抵消了这种简化。为了正确地产生时钟,您需要知道一些三角函数。CLOCK如程序8-4所示。
程序8-4 CLOCK
CLOCK.C
/*---------------------------------------------------------------------------
CLOCK.C -- Analog Clock Program
(c) Charles Petzold, 1998
---------------------------------------------------------------------------*/
#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") ;
HWND hwnd;
MSG msg;
WNDCLASS wndclass ;
wndclass.style = CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = NULL ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szAppName ;
if (!RegisterClass (&wndclass))
{
MessageBox ( NULL, TEXT ("Program requires Windows NT!"),
szAppName, MB_ICONERROR) ;
return 0 ;
}
hwnd = CreateWindow (szAppName, TEXT ("Analog Clock"),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL) ;
ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;
while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
return msg.wParam ;
}
void SetIsotropic (HDC hdc, int cxClient, int cyClient)
{
SetMapMode (hdc, MM_ISOTROPIC) ;
SetWindowExtEx (hdc, 1000, 1000, NULL) ;
SetViewportExtEx (hdc, cxClient / 2, -cyClient / 2, NULL) ;
SetViewportOrgEx (hdc, cxClient / 2, cyClient / 2, NULL) ;
}
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 ;
pt[0].x - = pt[2].x / 2 ;
pt[0].y - = pt[2].y / 2 ;
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 ;
iAngle[1] = pst->wMinute * 6 ;
iAngle[2] = pst->wSecond * 6 ;
memcpy (ptTemp, pt, sizeof (pt)) ;
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 ;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -