Method One

This method, the easiest, causes Windows to send WM_TIMER messages to the normal window procedure of the application. The SetTimer call looks like this:

SetTimer (hwnd, 1, wMsecInterval, NULL) ;

The first parameter is a handle to the window whose window procedure will receive the WM_TIMER messages. The second parameter is the timer ID, which should be a nonzero number. I have arbitrarily set it to 1 in this example. The third parameter is a WORD (16-bit unsigned integer) that specifies an interval in milliseconds. The largest value (65535) will deliver a WM_TIMER message about once a minute.

You can stop the WM_TIMER messages at any time (even while processing a WM_TIMER message) by calling:

KillTimer (hwnd, 1) ;

The second parameter is the same timer ID used in the SetTimer call. You should kill any active timers in response to a WM_DESTROY message before your program terminates.

When your window procedure receives a WM_TIMER message, wParam is equal to the timer ID (which in the above case is simply 1), and lParam is 0. If you need to set more than one timer, use a different timer ID for each timer. The value of wParam will differentiate the WM_TIMER messages passed to your window procedure. To make your program more readable, you may want to use #define statements for the different timer IDs:

#define TIMER_SEC 1

#define TIMER_MIN 2

You can then set the two timers with two SetTimer calls:

SetTimer (hwnd, TIMER_SEC, 1000, NULL) ;

SetTimer (hwnd, TIMER_MIN, 60000, NULL) ;

The WM_TIMER logic might look something like this:

case WM_TIMER :

switch (wParam)

{

case TIMER_SEC :

[once-per-second processing]

break ;

case TIMER_MIN :

[once-per-minute processing]

break ;

}

return 0 ;

If you want to set an existing timer to a different elapsed time, kill the timer and call SetTimer again. This code assumes that the timer ID is 1:

KillTimer (hwnd, 1) ;

SetTimer (hwnd, 1, wMsecInterval, NULL) ;

The wMsecInterval parameter is the new elapsed time in milliseconds. The Windows CLOCK application uses this method to change the timer from 1000 msec to 60,000 msec when it becomes an icon. As an icon, CLOCK needs to update the clock every minute rather than every second. When it is expanded from an icon to a window, CLOCK changes the timer back to 1000 msec.

What to do if no timer is available

Windows allows only 16 timers to be active at any time. If no timer is available, SetTimer returns NULL. Your program might be able to function reasonably well without the timer, but if you need the timer (as CLOCK certainly does), the application has no choice but to terminate if it can't get one. If you call SetTimer in WinMain, you can terminate the program simply by returning FALSE from WinMain.

Let's assume you want a 1000-msec timer. Following the CreateWindow call but before the message loop, you might have a statement like this:

if (!SetTimer (hwnd, 1, 1000, NULL))

return FALSE ;

This is the unfriendly way to terminate. The user is left wondering why the application will not load. (Surely the 16 clocks sitting down in the icon area have nothing to do with it!) It's much friendlier—and fairly easy—to use a Windows message box for displaying a message. A complete discussion of message boxes awaits you in Chapter 10, but this will get you started.

A message box is a popup window that always appears in the center of the display. Message boxes have a caption bar but no size box. The caption bar usually contains the name of the application. The message box encloses a message and one, two, or three buttons (some combination of OK, Retry, Cancel, Yes, No, and others). The message box can also contain a predefined icon: a lowercase ”i“ (which stands for ”information“), an exclamation point, a question mark, or a stop sign. You have probably seen plenty of message boxes when working with Windows.

This code creates an informatory message box that you can use when SetTimer fails to allocate a timer:

if (!SetTimer (hwnd, 1, 1000, NULL))

{

MessageBox (hwnd,

"Too many clocks or timers!",

"Program Name",

MB_ICONEXCLAMATION | MB_OK) ;

return FALSE ;

}

The message box is shown in Figure 5-1. When the user presses Enter or clicks the OK button, WinMain terminates by returning FALSE.

By default, message boxes are ”application modal“ windows. This means that a user must respond to the message box before the application will continue. However, the user can switch to other applications by pressing Alt-Tab or Alt-Esc or by clicking the mouse in the window of another program.

Why not give the user the opportunity to close one of those 16 minimized clocks at the bottom of the display and successfully load your application? That's what this code does:

while (!SetTimer (hwnd, 1, 1000, NULL))

if (IDCANCEL == MessageBox (hwnd,

"Too many clocks or timers!",

"Program Name",

MB_ICONEXCLAMATION | MB_RETRYCANCEL))

return FALSE ;

This message box, shown in Figure 5-2, has two buttons, labeled Retry and Cancel. If the user selects Cancel, the MessageBox function returns a value equal to IDCANCEL, and the program terminates. If the user selects Retry, SetTimer is called again.

A sample program

Figure 5-3 shows a sample program that uses the timer. This program, called BEEPER1, sets a timer for 1-second intervals. When it receives a WM_TIMER message, it alternates coloring the client area blue and red, and it beeps by calling the function MessageBeep. (Although MessageBeep is documented as a companion to MessageBox, it's really an all-purpose beep function. The WORD parameter to MessageBeep can be any value.) BEEPER1 sets the timer in the WinMain function and processes the WM_TIMER messages in the WndProc window procedure. During the WM_TIMER message, BEEPER1 calls MessageBeep, inverts the value of bFlipFlop and invalidates the window to generate a WM_PAINT message. During the WM_PAINT message, BEEPER1 obtains a RECT structure for the size of the window by calling GetClientRect and colors the window by calling FillRect.

BEEPER1.MAK

#-----------------------

# BEEPER1.MAK make file

#-----------------------

beeper1.exe : beeper1.obj beeper1.def

link beeper1, /align:16, NUL, /nod slibcew libw, beeper1

rc beeper1.exe

beeper1.obj : beeper1.c

cl -c -Gsw -Ow -W2 -Zp beeper1.c

BEEPER1.C

/*-----------------------------------------

BEEPER1.C -- Timer Demo Program No. 1

(c) Charles Petzold, 1990

-----------------------------------------*/

#include <windows.h>

#define ID_TIMER 1

long FAR PASCAL WndProc (HWND, WORD, WORD, LONG) ;

int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance,

LPSTR lpszCmdLine, int nCmdShow)

{

static char szAppName[] = "Beeper1" ;

HWND hwnd ;

MSG msg ;

WNDCLASS wndclass ;

if (!hPrevInstance)

{

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 = GetStockObject (WHITE_BRUSH) ;

wndclass.lpszMenuName = NULL ;

wndclass.lpszClassName = szAppName ;

RegisterClass (&wndclass) ;

}

hwnd = CreateWindow (szAppName, "Beeper1 Timer Demo",

WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT, CW_USEDEFAULT,

CW_USEDEFAULT, CW_USEDEFAULT,

NULL, NULL, hInstance, NULL) ;

while (!SetTimer (hwnd, ID_TIMER, 1000, NULL))

if (IDCANCEL == MessageBox (hwnd,

"Too many clocks or timers!", szAppName,

MB_ICONEXCLAMATION | MB_RETRYCANCEL))

return FALSE ;

ShowWindow (hwnd, nCmdShow) ;

UpdateWindow (hwnd) ;

while (GetMessage (&msg, NULL, 0, 0))

{

TranslateMessage (&msg) ;

DispatchMessage (&msg) ;

}

return msg.wParam ;

}

long FAR PASCAL WndProc (HWND hwnd, WORD message, WORD wParam, LONG lParam)

{

static BOOL fFlipFlop = FALSE ;

HBRUSH hBrush ;

HDC hdc ;

PAINTSTRUCT ps ;

RECT rc ;

switch (message)

{

case WM_TIMER :

MessageBeep (0) ;

fFlipFlop = !fFlipFlop ;

InvalidateRect (hwnd, NULL, FALSE) ;

return 0 ;

case WM_PAINT :

hdc = BeginPaint (hwnd, &ps) ;

GetClientRect (hwnd, &rc) ;

hBrush = CreateSolidBrush (fFlipFlop ? RGB(255,0,0) :

RGB(0,0,255)) ;

FillRect (hdc, &rc, hBrush) ;

EndPaint (hwnd, &ps) ;

DeleteObject (hBrush) ;

return 0 ;

case WM_DESTROY :

KillTimer (hwnd, ID_TIMER) ;

PostQuitMessage (0) ;

return 0 ;

}

return DefWindowProc (hwnd, message, wParam, lParam) ;

}

BEEPER1.DEF

;------------------------------------

; BEEPER1.DEF module definition file

;------------------------------------

NAME BEEPER1

DESCRIPTION 'Timer Demo Program No. 1 (c) Charles Petzold, 1990'

EXETYPE WINDOWS

STUB 'WINSTUB.EXE'

CODE PRELOAD MOVEABLE DISCARDABLE

DATA PRELOAD MOVEABLE MULTIPLE

HEAPSIZE 1024

STACKSIZE 8192

EXPORTS WndProc

Because BEEPER1 audibly indicates every WM_TIMER message it receives, you can get a good idea of the erratic nature of WM_TIMER messages by loading BEEPER1 and performing some other actions within Windows. For instance, try moving or resizing a window. This stops all messages, and BEEPER1 stops beeping. When you complete the move or resize, you'll note that BEEPER1 doesn't get all the WM_TIMER messages it has missed, although the first two messages may be less than a second apart.

This is our first encounter with a Windows program that uses color, so a brief look at how Windows handles color is worthwhile here.

Windows' use of color

Windows uses an unsigned long (32-bit) integer value to represent a color. The lowest three bytes specify red, green, and blue values that range from 0 through 255, as illustrated by Figure 5-4. This results in a potential 224 (or about 16 million) colors.

This unsigned long is often referred to as an ”RGB color.“ The WINDOWS.H header file provides several macros for working with RGB color values. The RGB macro in WINDOWS.H takes three arguments representing red, green, and blue values and combines them into an unsigned long:

#define RGB(r,g,b) ((DWORD)(((BYTE)(r) | \

((WORD)(g) << 8)) | \

(((DWORD)(BYTE)(b)) << 16)))

Thus, the value:

RGB (255, 0, 255)

is really 0x00FF00FF, an RGB color value for magenta. When all three arguments are set to 0, the color is black; when the arguments are set to 255, the color is white. The GetRValue, GetGValue, and GetBValue macros extract the unsigned character primary-color values from an unsigned long RGB color value. These macros are sometimes handy when you're using Windows functions that return RGB color values to your program.

The most common video display adapters used for Windows are the Enhanced Graphics Adapter (EGA) and Video Graphics Array (VGA). In the display resolutions that Windows uses, both these adapters can display 16 different colors. (Some ”Super VGA“ boards can display 256 different colors under Windows.) Windows can display additional colors by ”dithering,“ which is creating a pixel pattern that combines pixels of different pure colors.

Not all unique combinations of red, green, and blue bytes produce different dithering patterns. For instance, on a color EGA or VGA, a red, green, or blue value must generally be incremented by 4 to produce a different dithering pattern. So for these adapters, you have 218 (or 262,144) dithered colors.

BEEPER1 uses the FillRect function to color its client area. The first parameter to FillRect is the device context handle, the second is a pointer to the RECT structure, and the third is a handle to a ”brush.“ A brush is a graphics object that Windows uses to fill an area. Brushes can be solid colors or composed of various hatchmarks or patterns.

BEEPER1 creates a brush of a solid color by calling CreateSolidBrush. The only parameter is an RGB color value. Depending on the value of fFlipFlop, BEEPER sets this parameter to RGB(255,0,0), which is red, or RGB(0,0,255), which is blue.

A brush is a graphics object. If you create a brush, you must also delete it when you're finished. After calling FillRect, BEEPER1 deletes the brush by calling DeleteObject.