8.3 使用计时器作为时钟

来源:互联网 发布:vb opc 西门子 编辑:程序博客网 时间:2024/05/17 21:58

摘录于《Windows程序(第5版,珍藏版).CHarles.Petzold 著》P271

        时钟是计时器最明显的应用,我们来看两个例子:一个是数字时钟,另一个是模拟时钟。

8.3.1  数字时钟

        DIGCLOCK 程序模拟了 LED 的七段显示形式,显示当前的时间。

[cpp] view plain copy
  1. /*-------------------------------------------------------- 
  2.    DIGCLOCK.C --  Digital Clock 
  3.                  (c) Charles Petzold, 1998 
  4.   --------------------------------------------------------*/  
  5. #include <windows.h>  
  6.   
  7. #define ID_TIMER 1  
  8.   
  9. LRESULT CALLBACK WndProc (HWNDUINTWPARAMLPARAM) ;  
  10.   
  11. int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,  
  12.                     PSTR szCmdLine, int iCmdShow)  
  13. {  
  14.      static TCHAR szAppName[] = TEXT ("DigClock") ;  
  15.      HWND         hwnd ;  
  16.      MSG          msg ;  
  17.      WNDCLASS     wndclass ;  
  18.   
  19.      wndclass.style         = CS_HREDRAW | CS_VREDRAW ;  
  20.      wndclass.lpfnWndProc   = WndProc ;  
  21.      wndclass.cbClsExtra    = 0 ;  
  22.      wndclass.cbWndExtra    = 0 ;  
  23.      wndclass.hInstance     = hInstance ;  
  24.      wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;  
  25.      wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;  
  26.      wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;  
  27.      wndclass.lpszMenuName  = NULL ;  
  28.      wndclass.lpszClassName = szAppName ;  
  29.   
  30.      if (!RegisterClass (&wndclass))  
  31.      {  
  32.           MessageBox (NULL, TEXT ("This program requires Windows NT!"),  
  33.                       szAppName, MB_ICONERROR) ;  
  34.           return 0 ;  
  35.      }  
  36.   
  37.      hwnd = CreateWindow (szAppName, TEXT ("Digital Clock"),  
  38.                           WS_OVERLAPPEDWINDOW,  
  39.                           CW_USEDEFAULT, CW_USEDEFAULT,  
  40.                           CW_USEDEFAULT, CW_USEDEFAULT,  
  41.                           NULL, NULL, hInstance, NULL) ;  
  42.   
  43.      ShowWindow (hwnd, iCmdShow) ;  
  44.      UpdateWindow (hwnd) ;  
  45.   
  46.      while (GetMessage (&msg, NULL, 0, 0))  
  47.      {  
  48.           TranslateMessage (&msg) ;  
  49.           DispatchMessage (&msg) ;  
  50.      }  
  51.      return msg.wParam ;  
  52. }  
  53.   
  54. void DisplayDigit (HDC hdc, int iNumber)  
  55. {  
  56.     static BOOL fSevenSegment[10][7] = {  
  57.                     1, 1, 1, 0, 1, 1, 1,    // 0  
  58.                     0, 0, 1, 0, 0, 1, 0,    // 1  
  59.                     1, 0, 1, 1, 1, 0, 1,    // 2  
  60.                     1, 0, 1, 1, 0, 1, 1,    // 3  
  61.                     0, 1, 1, 1, 0, 1, 0,    // 4  
  62.                     1, 1, 0, 1, 0, 1, 1,    // 5  
  63.                     1, 1, 0, 1, 1, 1, 1,    // 6  
  64.                     1, 0, 1, 0, 0, 1, 0,     // 7  
  65.                     1, 1, 1, 1, 1, 1, 1,    // 8  
  66.                     1, 1, 1, 1, 0, 1, 1 };  // 9  
  67.   
  68.     static POINT ptSegment [7][6] = {  
  69.                     7, 6, 11, 2, 31, 2, 35, 6, 31, 10, 11, 10,  
  70.                     6, 7, 10, 11, 10, 31, 6, 35, 2, 31, 2, 11,  
  71.                     36, 7, 40, 11, 40, 31, 36, 35, 32, 31, 32, 11,  
  72.                     7, 36, 11, 32, 31, 32, 35, 36, 31, 40, 11, 40,  
  73.                     6, 37, 10, 41, 10, 61, 6, 65, 2, 61, 2, 41,  
  74.                     36, 37, 40, 41, 40, 61, 36, 65, 32, 61, 32, 41,  
  75.                     7, 66, 11, 62, 31, 62, 35, 66, 31, 70, 11, 70 };  
  76.     int iSeg;  
  77.   
  78.     for (iSeg = 0; iSeg < 7; ++ iSeg)  
  79.         if (fSevenSegment[iNumber][iSeg])  
  80.             Polygon(hdc, ptSegment[iSeg], 6);  
  81. }  
  82.   
  83. void DisplayTwoDigits (HDC hdc, int iNumber, BOOL fSuppress)  
  84. {  
  85.     if (!fSuppress || (iNumber / 10 != 0))  
  86.         DisplayDigit(hdc, iNumber / 10);  
  87.   
  88.     OffsetWindowOrgEx(hdc, -42, 0, NULL);  
  89.     DisplayDigit(hdc, iNumber % 10);  
  90.     OffsetWindowOrgEx(hdc, -42, 0, NULL);  
  91. }  
  92.   
  93. void DisplayColon (HDC hdc)  
  94. {  
  95.     POINT ptColon [2][4] = { 2, 21, 6, 17, 10, 21, 6, 25,  
  96.                              2, 51, 6, 47, 10, 51, 6, 55 };  
  97.   
  98.     Polygon(hdc, ptColon[0], 4);  
  99.     Polygon(hdc, ptColon[1], 4);  
  100.   
  101.     OffsetWindowOrgEx(hdc, -12, 0, NULL);  
  102. }  
  103.   
  104. void DisplayTime (HDC hdc, BOOL f24Hour, BOOL fSuppress)  
  105. {  
  106.     SYSTEMTIME st;  
  107.   
  108.     GetLocalTime (&st);  
  109.   
  110.     if (f24Hour)  
  111.         DisplayTwoDigits(hdc, st.wHour, fSuppress);  
  112.     else  
  113.         DisplayTwoDigits(hdc, (st.wHour %= 12) ? st.wHour : 12, fSuppress);  
  114.   
  115.     DisplayColon(hdc);  
  116.     DisplayTwoDigits(hdc, st.wMinute, FALSE);  
  117.     DisplayColon(hdc);  
  118.     DisplayTwoDigits(hdc, st.wSecond, FALSE);  
  119. }  
  120.   
  121. LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)  
  122. {  
  123.      static BOOL    f24Hour, fSuppress;  
  124.      static HBRUSH  hBrushRed;  
  125.      static int     cxClient, cyClient;  
  126.      HDC            hdc;  
  127.      PAINTSTRUCT    ps;  
  128.      TCHAR          szBuffer [2];  
  129.   
  130.      switch (message)  
  131.      {  
  132.      case WM_CREATE:  
  133.           hBrushRed = CreateSolidBrush(RGB (255, 0, 0));  
  134.           SetTimer(hwnd, ID_TIMER, 1000, NULL);  
  135.   
  136.                                             // fall through  
  137.   
  138.      case WM_SETTINGCHANGE:  
  139.           GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_ITIME, szBuffer, 2);  
  140.           f24Hour   = (szBuffer[0] == '1'); // 0 表示 12小时   1 表示 24小时  
  141.   
  142.           GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_ITLZERO, szBuffer, 2);  
  143.           fSuppress = (szBuffer[0] == '0'); // 0 表示缺位补0   1 表示缺位不补0(Hour)  
  144.   
  145.           InvalidateRect(hwnd, NULL, TRUE);  
  146.           return 0;  
  147.   
  148.      case WM_SIZE:  
  149.           cxClient = LOWORD(lParam);  
  150.           cyClient = HIWORD(lParam);  
  151.           return 0;  
  152.   
  153.      case WM_TIMER:  
  154.           InvalidateRect(hwnd, NULL, TRUE);  
  155.           return 0;  
  156.   
  157.      case WM_PAINT:  
  158.           hdc = BeginPaint(hwnd, &ps);  
  159.   
  160.           SetMapMode(hdc, MM_ISOTROPIC);  
  161.           SetWindowExtEx(hdc, 276, 72, NULL);  
  162.           SetViewportExtEx(hdc, cxClient, cyClient, NULL);  
  163.   
  164.           SetWindowOrgEx(hdc, 138, 36, NULL);  
  165.           SetViewportOrgEx(hdc, cxClient / 2, cyClient / 2, NULL);  
  166.           SelectObject(hdc, GetStockObject(NULL_PEN));  
  167.           SelectObject(hdc, hBrushRed);  
  168.   
  169.           DisplayTime(hdc, f24Hour, fSuppress);  
  170.   
  171.           EndPaint(hwnd, &ps);  
  172.           return 0;  
  173.   
  174.      case WM_DESTROY:  
  175.           KillTimer(hwnd, ID_TIMER);  
  176.           DeleteObject(hBrushRed);  
  177.           PostQuitMessage (0) ;  
  178.           return 0 ;  
  179.      }  
  180.      return DefWindowProc (hwnd, message, wParam, lParam) ;  
  181. }  

        DIGCLOCK 窗口的显示如图 8-4 所示。

图 8-4 DIGCLOCK 的显示

        DIGCLOCK 的窗口过程过程在 WM_CREATE 消息期间产生了一个红色的画刷,并在 WM_DESTROY 消息期间销毁它。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 使用的是原点位于显示区域左上角的坐标系统。

        WM_PAINT 然后把现在的画刷设置为之前产生的红色画刷,把当前的画笔设置为 NULL_PEN,并调用 DIGCLOCK 中的 DisplayTime 函数。

8.3.2  获取当前时间

        DisplayTime 函数首先调用 Windows 函数 GetLocalTime。GetLocalTime 函数需要一个 SYSTEMTIME 结构的参数,该结构在 WINBASE.H 中定义如下:

[cpp] view plain copy
  1. typedef struct _SYSTEMTIME  
  2. {  
  3.     WORD wYear;  
  4.     WORD wMoth;  
  5.     WORD wDayOfWeek;  
  6.     WORD wDay;  
  7.     WORD wHour;  
  8.     WORD wMinute;  
  9.     WORD wSecond;  
  10.     WORD wMilliseconds;  
  11. }  
  12. SYSTEMTIME, * PSYSTEMTIME;  
显而易见,在 SYSTEMTIME 数据结构里,日期和时间都被编码了。月份是从 1 开始的(1 月份就是 1)。周日是从 0 开始的(星期天是 0)。wDay 是再当前月份内的日期,也是从 1 开始的。

        SYSTEMTIME 结构主要用在 GetLocalTime 和 GetSystemTime 这两个函数中。GetSystemTime 函数给出当前的协调世界时(UTC)。UTC 大致与格林威治标准时间(英格兰的格林威治的日期和时间)一致。GetLocalTime 根据计算机的时区给出当地时间。这些时间的值的准确性完全取决于用户是否设置了正确的时区并在在本机上设定了准确的时间。可以双击任务栏上的时钟来查看计算机的时区。在第 23 章,我们将给出一个能根据互联网上精确的时间源为 PC 设置时间的程序。

8.3.3  显示数字和冒号

        如果有一种字体可用来模拟 7 段显示,DIGCLOCK 的实现还可以更简单一些。但这里 DIGCLOCK 必须调用 Polygon 函数来实现所有的显示工作。

        DIGCLOCK 的 DisplayDigit 函数定义了两个数组。fSevenSegment 是一个两维数组,一维表示为 0 到 9 的 10 个数字,另一维为每个数字定义了 7 个布尔值,分别对应 LED 的 7 个线段。当布尔值为 1 时,表示需要显示 LED 中相应的那条线段,值为 0 表示不需要显示相应的那条线段。在这个数组里,7 条线段按上到下、从左到右的顺序排列。每一条线段都是一个六边形。ptSegment 是一个 POINT 结构的数组,标识着六边形图形的每一个顶点的坐标。然后,程序这样画出每个数字:

[cpp] view plain copy
  1. for (iSeg = 0; iSeg < 7; ++ iSeg)  
  2.     if (fSevenSegment[iNumber][iSeg])  
  3.         Polygon(hdc, ptSegment[iSeg], 6);  

        类似地(但更简单),DisplayColon 函数画出小时与分钟及秒钟之间的冒号。数字是 42 个单位宽,冒号是 12 个单位宽,所以 6 个数字加上两个冒号,总宽度为 276 个单位,这就是 SetWindowExtEx 调用所用的尺寸。

        在 DisplayTimer 函数中,原点最初是在最左边的数字的左上角。DisplayTimer 随后调用 DisplayTwoDigits 函数。DisplayTwoDigits 函数会调用两次 Display 函数,每次调用完 DisplayDigit 后,都会调用 OffsetWindowOrgEx 函数把窗口的原点向右移动 42 个单位。类似地,DisplayColon 画完冒号后也会把窗口原点向右移动 12 个单位。这样,这些函数就能使用同一的坐标来画数字和冒号,不管该图形在窗口的实际位置怎样

        剩下的稍复杂的地方就是显示 12 小时或 24 小时的时间格式,以及如果最左边的小时数字是 0,就不显示。

8.3.4  考虑国际化

        虽然像 DIGCLOCK 那样显示时间是相当可靠的,但对复杂一点的日期和时间显示,应该依靠 Windows 的国际支持。最容易的是使用 GetDateFormat 和 GetTimeFormat 来格式化日期和时间。这些函数都接受 SYSTEMTIME 结构,然后按照用户在控制面板的【区域和语言选项】中设置的选项来格式化日期和时间。

        DIGCLOCK 没有使用 GetDateFormat 函数,因为它只知道如何显示数字和冒号。但是,DIGCLOCK 应该尊重用户希望显示 12 小时或 24 小时格式的选择,也要能支持显示或不显示小时的第一个数字。可以调用 GetLocaleInfo 函数获得这些资料。

        DIGCLOCK 一开始处理 WM_CREATE 消息时调用了两次 GetLocaleInfo: 第一次是用 LOCAL_ITIME 参数(以确定是 12 小时或 24 小时格式),第二次是用 LOCALE_ITLZERO 参数(以去掉显示显示开头为零的情况)。GetLocaleInfo 函数以字符串的形式返回所有信息,但大多数情况下,可以相当容易地将返回值转换成整数。在 DIGCLOCK 里,从 GetLocaleInfo 得到的信息分别被存放在两个静态变量中,随后被传入到 DisplayTime 函数。

        如果用户改变任何系统设置,Windows 会广播一个 WM_SETTINGCHANGE 消息,当前正在运行的所有应用程序都会接收到该消息。DIGCLOCK 调用 GetLocaleInfo 来处理这样的消息。这样,你就可以试验不同的地区设置。

        理论上讲,DIGCLOCK 应该使用 LOCAL_STIME 参数来调用 GetLocaleInfo。这样可以使用用户选择的字符来分隔小时、分钟和秒钟。但是因为 DIGCLOCK 设置只能显示冒号,所以无论用户是否选择其他的分隔字符,显示的都永远只是冒号。为了表明时间是上午或下午,应用程序还可以使用 LOCALE_S1159 和 LOCALE_S2359 参数来调用 GetLocaleInfo。这些参数让程序获取适合不同国家和语言的时间格式。

        我们本来也可以让 DIGCLOCK 处理用于通知应用程序系统日期或时间的变化的 WM_TIMECHANGE 消息。但因为 DIGCLOCK 每秒都被 WM_TIMER 消息更新一次,所以便不必处理 WM_TIMECHANGE 了(充分的说明了一件事情,不要做重复的事!)。如果时钟每分钟才被更新一次,就需要处理 WM_TIMECHANGE 消息。

8.3.5  模拟时钟

        模拟时钟程序无需考虑国际化问题。但这方面的简化并不能抵消图形处理所增加的复杂度。要正确开发模拟时钟,需要一些三角函数的知识。

[cpp] view plain copy
  1. /*-------------------------------------------------------- 
  2.    CLOCK.C --  Analog Clock Program 
  3.                  (c) Charles Petzold, 1998 
  4.   --------------------------------------------------------*/  
  5. #include <windows.h>  
  6. #include <math.h>  
  7.   
  8. #define ID_TIMER    1  
  9. #define TWOPI       (2 * 3.14159)  
  10.   
  11. LRESULT CALLBACK WndProc (HWNDUINTWPARAMLPARAM) ;  
  12.   
  13. int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,  
  14.                     PSTR szCmdLine, int iCmdShow)  
  15. {  
  16.      static TCHAR szAppName[] = TEXT ("Clock") ;  
  17.      HWND         hwnd ;  
  18.      MSG          msg ;  
  19.      WNDCLASS     wndclass ;  
  20.   
  21.      wndclass.style         = CS_HREDRAW | CS_VREDRAW ;  
  22.      wndclass.lpfnWndProc   = WndProc ;  
  23.      wndclass.cbClsExtra    = 0 ;  
  24.      wndclass.cbWndExtra    = 0 ;  
  25.      wndclass.hInstance     = hInstance ;  
  26.      wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;  
  27.      wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;  
  28.      wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;  
  29.      wndclass.lpszMenuName  = NULL ;  
  30.      wndclass.lpszClassName = szAppName ;  
  31.   
  32.      if (!RegisterClass (&wndclass))  
  33.      {  
  34.           MessageBox (NULL, TEXT ("This program requires Windows NT!"),  
  35.                       szAppName, MB_ICONERROR) ;  
  36.           return 0 ;  
  37.      }  
  38.   
  39.      hwnd = CreateWindow (szAppName, TEXT ("Analog Clock"),  
  40.                           WS_OVERLAPPEDWINDOW,  
  41.                           CW_USEDEFAULT, CW_USEDEFAULT,  
  42.                           CW_USEDEFAULT, CW_USEDEFAULT,  
  43.                           NULL, NULL, hInstance, NULL) ;  
  44.   
  45.      ShowWindow (hwnd, iCmdShow) ;  
  46.      UpdateWindow (hwnd) ;  
  47.   
  48.      while (GetMessage (&msg, NULL, 0, 0))  
  49.      {  
  50.           TranslateMessage (&msg) ;  
  51.           DispatchMessage (&msg) ;  
  52.      }  
  53.      return msg.wParam ;  
  54. }  
  55.   
  56. void SetIsotropic (HDC hdc, int cxClient, int cyClient)  
  57. {  
  58.     SetMapMode(hdc, MM_ISOTROPIC);  
  59.     SetWindowExtEx(hdc, 1000, 1000, NULL);  
  60.     SetViewportExtEx(hdc, cxClient / 2, -cyClient / 2, NULL);  
  61.     SetViewportOrgEx(hdc, cxClient / 2, cyClient / 2, NULL);  
  62. }  
  63.   
  64. void RotatePoint (POINT pt[], int iNum, int iAngle)  
  65. {  
  66.     int     i;  
  67.     POINT   ptTemp;  
  68.   
  69.     for (i = 0; i < iNum; ++ i)  
  70.     {  
  71.         ptTemp.x = (int) (pt[i].x * cos (TWOPI * iAngle / 360) +  
  72.             pt[i].y * sin (TWOPI * iAngle / 360));  
  73.   
  74.         ptTemp.y = (int) (pt[i].y * cos (TWOPI * iAngle / 360) -  
  75.             pt[i].x * sin (TWOPI * iAngle / 360));  
  76.   
  77.         pt[i] = ptTemp;  
  78.     }  
  79. }  
  80.   
  81. void DrawClock (HDC hdc)  
  82. {  
  83.     int     iAngle;  
  84.     POINT   pt[3];  
  85.   
  86.     for (iAngle = 0; iAngle < 360; iAngle += 6)  
  87.     {  
  88.         pt[0].x = 0;  
  89.         pt[0].y = 900;  
  90.   
  91.         RotatePoint(pt, 1, iAngle);  
  92.   
  93.         pt[2].x = pt[2].y = iAngle % 5 ? 33 : 100;  
  94.   
  95.         pt[0].x -= pt[2].x / 2;  
  96.         pt[0].y -= pt[2].y / 2;  
  97.   
  98.         pt[1].x = pt[0].x + pt[2].x;  
  99.         pt[1].y = pt[0].y + pt[2].y;  
  100.   
  101.         SelectObject(hdc, GetStockObject(BLACK_BRUSH));  
  102.   
  103.         Ellipse (hdc, pt[0].x, pt[0].y, pt[1].x, pt[1].y);  
  104.     }  
  105. }  
  106.   
  107. void DrawHands (HDC hdc, SYSTEMTIME * pst, BOOL fChange)  
  108. {  
  109.     static POINT pt[3][5] = { 0, -150, 100, 0, 0, 600, -100, 0, 0, -150,  
  110.                               0, -200,  50, 0, 0, 800,  -50, 0, 0, -200,  
  111.                               0,    0,   0, 0, 0,   0,    0, 0, 0,  800 };  
  112.   
  113.     int     i, iAngle[3];  
  114.     POINT   ptTemp[3][5];  
  115.   
  116.     iAngle[0] = (pst->wHour * 30) % 360 + pst->wMinute / 2;  
  117.     iAngle[1] = pst->wMinute * 6;  
  118.     iAngle[2] = pst->wSecond * 6;  
  119.   
  120.     memcpy (ptTemp, pt, sizeof(pt));  
  121.   
  122.     for (i = fChange ? 0 : 2; i < 3; ++ i)  
  123.     {  
  124.         RotatePoint(ptTemp[i], 5, iAngle[i]);  
  125.   
  126.         Polyline(hdc, ptTemp[i], 5);  
  127.   
  128.     }  
  129. }  
  130.   
  131. LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)  
  132. {  
  133.      static int         cxClient, cyClient;  
  134.      static SYSTEMTIME  stPrevious;  
  135.      BOOL               fChange;  
  136.      HDC                hdc;  
  137.      PAINTSTRUCT        ps;  
  138.      SYSTEMTIME         st;  
  139.   
  140.      switch (message)  
  141.      {  
  142.      case WM_CREATE:  
  143.           SetTimer(hwnd, ID_TIMER, 1000, NULL);  
  144.           GetLocalTime(&st);  
  145.           stPrevious = st;  
  146.           return 0;  
  147.   
  148.      case WM_SIZE:  
  149.           cxClient = LOWORD(lParam);  
  150.           cyClient = HIWORD(lParam);  
  151.           return 0;  
  152.   
  153.      case WM_TIMER:  
  154.           GetLocalTime(&st);  
  155.           fChange = st.wHour != stPrevious.wHour ||  
  156.                     st.wMinute != stPrevious.wMinute;  
  157.   
  158.           hdc = GetDC (hwnd);  
  159.           SetIsotropic(hdc, cxClient, cyClient);  
  160.   
  161.           SelectObject(hdc, GetStockObject(WHITE_PEN));  
  162.           DrawHands(hdc, &stPrevious, fChange);  
  163.   
  164.           SelectObject(hdc, GetStockObject(BLACK_PEN));  
  165.           DrawHands(hdc, &st, TRUE);  
  166.   
  167.           ReleaseDC(hwnd, hdc);  
  168.   
  169.           stPrevious = st;  
  170.           return 0;  
  171.   
  172.      case WM_PAINT:  
  173.           hdc = BeginPaint(hwnd, &ps);  
  174.   
  175.           SetIsotropic(hdc, cxClient, cyClient);  
  176.           DrawClock(hdc);  
  177.           DrawHands(hdc, &stPrevious, TRUE);  
  178.   
  179.           EndPaint(hwnd, &ps);  
  180.           return 0;  
  181.   
  182.      case WM_DESTROY:  
  183.           KillTimer(hwnd, ID_TIMER);  
  184.           PostQuitMessage (0) ;  
  185.           return 0 ;  
  186.      }  
  187.      return DefWindowProc (hwnd, message, wParam, lParam) ;  
  188. }  

