📄 8. 定时器.txt
字号:
BOOL fChange ;
HDC hdc ;
PAINTSTRUCT ps ;
SYSTEMTIME st ;
switch (message)
{
case WM_CREATE :
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) ;
}
CLOCK屏幕显示如图8-2。
图8-2 CLOCK的屏幕显示
等方向性(isotropic)映像对于这样的应用来说是理想的,CLOCK.C中的SetIsotropic函数负责设定此模式。在呼叫SetMapMode之后,SetIsotropic将窗口范围设定为1000,并将视端口范围设定为显示区域的一半宽度和显示区域的负的一半高度。视端口原点被设定为显示区域的中心。我在第五章中讨论过,这将建立一个笛卡儿坐标系,其点(0,0)位于显示区域的中心,在所有方向上的范围都是1000。
RotatePoint函数是用到三角函数的地方,此函式的三个参数分别是一个或者多个点的数组、数组中点的个数以及以度为单位的旋转角度。函式以原点为中心按顺时针方向(这对一个时钟正合适)旋转这些点。例如,如果传给函式的点是(0,100)-即12:00的位置-而角度为90度,那么该点将被变换为(100,0)-即3:00。它使用下列公式来做到这一点:
x' = x * cos (a) + y * sin (a)
y' = y * cos (a) - x * sin (a)
RotatePoint函数在绘制时钟表面的点和表针时都是有用的,我们将马上看到这一点。
DrawClock函数绘制60个时钟表面的点,从顶部(12:00)开始,其中每个点离原点900单位,因此第一个点位于(0,900),此后的每个点按顺时针依次增加6度。这些点中的l2个直径为100个单位;其余的为33个单位。使用Ellipse函数来画点。
DrawHands函数绘制时钟的时针、分针和秒针。定义表针轮廓(当它们垂直向上时的形状)的坐标存放在一个POINT结构的数组中。根据时间,这些坐标使用RotatePoint函数进行旋转,并用Windows的Polyline函数进行显示。注意时针和分针只有当传递给DrawHands的bChange参数为TRUE时才被显示。当程序更新时钟的表针时,大多数情况下时针和分针不需要重画。
现在让我们将注意力转到窗口消息处理程序。在WM_CREATE消息处理期间,窗口消息处理程序取得目前时间并将它存放在名为dtPrevious的变量中,这个变量将在以后被用于确定时针或者分针从上次更新以来是否改变过。
第一次绘制时钟是在第一个WM_PAINT消息处理期间,这只不过是依次呼叫SetIsotropic、DrawClock和DrawHands,后者的bChange参数被设定为TRUE。
在WM_TIMER消息处理期间,WndProc首先取得新的时间并确定是否需要重新绘制时针和分针。如果需要,则使用一个白色画笔和上一次时间绘制所有的表针,从而有效地擦除它们。否则,只对秒针使用白色画笔进行擦除,然后,再使用一个黑色画笔绘制所有的表针。
以定时器进行状态报告
本章的最后一个程序是我在第五章提到过的。它是一个使用GetPixel函数的好例子。
WHATCLR (见程序8-5)显示了鼠标光标下目前图素的RGB颜色。
程序8-5 WHATCLR
WHATCLR.C
/*--------------------------------------------------------------------------
WHATCLR.C -- Displays Color Under Cursor
(c) Charles Petzold, 1998
---------------------------------------------------------------------------*/
#include <windows.h>
#define ID_TIMER 1
void FindWindowSize (int *, int *) ;
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT ("WhatClr") ;
HWND hwnd ;
int cxWindow, cyWindow ;
MSG msg ;
WNDCLASS wndclass ;
wndclass.style = CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szAppName ;
if (!RegisterClass (&wndclass))
{
MessageBox ( NULL, TEXT ("This program requires Windows NT!"),
szAppName, MB_ICONERROR) ;
return 0 ;
}
FindWindowSize (&cxWindow, &cyWindow) ;
hwnd = CreateWindow (szAppName, TEXT ("What Color"),
WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_BORDER,
CW_USEDEFAULT, CW_USEDEFAULT,
cxWindow, cyWindow,
NULL, NULL, hInstance, NULL) ;
ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;
while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
return msg.wParam ;
}
void FindWindowSize (int * pcxWindow, int * pcyWindow)
{
HDC hdcScreen ;
TEXTMETRIC tm ;
hdcScreen = CreateIC (TEXT ("DISPLAY"), NULL, NULL, NULL) ;
GetTextMetrics (hdcScreen, &tm) ;
DeleteDC (hdcScreen) ;
* pcxWindow = 2 * GetSystemMetrics (SM_CXBORDER) +
12 * tm.tmAveCharWidth ;
* pcyWindow = 2 * GetSystemMetrics (SM_CYBORDER) +
GetSystemMetrics (SM_CYCAPTION) +
2 * tm.tmHeight ;
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam)
{
static COLORREF cr, crLast ;
static HDC hdcScreen ;
HDC hdc ;
PAINTSTRUCT ps ;
POINT pt ;
RECT rc ;
TCHAR szBuffer [16] ;
switch (message)
{
case WM_CREATE:
hdcScreen = CreateDC (TEXT ("DISPLAY"), NULL, NULL, NULL) ;
SetTimer (hwnd, ID_TIMER, 100, NULL) ;
return 0 ;
case WM_TIMER:
GetCursorPos (&pt) ;
cr = GetPixel (hdcScreen, pt.x, pt.y) ;
SetPixel (hdcScreen, pt.x, pt.y, 0) ;
if (cr != crLast)
{
crLast = cr ;
InvalidateRect (hwnd, NULL, FALSE) ;
}
return 0 ;
case WM_PAINT:
hdc = BeginPaint (hwnd, &ps) ;
GetClientRect (hwnd, &rc) ;
wsprintf (szBuffer, TEXT (" %02X %02X %02X "),
GetRValue (cr), GetGValue (cr), GetBValue (cr)) ;
DrawText (hdc, szBuffer, -1, &rc,
DT_SINGLELINE | DT_CENTER | DT_VCENTER) ;
EndPaint (hwnd, &ps) ;
return 0 ;
case WM_DESTROY:
DeleteDC (hdcScreen) ;
KillTimer (hwnd, ID_TIMER) ;
PostQuitMessage (0) ;
return 0 ;
}
return DefWindowProc (hwnd, message, wParam, lParam) ;
}
WHATCLR在WinMain中做了一点与以往不同的事。因为WHATCLR的窗口只需要显示十六进制RGB值那么大,所以它在CreateWindow函数中使用WS_BORDER窗口样式建立了一个不能改变大小的窗口。要计算窗口的大小,WHATCLR通过先呼叫CreateIC再呼叫GetSystemMetrics以取得用于视讯显示的设备内容信息。计算好的窗口宽度和高度值被传递给CreateWindow。
WHATCLR的窗口消息处理程序在处理WM_CREATE消息处理期间,呼叫CreateDC建立了用于整个视讯显示的设备内容。这个设备内容在程序的生命周期内都有效。在处理WM_TIMER消息处理期间,程序取得目前鼠标光标位置的图素。在处理WM_PAINT消息处理期间显示RGB颜色。
您可能想知道,从CreateDC函数中取得的设备内容句柄是否能让您在屏幕的任意位置显示一些东西,而不光只是取得图素颜色。答案是可以的,一般而言,让一个应用程序在另一个程控的画面区域上画图是不好的,但在某些特殊情况下,这可能会非常有用。
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -