PROGRAMS THAT DRAW FOREVER

A fun program in any graphics system is one that runs ”forever,“ simply drawing a hypnotic series of rectangles with random sizes and colors. You can create such a program in Windows, but it's not quite as easy as it first seems. By now you should know that this is definitely not the way to do it:

case WM_PAINT : // Very, very bad code !!!

hdc = BeginPaint (hwnd, &ps) ;

while (TRUE)

{

xLeft = rand () % xClient ;

xRight = rand () % xClient ;

yTop = rand () % yClient ;

yBottom = rand () % yClient ;

nRed = rand () & 255 ;

nGreen = rand () & 255 ;

nBlue = rand () & 255 ;

hdc = GetDC (hwnd) ;

hBrush = CreateSolidBrush (RGB (nRed, nGreen, nBlue)) ;

SelectObject (hdc, hBrush) ;

Rectangle (hdc, min (xLeft, xRight), min (yTop, yBottom),

max (xLeft, xRight), max (yTop, yBottom)) ;

}

EndPaint (hwnd, &ps) ;

return 0 ;

Sure, this will work, but nothing else will. Because Windows is a nonpreemptive multitasking environment, a program can't enter into an infinite loop like this. This loop will stop all other processing in the system.

One acceptable alternative is setting a Windows timer to send WM_TIMER messages to your window function. For each WM_TIMER message, you obtain a device context with GetDC, draw a random rectangle, and then release the device context with ReleaseDC. But that takes some of the fun out of the program, because the program can't draw the random rectangles as quickly as possible. It must wait for each WM_TIMER message.

There must be plenty of ”dead time“ in Windows, time during which all the message queues are empty and Windows is just sitting in a little loop waiting for keyboard or mouse input. Couldn't we somehow get control during that dead time and draw the rectangles, relinquishing control only when a message is added to a program's message queue? That's one of the purposes of the PeekMessage function. Here's one example of a PeekMessage call:

PeekMessage (&msg, NULL, 0, 0, PM_REMOVE) ;

The first four parameters (a pointer to a MSG structure, a window handle, and two values indicating a message range) are identical to those of GetMessage. Setting the second, third, and fourth parameters to NULL or 0 indicates that we want PeekMessage to return all messages for all windows in the program. Like GetMessage, PeekMessage effectively yields control to other programs if messages are waiting in the other programs' message queues. Like GetMessage, PeekMessage returns messages only for window functions in the program that makes the function call.

The last parameter to PeekMessage is set to PM_REMOVE if the message is to be removed from the message queue. You can set it to PM_NOREMOVE if the message isn't to be removed. This is why PeekMessage is a ”peek“ rather than a ”get“—it allows a program to check the next message in the program's queue without actually removing it. GetMessage doesn't return control to a program unless it retrieves a message from the program's message queue. But PeekMessage will return under two conditions:

When there's a message in the program's message queue, in which case the return value of PeekMessage is TRUE (nonzero).

When there are no messages in the message queue of any program running under Windows, in which case the return value of PeekMessage is FALSE (0).

A message loop that uses PeekMessage rather than GetMessage essentially says to Windows, ”Let other programs run for a little while, but once they've emptied their message queues, return control to me—I'm not finished with my work.“ If two or more programs are running that use a PeekMessage loop to retrieve messages, Windows uses a round-robin approach, returning control sequentially to each program waiting with a PeekMessage call.

This allows us to replace the normal message loop, which looks like this:

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

{

TranslateMessage (&msg) ;

DispatchMessage (&msg) ;

}

return msg.wParam ;

with an alternative message loop like this:

while (TRUE)

{

if (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))

{

if (msg.message == WM_QUIT)

break ;

TranslateMessage (&msg) ;

DispatchMessage (&msg) ;

}

else

{

[other program lines to do some work]

}

}

return msg.wParam ;

Notice that the WM_QUIT message is explicitly checked. You don't have to do this in a normal message loop, because the return value of GetMessage is 0 when it retrieves a WM_QUIT message. But PeekMessage uses its return value to indicate whether a message was retrieved, so the check of WM_QUIT is required.

If the return value of PeekMessage is TRUE, the message is processed normally. If the value is FALSE, the program can do some work (such as displaying yet another random rectangle) before returning control to Windows.

(Although the Windows documentation notes that you can't use PeekMessage to remove WM_PAINT messages from the message queue, this isn't really a problem. After all, GetMessage doesn't remove WM_PAINT messages from the queue either. The only way to remove a WM_PAINT message from the queue is to validate the invalid regions of the window's client area, which you can do with ValidateRect, ValidateRgn, or a BeginPaint and EndPaint pair. If you process a WM_PAINT message normally after retrieving it from the queue with PeekMessage, you'll have no problems. What you can't do is use code like this to empty your message queue of all messages:

while (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE)) ;

This statement removes and discards all messages from your message queue except WM_PAINT. If a WM_PAINT message is in the queue, you'll be stuck inside the while loop forever.)

The Windows TERMINAL program uses a PeekMessage loop when it receives input from a communications line. This allows TERMINAL to check ”continuously“ for incoming data. The PRINT MANAGER program uses this technique to print on a printer or plotter and—as you'll see in Chapter 15—a Windows program that prints also includes a function with a PeekMessage loop. Armed with the PeekMessage function, we can now write a program that relentlessly displays random rectangles. The program, called RANDRECT, is shown in Figure 12-21.

RANDRECT.MAK

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

# RANDRECT.MAK make file

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

randrect.exe : randrect.obj randrect.def

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

rc randrect.exe

randrect.obj : randrect.c

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

RANDRECT.C

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

RANDRECT.C -- Displays Random Rectangles

(c) Charles Petzold, 1990

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

#include <windows.h>

#include <stdlib.h>

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

void DrawRectangle (HWND) ;

short cxClient, cyClient ;

int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance,

LPSTR lpszCmdLine, int nCmdShow)

{

static char szAppName[] = "RandRect" ;

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 = NULL ;

wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;

wndclass.hbrBackground = GetStockObject (WHITE_BRUSH) ;

wndclass.lpszMenuName = NULL ;

wndclass.lpszClassName = szAppName ;

!RegisterClass (&wndclass) ;

}

hwnd = CreateWindow (szAppName, "Random Rectangles",

WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT, CW_USEDEFAULT,

CW_USEDEFAULT, CW_USEDEFAULT,

NULL, NULL, hInstance, NULL) ;

ShowWindow (hwnd, nCmdShow) ;

UpdateWindow (hwnd) ;

while (TRUE)

{

if (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))

{

if (msg.message == WM_QUIT)

break ;

TranslateMessage (&msg) ;

DispatchMessage (&msg) ;

}

else

DrawRectangle (hwnd) ;

}

return msg.wParam ;

}

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

{

switch (message)

{

case WM_SIZE :

cxClient = LOWORD (lParam) ;

cyClient = HIWORD (lParam) ;

return 0 ;

case WM_DESTROY :

PostQuitMessage (0) ;

return 0 ;

}

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

}

void DrawRectangle (HWND hwnd)

{

HBRUSH hBrush ;

HDC hdc ;

short xLeft, xRight, yTop, yBottom, nRed, nGreen, nBlue ;

xLeft = rand () % cxClient ;

xRight = rand () % cxClient ;

yTop = rand () % cyClient ;

yBottom = rand () % cyClient ;

nRed = rand () & 255 ;

nGreen = rand () & 255 ;

nBlue = rand () & 255 ;

hdc = GetDC (hwnd) ;

hBrush = CreateSolidBrush (RGB (nRed, nGreen, nBlue)) ;

SelectObject (hdc, hBrush) ;

Rectangle (hdc, min (xLeft, xRight), min (yTop, yBottom),

max (xLeft, xRight), max (yTop, yBottom)) ;

ReleaseDC (hwnd, hdc) ;

DeleteObject (hBrush) ;

}

RANDRECT.DEF

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

; RANDRECT.DEF module definition file

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

NAME RANDRECT

DESCRIPTION 'Random Rectangle Drawing Program (c) Charles Petzold, 1990'

EXETYPE WINDOWS

STUB 'WINSTUB.EXE'

CODE PRELOAD MOVEABLE DISCARDABLE

DATA PRELOAD MOVEABLE MULTIPLE

HEAPSIZE 1024

STACKSIZE 8192

EXPORTS WndProc