图 8-6  CLOCK 程序的输出

        各向同性映射模式再次成为这样的应用程序的最佳选择。在 CLOCK.C 中的 SetIsotropic 函数设置了该模式。调用 SetMapMode 函数之后,窗口大小被设定为 1000,视口范围被设定为宽度是客户区宽度的一半,高度是客户区高度一半的负数。视口原点设置为客户区的中心点。如第 5 章所述,这样就创建了一个以客户区中心为原点的笛卡尔坐标系统,并向各方向(x, y 方向)各延展 1000 单位。

        RotatePoint 函数是三角形函数发挥作用的地方。该函数的三个输入参数是:含有一个或多个点的数组、数组中点的数目和每个点的旋转角度。该函数把这些点绕着原点顺时针旋转。举个例子,如果输入的点是(0, 100),它对应于 12 点钟的位置,如果输入的旋转角度为 90 度,那么这个点就被转换成(100, 0),相当于 3 点钟的位置。它使用的公式如下:

我们很快就可以看见,RotatePoint 函数对绘制时钟的刻度和指针很有用。

        DrawClock 函数画出了钟面上从最上面(12 点钟)开始的 60 个刻度点。每个点距离原点 960 个单位,所以第一个点位于(0, 900),然后下面的每一点都顺指针转 6 度。其中有 12 个刻度点的直径为 100 个单位,而其余点的直径为 33 个单位。这些点是用 Ellipse 函数画出来的。

        DrawHands 函数画出了时针、分针和秒针指针轮廓(当指针垂直往上指的时候)的坐标被存储在一个 POINT 数据结构的数组中,根据当前的时间,这些坐标会被 RotatePoint 函数旋转,然后被 Windows 的 Polyline 函数显示。注意,仅仅在 DrawHands 的参数 bChange 为 true 的情况下,时针、分针和秒针才被显示。当程序更新这些指针的时候,大多数情况下,时针和分针都不需要被重新绘制。

        现在让我们把注意力转到窗口过程。在 WM_CREATE 消息中,窗口过程获取了当前时间同时也吧它存储在 变量 dtPrevious 中。这个变量后面会用来确定小时或分钟在本次的更新中是否有改变。

        时钟第一次被绘制是再处理第一个 WM_PAINT 消息的时候。基本就是调用 SetIsotropic、DrawClock 和 DrawHands 函数,调用 DrawHands 时 bChange 参数要设定为 TRUE。

        在处理 WM_TIMER 消息的时候,WndProc 先是获得新的时间来确定时针和分针是否需要重画。如果需要,就用白色画笔在之前的时间未知上把所有指针画一次,相当于抹掉了它们;如果不需要,那么只有秒针被抹掉。然后,用黑色画笔把所有指针绘制一次。

0 0