Chapter 4  The Mouse
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ

The mouse is a pointing device with one or more buttons. Although a mouse is
considered an important part of Windows' user interface, it is an optional
accessory. You can install Windows without a mouse, and you can control most
Windows programs entirely from the keyboard. Often, the most difficult
aspect of using the mouse in your program is adding a keyboard interface to
duplicate the mouse functions.

MOUSE BASICS

Windows can support a one-button, two-button, or three-button mouse or use a
joystick or light pen to mimic a one-button mouse. The support of a second
and third mouse button is rarely exploited, however. Unless you know your
Windows program will run only on machines equipped with a two-button or
three-button mouse, you must write programs for the lowest common
denominator and use only a single button.

You can determine if a mouse is present by using the GetSystemMetrics
function:

fMouse = GetSystemMetrics (SM_MOUSEPRESENT) ;

The value of fMouse will be TRUE (nonzero) if a mouse is installed. No
method is documented for determining the number of buttons on the installed
mouse.

Some Quick Definitions

When the Windows user moves the mouse, Windows moves a small bit-mapped
picture on the display called the "mouse cursor." The mouse cursor has a
single-pixel "hot spot" that points to a precise location on the display.

The display driver contains several predefined mouse cursors that programs
may use. The most common is the slanted arrow called IDC_ARROW in WINDOWS.H.
The hot spot is the tip of the arrow. The IDC_CROSS cursor (used in the
BLOWUP1 program shown in this chapter) has a hot spot in the center of a
cross-hair pattern. The IDC_WAIT cursor is an hourglass generally used by
programs to indicate they are busy. Programmers can also design their own
cursors (as we'll do in Chapter 8). The default cursor for a particular
window is specified when defining the window class structure, for instance:

wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;

The following terms describe the actions you take with mouse buttons:

  þ   Clicking--Pressing and releasing a mouse button

  þ   Double-clicking--Pressing and releasing a mouse button twice in quick
      succession

  þ   Dragging--Moving the mouse while holding down a button

On a three-button mouse, the buttons are called the left button, middle
button, and right button. Mouse-related identifiers defined in WINDOWS.H use
the abbreviations LBUTTON, MBUTTON, and RBUTTON. A two-button mouse has only
a left button and a right button. The single button on a one-button mouse is
a left button.



CLIENT-AREA MOUSE MESSAGES

In the previous chapter you saw how Windows sends keyboard messages only to
the window with the input focus. Mouse messages are different: A window
procedure receives mouse messages whenever the mouse passes over the window
or is clicked within the window, even if the window is not active or does
not have the input focus.

Windows defines 21 messages for the mouse. However, 11 of these messages do
not relate to the client area (hereinafter, "nonclient-area" messages), and
Windows programs usually ignore them. Of the 10 "client-area" mouse
messages, 6 pertain to the right and middle buttons. Windows programs
usually ignore these messages also.

When the mouse is moved over the client area of a window, the window
procedure receives the message WM_MOUSEMOVE. When a mouse button is pressed
or released within the client area of a window, the window procedure
receives these messages:

Button  Pressed         Released      Pressed (2d Click)
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Left    WM_LBUTTONDOWN  WM_LBUTTONUP  WM_LBUTTONDBLCLK
Middle  WM_MBUTTONDOWN  WM_MBUTTONUP  WM_MBUTTONDBLCLK
Right   WM_RBUTTONDOWN  WM_RBUTTONUP  WM_RBUTTONDBLCLK

Your window procedure receives "MBUTTON" messages only for a three-button
mouse and "RBUTTON" messages only for a two-button or three-button mouse.
The window procedure receives "DBLCLK" (double-click) messages only if the
window class has been defined to receive them (as described below).

For all these messages, the value of lParam contains the position of the
mouse. The low word is the x-coordinate, and the high word is the
y-coordinate relative to the upper left corner of the client area of the
window. You can extract the x-coordinate and y-coordinate from lParam using
the LOWORD and HIWORD macros defined in WINDOWS.H. The value of wParam
indicates the state of the mouse buttons and the Shift and Ctrl keys. You
can test wParam using the bit masks defined in WINDOWS.H. The MK prefix
stands for "mouse key."

ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
MK_LBUTTON               Left button is down
MK_MBUTTON               Middle button is down
MK_RBUTTON               Right button is down
MK_SHIFT                 Shift key is down
MK_CONTROL               Ctrl key is down

As you move the mouse over the client area of a window, Windows does not
generate a WM_MOUSEMOVE message for every possible pixel position of the
mouse. The number of WM_MOUSEMOVE messages your program receives depends on
the mouse hardware and on the speed at which your window procedure can
process the mouse movement messages. You'll get a good idea of the rate of
WM_MOUSEMOVE messages when you experiment with the CONNECT program described
below.

If you click the left mouse button in the client area of an inactive window,
Windows changes the active window to the window that is being clicked and
then passes the WM_LBUTTONDOWN message to the window procedure. When your
window procedure gets a WM_LBUTTONDOWN message, your program can safely
assume the window is active. However, your window procedure can receive a
WM_LBUTTONUP message without first receiving a WM_LBUTTONDOWN message. This
can happen if the mouse button is pressed in one window, moved to your
window, and released. Similarly, the window procedure can receive a
WM_LBUTTONDOWN without a corresponding WM_LBUTTONUP message if the mouse
button is released while positioned over another window.

There are two exceptions to these rules:

  þ   A window procedure can "capture the mouse" and continue to receive
      mouse messages even when the mouse is outside the window's client
      area. You'll learn how to capture the mouse later in this chapter.

  þ   If a system-modal message box or a system-modal dialog box is on the
      display, no other program can receive mouse messages. System-modal
      message boxes and dialog boxes prohibit switching to another window or


      program while the box is active. (An example of a system-modal message
      box is the one that says "This will end your Windows session" when you
      close the Program Manager.)

Simple Mouse Processing: An Example

The CONNECT program, shown in Figure 4-1, does some simple mouse processing
to let you get a good feel for how Windows sends your program mouse
messages.

 CONNECT.MAK

#-----------------------
# CONNECT.MAK make file
#-----------------------

connect.exe : connect.obj connect.def
     link connect, /align:16, NUL, /nod slibcew libw, connect
     rc connect.exe

connect.obj : connect.c
     cl -c -Gsw -Ow -W2 -Zp connect.c

 CONNECT.C

/*--------------------------------------------------
   CONNECT.C -- Connect-the-Dots Mouse Demo Program
                (c) Charles Petzold, 1990
  --------------------------------------------------*/

#include 
#define MAXPOINTS 1000

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

int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance,
                    LPSTR lpszCmdLine, int nCmdShow)
     {
     static char szAppName[] = "Connect" ;
     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, "Connect-the-Dots Mouse Demo",
                         WS_OVERLAPPEDWINDOW,
                         CW_USEDEFAULT, CW_USEDEFAULT,
                         CW_USEDEFAULT, CW_USEDEFAULT,
                         NULL, NULL, hInstance, NULL) ;

     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 POINT points[MAXPOINTS] ;
     static short nCount ;
     HDC          hdc ;
     PAINTSTRUCT  ps ;
     short        i, j ;

     switch (message)
          {
          case WM_LBUTTONDOWN :
               nCount = 0 ;
               InvalidateRect (hwnd, NULL, TRUE) ;
               return 0 ;
          case WM_MOUSEMOVE :
               if (wParam & MK_LBUTTON && nCount < MAXPOINTS)
                    {
                    points [nCount++] = MAKEPOINT (lParam) ;
                    hdc = GetDC (hwnd) ;
                    SetPixel (hdc, LOWORD (lParam), HIWORD (lParam), 0L) ;
                    ReleaseDC (hwnd, hdc) ;
                    }
               return 0 ;

          case WM_LBUTTONUP :
               InvalidateRect (hwnd, NULL, FALSE) ;
               return 0 ;

          case WM_PAINT :
               hdc = BeginPaint (hwnd, &ps) ;

               for (i = 0 ; i < nCount - 1 ; i++)
                    for (j = i ; j < nCount ; j++)
                         {
                         MoveTo (hdc, points[i].x, points[i].y) ;
                         LineTo (hdc, points[j].x, points[j].y) ;
                         }

               EndPaint (hwnd, &ps) ;
               return 0 ;

          case WM_DESTROY :
               PostQuitMessage (0) ;
               return 0 ;
          }
     return DefWindowProc (hwnd, message, wParam, lParam) ;
     }

 CONNECT.DEF

;------------------------------------
; CONNECT.DEF module definition file
;------------------------------------

NAME           CONNECT

DESCRIPTION    'Mouse Connect Program (c) Charles Petzold, 1990'
EXETYPE        WINDOWS
STUB           'WINSTUB.EXE'
CODE           PRELOAD MOVEABLE DISCARDABLE
DATA           PRELOAD MOVEABLE MULTIPLE
HEAPSIZE       1024
STACKSIZE      8192
EXPORTS        WndProc

CONNECT processes three mouse messages:

  þ   WM_LBUTTONDOWN--CONNECT clears the client area.

  þ   WM_MOUSEMOVE--If the left button is down, CONNECT draws a black dot on
      the client area at the mouse position.

  þ   WM_LBUTTONUP--CONNECT connects every dot drawn in the client area to
      every other dot. Sometimes this results in a pretty design; sometimes
      in a dense blob. (See Figure 4-2.)

To use CONNECT, bring the mouse cursor into the client area, press the left
button, move the mouse around a little, and release the left button. CONNECT
works best for a curved pattern of a few dots, which you can draw by moving
the mouse quickly while the left button is depressed. CONNECT uses several
simple Graphics Device Interface (GDI) functions. SetPixel draws a one-pixel
dot of a particular color, in this case black. (On high-resolution displays,
the pixel may be nearly invisible.) Drawing the lines requires two
functions: MoveTo marks the x-coordinate and y-coordinate of the beginning
of the line, and LineTo draws the line.

If you move the mouse cursor out of the client area before releasing the
button, CONNECT does not connect the dots, because it doesn't receive the
WM_LBUTTONUP message. If you move the mouse back into the client area and
press the left button again, CONNECT clears the client area. (If you want to
continue a design after releasing the

  (Figure 4-2. may be found in the printed book.)

button outside the client area, press the left button again while the mouse
is outside the client area and then move the mouse back inside.)

CONNECT stores a maximum of 1000 points. The number of lines it draws is
equal to:


  (P) x (P - 1)

               where P is the number of points. With 1000 points, this
involves almost 500,000 lines, which can take several minutes to draw. For
anything but a demonstration program, this is too long for a Windows program
to hog system resources.

If CONNECT is busy drawing lines, you can press the mouse button, move the
mouse around, and release the mouse button, but nothing will happen. CONNECT
does not receive these messages because it is busy and not making any
GetMessage calls. After CONNECT finishes drawing the lines, it does not
receive these messages because the mouse button has been released already.
In this respect, the mouse is not like the keyboard. Windows treats every
keystroke as if it were important. However, if a mouse button is pressed and
released in the client area while a program is busy, the mouse clicks are
discarded.

Now try this: While CONNECT is engaged in a lengthy drawing routine, hold
down the mouse button and move the mouse around. After CONNECT is finished
drawing, it will retrieve the WM_LBUTTONDOWN message from the queue (and
clear the client area) because the button is currently down. However, it
receives only the WM_MOUSEMOVE messages that occur after it receives the
WM_LBUTTONDOWN message.

Sometimes the word "tracking" is used to refer to the way that programs
process mouse movement. Tracking does not mean, however, that your program
sits in a loop in its window procedure attempting to follow the mouse's
movements on the display. The window procedure instead processes each mouse
message as it comes and then quickly exits.


POINT, RECT, and lParam

CONNECT uses an array of POINT structures for saving points. The POINT
structure is defined in WINDOWS.H and has two fields named x and y:

typedef struct tagPOINT
  {
    int x ;
    int y ;
  } POINT ;

Some Windows functions require a POINT structure (or a pointer to a POINT
structure) as a parameter. You can define a POINT structure variable (named
point, for instance) in your program with this definition:

POINT point ;

If you need to convert an lParam value--the x and y mouse coordinates--to a
POINT structure, you can use the MAKEPOINT macro:

point = MAKEPOINT (lParam) ;

In WINDOWS.H, MAKEPOINT is defined like this:

#define MAKEPOINT(l) (*((POINT *)&l))

Despite the apparent complexity of this macro, it compiles very efficiently
because all it does is store lParam at the address of point. WINDOWS.H
defines the type PPOINT as a pointer to a POINT structure, so perhaps this
statement (without using the macro) makes the conversion a little clearer:

point = * (PPOINT) &lParam ;

(Remember that standard C order-of-evaluation rules cause address,
indirection, and type cast operators to be evaluated from right to left.)

The RECT structure defines a rectangle. Here's the WINDOWS.H definition:

typedef struct tagRECT
  {
    int left ;
    int top ;
    int right ;
    int bottom ;
  } RECT ;

This structure really contains two points side by side: left and right are
x-coordinates, and top and bottom are y-coordinates. You can define a
structure variable (named rect, for instance) with the statement:

RECT rect ;

Transferring coordinates from a RECT structure to a POINT structure is also
straightforward. This statement sets point to the upper left corner of the
rectangle:

point = * (PPOINT) &rect.left ;

This does the same for the lower right corner:

point = * (PPOINT) &rect.right ;

You can also define an array of two points:

POINT points [2] ;

and transfer these two points into a RECT structure:

rect = * (PRECT) points ;

PRECT is defined in WINDOWS.H as a pointer to a RECT structure. You don't
need the & (address) operator before points because points is an array. In
C, an array name is the address of the first element of the array.


Processing Shift Keys

When CONNECT receives a WM_MOUSEMOVE message, it performs a bitwise AND
operation on the value of wParam and MK_LBUTTON to determine if the left
button is depressed. You can also use wParam to determine the state of the
Shift keys. For instance, if processing must be dependent on the status of
the Shift and Ctrl keys, you might use logic that looks like this:

if (MK_SHIFT & wParam)
     if (MK_CONTROL & wParam)
          {
[Shift and Ctrl keys are down]
          }
     else
          {
[Shift key is down]
          }
else if (MK_CONTROL & wParam)
          {
[Ctrl key is down]
          }
     else
          {
[neither Shift nor Ctrl key is down]
          }

The Windows function GetKeyState (described in Chapter 3) can also return
the state of the mouse buttons or shift keys using the virtual-key codes
VK_LBUTTON, VK_RBUTTON, VK_MBUTTON, VK_SHIFT, and VK_CONTROL. The button or
key is down if the value returned from GetKeyState is negative. Because
GetKeyState returns mouse or key states as of the message currently being
processed, the status information is properly synchronized with the
messages. But just as you cannot use GetKeyState for a key that has yet to
be pressed, so you cannot use it for a mouse button that has yet to be
pressed. Don't do this:

while (GetKeyState (VK_LBUTTON) >= 0) ;  // WRONG !!!

The GetKeyState function will report that the left button is depressed only
if the button is already depressed when you process the message during which
you call GetKeyState.


Mouse Double-Clicks

A mouse double-click is two clicks in quick succession. To qualify as a
double-click, the two clicks must occur within a specific interval called
the "double-click time." If you want your window procedure to receive
double-click mouse messages, you must include the identifier CS_DBLCLKS when
initializing the window style in the window class structure before calling
RegisterClass:

wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS ;

If you do not include CS_DBLCLKS in the window style and the user clicks the
left mouse button twice in quick succession, your window procedure receives
these messages: WM_LBUTTONDOWN, WM_LBUTTONUP, WM_LBUTTONDOWN, and
WM_LBUTTONUP. (The window procedure might also receive other messages
between these button messages.) If you want to implement your own
double-click logic, you can use the Windows function GetMessageTime to
obtain the relative times of the WM_LBUTTONDOWN messages. This function is
discussed in more detail in Chapter 5.

If you include CS_DBLCLKS in your window class, the window procedure
receives these messages for a double-click: WM_LBUTTONDOWN, WM_LBUTTONUP,
WM- _LBUTTONDBLCLK, and WM_LBUTTONUP. The WM_LBUTTONDBLCLK message simply
replaces the second WM_LBUTTONDOWN message.

Double-click messages are much easier to process if the first click of a
double-click performs the same action as a single click. The second click
(the WM_LBUTTONDBLCLK message) then does something in addition to the first
click. For example, look at how the mouse works with the file list in the
File Manager program. A single click selects the file. The File Manager
highlights the file with a reverse-video bar. A double-click performs two
actions: The first click selects the file, just as a single click does; the
second click (which is a WM_LBUTTONDBLCLK message) directs the File Manager
to run the file. That's fairly easy logic.

Mouse-handling logic could get more complex if the first click of a
double-click does not perform the same action as a single click.



NONCLIENT-AREA MOUSE MESSAGES

The 10 mouse messages discussed so far occur when the mouse is moved or
clicked within the client area of a window. If the mouse is outside a
window's client area but still within the window, Windows sends the window
procedure a "nonclient-area" mouse message. The nonclient area includes the
caption bar, the menu, and window scroll bars.

You do not usually need to process nonclient-area mouse messages. Instead,
you simply pass them on to DefWindowProc so Windows can perform system
functions. In this respect, the nonclient-area mouse messages are similar to
the system keyboard messages WM_SYSKEYDOWN, WM_SYSKEYUP, and WM_SYSCHAR.

The nonclient-area mouse messages parallel almost exactly the client-area
mouse messages. The messages include the letters "NC" to indicate
"nonclient." If the mouse is moved within a nonclient area of a window, then
the window procedure receives the message WM_NCMOUSEMOVE. The mouse buttons
generate these messages:

Button  Pressed           Released        Pressed (2d Click)
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Left    WM_NCLBUTTONDOWN  WM_NCLBUTTONUP  WM_NCLBUTTONDBLCLK
Middle  WM_NCMBUTTONDOWN  WM_NCMBUTTONUP  WM_NCMBUTTONDBLCLK
Right   WM_NCRBUTTONDOWN  WM_NCRBUTTONUP  WM_NCRBUTTONDBLCLK

However, the wParam and lParam parameters for nonclient-area mouse messages
are different from those for client-area mouse messages. The wParam
parameter indicates the nonclient area where the mouse was moved or clicked.
It is set to one of the identifi- ers beginning with HT that are defined in
WINDOWS.H (such as HTCAPTION and HTSYSMENU).

The lParam variable contains an x-coordinate in the low word and a
y-coordinate in the high word. However, these are screen coordinates, not
client-area coordinates as they are for client-area mouse messages. For
screen coordinates, the upper left corner of the display area has x and y
values of 0. Values of x increase as you move to the right, and values of y
increase as you move down. (See Figure 4-3.)

  (Figure 4-3. may be found in the printed book.)

You can convert screen coordinates to client-area coordinates and vice versa
with two Windows functions:

ScreenToClient (hwnd, lpPoint) ;
ClientToScreen (hwnd, lpPoint) ;

The lpPoint parameter is a far (or long) pointer to a structure of type
POINT. These two functions convert the values stored in the structure
without preserving the old values. Note that if a screen-coordinate point is
above the window's client area, then the converted client-area y-coordinate
will be negative. Similarly, a screen coordinate to the left of a client
area is a negative x value when expressed as a client-area coordinate.

The Hit-Test Message

If you've been keeping count, you know that we've covered 20 of the 21 mouse
messages. The last message is WM_NCHITTEST, which stands for "nonclient hit
test." This message precedes all other client-area and nonclient-area mouse
messages. The lParam parameter contains the x and y screen coordinates of
the mouse position. The wParam parameter is not used.

Windows applications usually pass this message to DefWindowProc. Windows
then uses the WM_NCHITTEST message to generate all other mouse messages
based on the position of the mouse. For nonclient-area mouse messages, the
value returned from DefWindowProc when processing WM_NCHITTEST becomes the
wParam parameter in the mouse message. This value can be any of the wParam
values that accompany the nonclient-area mouse messages plus the following:

ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
HTCLIENT             Client area
HTNOWHERE            Not on any window
HTTRANSPARENT        A window covered by another window
HTERROR              Causes DefWindowProc to produce a beep

If DefWindowProc returns HTCLIENT after it processes a WM_NCHITTEST message,
then Windows converts the screen coordinates to client-area coordinates and
generates a client-area mouse message.

If you remember how we disabled all system keyboard functions by trapping
the WM_SYSKEYDOWN message, you may wonder if you can do something similar by
trapping mouse messages. Sure. If you include the lines:

case WM_NCHITTEST :
     return (long) HTNOWHERE ;

in your window procedure, you will effectively disable all client-area and
nonclient-area mouse messages to your window. The mouse buttons will simply
not work while the mouse is anywhere within your window, including the
system menu box and size box.


Messages Beget Messages

Windows uses the WM_NCHITTEST message to generate all other mouse messages.
The idea of messages giving birth to other messages is common in Windows.
Let's take an example. As you know, if you double-click the system menu box
of a Windows program, the program will be terminated. The double-click
generates a series of WM_NCHITTEST messages. Because the mouse is positioned
over the system menu box, DefWindowProc returns a value of HTSYSMENU and
Windows puts a WM_NCLBUTTONDBLCLK message in the message queue with wParam
equal to HTSYSMENU.

The window procedure usually passes that mouse message to DefWindowProc.
When DefWindowProc receives the WM_NCLBUTTONDBLCLK message with wParam equal
to HTSYSMENU, it puts a WM_SYSCOMMAND message with wParam equal to  SC_CLOSE
in the message queue. (This WM_SYSCOMMAND message is also generated when a
user selects Close from the system menu box.) Again, the window procedure
usually passes that message to DefWindowProc. DefWindowProc processes the
message by sending a WM_CLOSE message to the window procedure.

If a program wants to require confirmation from a user before terminating,
the window procedure can trap WM_CLOSE. Otherwise, DefWindowProc processes
WM_CLOSE by calling the DestroyWindow function. Among other actions,
DestroyWindow sends a WM_DESTROY message to the window procedure. Normally,
a window procedure processes WM_DESTROY with the code:

case WM_DESTROY :
     PostQuitMessage (0) ;
     return 0 ;

The PostQuitMessage causes Windows to place a WM_QUIT message in the message
queue. This message never reaches the window procedure because it causes
GetMessage to return 0, which terminates the message loop and the program.



HIT-TESTING IN YOUR PROGRAMS

Earlier I discussed how the File Manager responded to mouse clicks and
double-clicks. Obviously, the program must determine which file the user is
pointing at with the mouse. This is called "hit-testing." Just as
DefWindowProc must do some hit-testing when processing WM_NCHITTEST
messages, very often a window procedure must do some hit-testing within the
client area. In general, hit-testing involves calculations using the x- and
y-coordinates passed to your window procedure in the lParam parameter of the
mouse message.

A Hypothetical Example

Here's an example. Your program displays several columns of alphabetically
sorted files similar to the File Manager file windows. The file list starts
at the top of the client area, which is cxClient pixels wide and cyClient
pixels high; each character is cyChar pixels high. The filenames are stored
in a sorted array of pointers to character strings called szFileNames.

Let's assume that the columns are cxColWidth pixels wide. The number of
files you can fit in each column is:

nNumInCol = cyClient / cyChar ;

You receive a mouse click message with the coordinates cxMouse and cyMouse
derived from lParam. You can determine which column of filenames the user is
pointing to by using the formula:

nColumn = cxMouse / cxColWidth ;

The position of the filename in relation to the top of the column is:

nFromTop = cyMouse / cyChar ;

Now you can calculate an index to the szFileNames array:

nIndex = nColumn * nNumInCol + nFromTop ;

Obviously, if nIndex exceeds the number of files in the array, the user is
clicking on a blank area of the display.

In many cases, hit-testing is more complex than this example suggests. For
instance, it can become very messy in a word processing program that uses
variable font sizes (such as WRITE). When you display something to the
client area, you must determine the coordinates for each item you display.
In hit-testing calculations, you must go backward from the coordinates to
the object. However, if the objects you display are strings, then going
backward involves finding the character position within the string.


A Sample Program

The CHECKER1 program, shown in Figure 4-4, demonstrates some simple
hit-testing. The program divides the client area into a 5-by-5 array of 25
rectangles. If you click the mouse on one of the rectangles, the rectangle
is filled with an X. If you click there again, the X is removed.

 CHECKER1.MAK

#------------------------
# CHECKER1.MAK make file
#------------------------

checker1.exe : checker1.obj checker1.def
     link checker1, /align:16, NUL, /nod slibcew libw, checker1
     rc checker1.exe


checker1.obj : checker1.c
     cl -c -Gsw -Ow -W2 -Zp checker1.c

 CHECKER1.C

/*-------------------------------------------------
   CHECKER1.C -- Mouse Hit-Test Demo Program No. 1
                 (c) Charles Petzold, 1990
  -------------------------------------------------*/

#include 
#define DIVISIONS 5

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

int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance,
                    LPSTR  lpszCmdLine, int nCmdShow)
     {
     static char szAppName[] = "Checker1" ;
     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, "Checker1 Mouse Hit-Test Demo",
                         WS_OVERLAPPEDWINDOW,
                         CW_USEDEFAULT, CW_USEDEFAULT,
                         CW_USEDEFAULT, CW_USEDEFAULT,
                         NULL, NULL, hInstance, NULL) ;

     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  fState[DIVISIONS][DIVISIONS] ;
     static short cxBlock, cyBlock ;
     HDC          hdc ;
     PAINTSTRUCT  ps ;
     RECT         rect ;
     short        x, y ;

     switch (message)
          {
          case WM_SIZE :
               cxBlock = LOWORD (lParam) / DIVISIONS ;
               cyBlock = HIWORD (lParam) / DIVISIONS ;
               return 0 ;

          case WM_LBUTTONDOWN :
               x = LOWORD (lParam) / cxBlock ;
               y = HIWORD (lParam) / cyBlock ;

               if (x < DIVISIONS && y < DIVISIONS)
                    {
                    fState [x][y] ^= 1 ;
                    rect.left   = x * cxBlock ;
                    rect.top    = y * cyBlock ;
                    rect.right  = (x + 1) * cxBlock ;
                    rect.bottom = (y + 1) * cyBlock ;

                    InvalidateRect (hwnd, &rect, FALSE) ;
                    }
               else
                    MessageBeep (0) ;
               return 0 ;

          case WM_PAINT :
               hdc = BeginPaint (hwnd, &ps) ;

               for (x = 0 ; x < DIVISIONS ; x++)
                    for (y = 0 ; y < DIVISIONS ; y++)
                         {
                         Rectangle (hdc, x * cxBlock, y * cyBlock,
                                   (x + 1) * cxBlock, (y + 1) * cyBlock) ;
                         if (fState [x][y])
                              {
                              MoveTo (hdc,  x    * cxBlock,  y    * cyBlock)
;
                              LineTo (hdc, (x+1) * cxBlock, (y+1) * cyBlock)
;
                              MoveTo (hdc,  x    * cxBlock, (y+1) * cyBlock)
;
                              LineTo (hdc, (x+1) * cxBlock,  y    * cyBlock)
;
                              }
                         }
               EndPaint (hwnd, &ps) ;
               return 0 ;

          case WM_DESTROY :
               PostQuitMessage (0) ;
               return 0 ;
          }
     return DefWindowProc (hwnd, message, wParam, lParam) ;
     }

 CHECKER1.DEF

;-------------------------------------
; CHECKER1.DEF module definition file
;-------------------------------------

NAME           CHECKER1

DESCRIPTION    'Mouse Hit-Test 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

Figure 4-5 shows the CHECKER1 display. All 25 rectangles have the same width
and height. These width and height values are stored in cxBlock and cyBlock
and are recalculated when the size of the client area changes. The
WM_LBUTTONDOWN logic uses the mouse coordinates to determine which rectangle
has been clicked. It flags the current state of the rectangle in the array
fState and invalidates the rectangle to generate a WM- _PAINT message. If
the width or height of the client area is not evenly divisible by five, a
small strip of client area at the left or bottom will not be covered by a
rectangle. For error processing, CHECKER1 responds to a mouse click in this
area by calling MessageBeep.

When CHECKER1 receives a WM_PAINT message, it repaints the entire client
area by drawing rectangles using the GDI Rectangle function. If the fState
value is set,

  (Figure 4-5. may be found in the printed book.)

CHECKER1 draws two lines using the MoveTo and LineTo functions. During
WM_PAINT processing, CHECKER1 does not check the validity of each
rectangular section before repainting it, but it could. One method for
checking validity involves building a RECT structure for each rectangular
block within the loop (using the same formulas as in the WM_LBUTTONDOWN
logic) and checking whether it intersects the invalid rectangle
(ps.rcPaint) by using the function IntersectRect. Another method is to use
PtInRect to determine if any of the four corners of the rectangular block
are within the invalid rectangle.


Emulating the Mouse with the Keyboard

CHECKER1 works only if you have a mouse. We'll be adding a keyboard
interface to the program shortly, as we did for the SYSMETS program in
Chapter 3. However, adding a keyboard interface to a program that uses the
mouse cursor for pointing purposes requires that we also must worry about
displaying and moving the mouse cursor.

Even if a mouse device is not installed, Windows can still display a mouse
cursor. Windows maintains a "display count" for this cursor. If a mouse is
installed, the display count is initially 0; if not, the display count is
initially -1. The mouse cursor is displayed only if the display count is 0
or positive. You can increment the display count by calling:

ShowCursor (TRUE) ;

and decrement it by calling:

ShowCursor (FALSE) ;

You do not need to determine if a mouse is installed before using
ShowCursor. If you want to display the mouse cursor regardless of the
presence of the mouse, simply increment the display count. After you
increment the display count once, decrementing it will hide the cursor if no
mouse is installed but leave it displayed if a mouse is present. The display
count applies to all of Windows, so you should ensure that you increment and
decrement the display count an equal number of times.

You may want to use the following simple logic in your window procedure:

WM_SETFOCUS :
     ShowCursor (TRUE) ;
     return 0 ;

WM_KILLFOCUS :
     ShowCursor (FALSE) ;
     return 0 ;

A window procedure receives the WM_SETFOCUS message when the window obtains
the keyboard input focus and WM_KILLFOCUS when it loses the input focus.
These are ideal times to display and hide the mouse cursor. First, the
WM_SETFOCUS and WM_KILLFOCUS calls are balanced--that is, the window
procedure will increment and decrement the mouse cursor display count an
equal number of times. Second, for versions of Windows installed without a
mouse, using the WM_SETFOCUS and WM_KILLFOCUS messages causes the cursor to
be visible only when the window has the input focus. That is also the only
time the user can move the cursor using the keyboard interface that you'll
design.

Windows maintains a current mouse cursor position even if a mouse is not
installed. If a mouse is not installed, and you display the mouse cursor, it
may appear in any part of the display, and will remain in that position
until you explicitly move it. You can obtain the cursor position by using:

GetCursorPos (lpPoint) ;

where lpPoint is a far pointer to a POINT structure. The function fills in
the POINT fields with the x- and y-coordinates of the mouse. You can set the
cursor position by using:

SetCursorPos (x, y) ;

In both cases, the x and y values are screen coordinates, not client-area
coordinates. (This should be evident because the functions do not require a
hwnd parameter.) As noted earlier, you can convert screen coordinates to
client-area coordinates and vice versa by using ScreenToClient and
ClientToScreen.

If you call GetCursorPos while processing a mouse message and convert to
client-area coordinates, the coordinates may still be slightly different
from those in lParam of the mouse message. The coordinates returned from
GetCursorPos indicate the current position of the mouse. The coordinates in
lParam of a mouse message are the coordinates of the mouse when the message
was generated.

You'll probably want to write keyboard logic to move the mouse cursor with
the keyboard arrow keys and simulate the mouse button with the Spacebar or
Enter key. What you don't want to do is move the mouse cursor one pixel per
keystroke. That forces a user to hold down an arrow key for more than a
minute to move the mouse cursor from one side of the display to the other.

If you need to implement a keyboard interface to the mouse cursor but still
maintain the ability to position the cursor at precise pixel locations, take
a look at Windows' PAINTBRUSH program. When you hold down an arrow key, the
mouse cursor starts moving slowly but then speeds up. You'll recall that the
lParam parameter in WM_KEYDOWN messages indicates if the keystroke messages
are the result of typematic action. This is an excellent application of that
information.


Adding a Keyboard Interface to CHECKER

The CHECKER2 program, shown in Figure 4-6, is the same as CHECKER1 except
that it includes a keyboard interface. You can use the Left, Right, Up, and
Down arrow keys to move the cursor among the 25 rectangles. The Home key
sends the cursor to the upper left rectangle; the End key drops it down to
the lower right rectangle. Both the Spacebar and Enter keys toggle the X
mark.

 CHECKER2.MAK

#------------------------
# CHECKER2.MAK make file
#------------------------

checker2.exe : checker2.obj checker2.def
     link checker2, /align:16, NUL, /nod slibcew libw, checker2
     rc checker2.exe

checker2.obj : checker2.c
     cl -c -Gsw -Ow -W2 -Zp checker2.c

 CHECKER2.C

/*-------------------------------------------------
   CHECKER2.C -- Mouse Hit-Test Demo Program No. 2
                 (c) Charles Petzold, 1990
  -------------------------------------------------*/


#include 
#define DIVISIONS 5

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

int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance,
                    LPSTR lpszCmdLine, int nCmdShow)

     {
     static char szAppName[] = "Checker2" ;
     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, "Checker2 Mouse Hit-Test Demo",
                         WS_OVERLAPPEDWINDOW,
                         CW_USEDEFAULT, CW_USEDEFAULT,
                         CW_USEDEFAULT, CW_USEDEFAULT,
                         NULL, NULL, hInstance, NULL) ;

     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  fState[DIVISIONS][DIVISIONS] ;
     static short cxBlock, cyBlock ;
     HDC          hdc ;
     PAINTSTRUCT  ps ;
     POINT        point ;
     RECT         rect ;
     short        x, y ;

     switch (message)
          {
          case WM_SIZE :
               cxBlock = LOWORD (lParam) / DIVISIONS ;
               cyBlock = HIWORD (lParam) / DIVISIONS ;
               return 0 ;

          case WM_SETFOCUS :
               ShowCursor (TRUE) ;
               return 0 ;

          case WM_KILLFOCUS :
               ShowCursor (FALSE) ;

               return 0 ;

          case WM_KEYDOWN :
               GetCursorPos (&point) ;
               ScreenToClient (hwnd, &point) ;

               x = max (0, min (DIVISIONS - 1, point.x / cxBlock)) ;
               y = max (0, min (DIVISIONS - 1, point.y / cyBlock)) ;

               switch (wParam)
                    {
                    case VK_UP :
                         y-- ;
                         break ;

                    case VK_DOWN :
                         y++ ;
                         break ;

                    case VK_LEFT :
                         x-- ;
                         break ;

                    case VK_RIGHT :
                         x++ ;
                         break ;

                    case VK_HOME :
                         x = y = 0 ;
                         break ;
                    case VK_END :
                         x = y = DIVISIONS - 1 ;
                         break ;

                    case VK_RETURN :
                    case VK_SPACE :
                         SendMessage (hwnd, WM_LBUTTONDOWN, MK_LBUTTON,
                                   MAKELONG (x * cxBlock, y * cyBlock)) ;
                         break ;
                    }
               x = (x + DIVISIONS) % DIVISIONS ;
               y = (y + DIVISIONS) % DIVISIONS ;

               point.x = x * cxBlock + cxBlock / 2 ;
               point.y = y * cyBlock + cyBlock / 2 ;

               ClientToScreen (hwnd, &point) ;
               SetCursorPos (point.x, point.y) ;
               return 0 ;

          case WM_LBUTTONDOWN :
               x = LOWORD (lParam) / cxBlock ;
               y = HIWORD (lParam) / cyBlock ;

               if (x < DIVISIONS && y < DIVISIONS)
                    {
                    fState[x][y] ^= 1 ;

                    rect.left   = x * cxBlock ;
                    rect.top    = y * cyBlock ;
                    rect.right  = (x + 1) * cxBlock ;
                    rect.bottom = (y + 1) * cyBlock ;

                    InvalidateRect (hwnd, &rect, FALSE) ;
                    }
               else
                    MessageBeep (0) ;
               return 0 ;

          case WM_PAINT :
               hdc = BeginPaint (hwnd, &ps) ;

               for (x = 0 ; x < DIVISIONS ; x++)
                    for (y = 0 ; y < DIVISIONS ; y++)
                         {
                         Rectangle (hdc, x * cxBlock, y * cyBlock,
                                   (x + 1) * cxBlock, (y + 1) * cyBlock) ;

                         if (fState [x][y])
                              {
                              MoveTo (hdc,  x    * cxBlock,  y    * cyBlock)
;
                              LineTo (hdc, (x+1) * cxBlock, (y+1) * cyBlock)
;
                              MoveTo (hdc,  x    * cxBlock, (y+1) * cyBlock)
;
                              LineTo (hdc, (x+1) * cxBlock,  y    * cyBlock)
;
                              }
                         }
               EndPaint (hwnd, &ps) ;
               return 0 ;

          case WM_DESTROY :
               PostQuitMessage (0) ;
               return 0 ;
          }
     return DefWindowProc (hwnd, message, wParam, lParam) ;
     }

 CHECKER2.DEF

;-------------------------------------
; CHECKER2.DEF module definition file
;-------------------------------------

NAME           CHECKER2

DESCRIPTION    'Mouse Hit-Test Demo Program No. 2 (c) Charles Petzold, 1990'
EXETYPE        WINDOWS
STUB           'WINSTUB.EXE'
CODE           PRELOAD MOVEABLE DISCARDABLE
DATA           PRELOAD MOVEABLE MULTIPLE
HEAPSIZE       1024
STACKSIZE      8192
EXPORTS        WndProc

The WM_KEYDOWN logic in CHECKER2 determines the position of the cursor
(GetCursorPos), converts the screen coordinates to client-area coordinates
(ScreenToClient), and divides the coordinates by the width and height of the
rectangular block. This produces x and y values that indicate the position
of the rectangle in the 5-by-5 array. The mouse cursor may or may not be in
the client area when a key is pressed, so x and y must be passed through the
WINDOWS.H min and max macros to ensure that they range from 0 through 4.

For arrow keys, CHECKER2 increments or decrements x and y appropriately. If
the key is the Enter key (VK_RETURN) or Spacebar (VK_SPACE), CHECKER2 uses
SendMessage to send a WM_LBUTTONDOWN message to itself. This technique is
similar to the method used in the SYSMETS program in Chapter 3 to add a
keyboard interface to the window scroll bar. The WM_KEYDOWN logic finishes
by calculating client-area coordinates that point to the center of the
rectangle, converting to screen coordinates (ClientToScreen), and setting
the cursor position (SetCursorPos).


Using Child Windows for Hit-Testing

Some programs, like the Windows PAINTBRUSH program, divide the client area
into several smaller logical areas. The PAINTBRUSH program, shown in Figure
4-7, has an area at the left for its icon-based menu and an area at the
bottom for the color menu. PAINTBRUSH, when hit-testing on these two menus,
must take into account the location of the menu within the client area
before determining the menu item being selected by the user.

Or maybe not. In reality, PAINTBRUSH simplifies the menu drawing and
hit-testing through the use of "child windows." The child windows divide the
entire client area into several smaller rectangular regions. Each child
window has its own window handle, window procedure, and client area. Each
window procedure receives mouse messages that apply only to its child
window. The lParam parameter in the mouse message contains coordinates
relative to the upper left corner of the client area of the child window,
not of the parent window.

Child windows used in this way can help you structure and modularize your
programs. If the child windows use different window classes, each child
window can have its

  (Figure 4-7. may be found in the printed book.)

own window procedure. The different window classes can also define different
background colors and different default cursors. In Chapter 6, we'll look at
"child window controls"--predefined child windows that take the form of
scroll bars, buttons, and edit boxes. Right now, let's see how we can use
child windows in the CHECKER program.


Child Windows in CHECKER

Figure 4-8 shows CHECKER3. This version of the program creates 25 child
windows to process mouse clicks. It does not have a keyboard interface, but
one could be easily added.

CHECKER3 has two window procedures called WndProc and ChildWndProc. WndProc
is still the window procedure for the main (or parent) window. ChildWndProc
is the window procedure for the 25 child windows. The names of both window
procedures must appear as EXPORTS in the CHECKER3.DEF file because both
procedures are called from Windows.

Because the window procedure is defined by the window class structure that
you register with Windows using the RegisterClass call, the two window
procedures in CHECKER3.C require two window classes. The first window class
is for the main window and has the name "Checker3". The second window class
is given the name "Checker3_Child".

Most of the fields of the wndclass structure variable are simply reused when
"Checker3_Child" is registered in WinMain. The lpszClassName field is set to
"Checker3_Child"--the name of the class. The lpfnWndProc field is set to
ChildWndProc, the window procedure for this window class, and the hIcon
field is set to NULL, because icons are not used with child windows. For the
"Checker3_Child" window class, the cbWndExtra field in the wndclass
structure variable is set to 2 bytes, or more precisely, sizeof(WORD). This
field tells Windows to reserve 2 bytes of extra space in a structure that
Windows maintains for each window based on this window class. You can use
this space to store information that may be different for each window.

 CHECKER3.MAK

#------------------------
# CHECKER3.MAK make file
#------------------------

checker3.exe : checker3.obj checker3.def
     link checker3, /align:16, NUL, /nod slibcew libw, checker3
     rc checker3.exe

checker3.obj : checker3.c
     cl -c -Gsw -Ow -W2 -Zp checker3.c

gure 4-8.

The CHECKER3 program.

 CHECKER3.C

/*-------------------------------------------------
   CHECKER3.C -- Mouse Hit-Test Demo Program No. 3
                 (c) Charles Petzold, 1990
  -------------------------------------------------*/

#include 
#define DIVISIONS 5

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

char szChildClass[] = "Checker3_Child" ;

int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance,
                    LPSTR lpszCmdLine, int nCmdShow)
     {
     static char szAppName[] = "Checker3" ;
     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) ;

          wndclass.lpfnWndProc   = ChildWndProc ;
          wndclass.cbWndExtra    = sizeof (WORD) ;
          wndclass.hIcon         = NULL ;
          wndclass.lpszClassName = szChildClass ;

          RegisterClass (&wndclass) ;
          }

     hwnd = CreateWindow (szAppName, "Checker3 Mouse Hit-Test Demo",
                         WS_OVERLAPPEDWINDOW,
                         CW_USEDEFAULT, CW_USEDEFAULT,
                         CW_USEDEFAULT, CW_USEDEFAULT,
                         NULL, NULL, hInstance, NULL) ;

     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 HWND hwndChild [DIVISIONS] [DIVISIONS] ;
     short       cxBlock, cyBlock, x, y ;

     switch (message)
          {
          case WM_CREATE :
               for (x = 0 ; x < DIVISIONS ; x++)
                    for (y = 0 ; y < DIVISIONS ; y++)
                         {
                         hwndChild [x][y] = CreateWindow (szChildClass,
NULL,
                              WS_CHILDWINDOW | WS_VISIBLE,
                              0, 0, 0, 0,
                              hwnd, y << 8 | x,
                              GetWindowWord (hwnd, GWW_HINSTANCE), NULL) ;
                         }
               return 0 ;

          case WM_SIZE :
               cxBlock = LOWORD (lParam) / DIVISIONS ;
               cyBlock = HIWORD (lParam) / DIVISIONS ;
               for (x = 0 ; x < DIVISIONS ; x++)
                    for (y = 0 ; y < DIVISIONS ; y++)
                         MoveWindow (hwndChild [x][y],
                              x * cxBlock, y * cyBlock,
                              cxBlock, cyBlock, TRUE) ;
               return 0 ;

          case WM_LBUTTONDOWN :
               MessageBeep (0) ;
               return 0 ;

          case WM_DESTROY :
               PostQuitMessage (0) ;
               return 0 ;
          }
     return DefWindowProc (hwnd, message, wParam, lParam) ;
     }

long FAR PASCAL ChildWndProc (HWND hwnd, WORD message, WORD wParam, LONG
lParam)
     {
     HDC         hdc ;
     PAINTSTRUCT ps ;
     RECT        rect ;

     switch (message)
          {
          case WM_CREATE :
               SetWindowWord (hwnd, 0, 0) ;       // on/off flag
               return 0 ;

          case WM_LBUTTONDOWN :
               SetWindowWord (hwnd, 0, 1 ^ GetWindowWord (hwnd, 0)) ;
               InvalidateRect (hwnd, NULL, FALSE) ;
               return 0 ;

          case WM_PAINT :
               hdc = BeginPaint (hwnd, &ps) ;

               GetClientRect (hwnd, &rect) ;
               Rectangle (hdc, 0, 0, rect.right, rect.bottom) ;

               if (GetWindowWord (hwnd, 0))
                    {
                    MoveTo (hdc, 0,          0) ;
                    LineTo (hdc, rect.right, rect.bottom) ;
                    MoveTo (hdc, 0,          rect.bottom) ;
                    LineTo (hdc, rect.right, 0) ;
                    }

               EndPaint (hwnd, &ps) ;
               return 0 ;
          }
     return DefWindowProc (hwnd, message, wParam, lParam) ;
     }

 CHECKER3.DEF

;-------------------------------------
; CHECKER3.DEF module definition file
;-------------------------------------
NAME           CHECKER3

DESCRIPTION    'Mouse Hit-Test Demo Program No. 3 (c) Charles Petzold, 1990'
EXETYPE        WINDOWS
STUB           'WINSTUB.EXE'
CODE           PRELOAD MOVEABLE DISCARDABLE
DATA           PRELOAD MOVEABLE MULTIPLE
HEAPSIZE       1024
STACKSIZE      8192
EXPORTS        WndProc
               ChildWndProc

The CreateWindow call in WinMain creates the main window based on the
"Checker3" class. This is normal. However, when WndProc receives a WM_CREATE
message, it calls CreateWindow 25 times to create 25 child windows based on
the "Checker3_Child" window class. Here's a comparison of the parameters to
the CreateWindow call in WinMain that creates the main window and the
CreateWindow call in WndProc that creates the 25 child windows:


Parameter               Main Window          Child Window
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
window class            "Checker3"           "Checker3_Child"
window caption          "Checker3..."        NULL
window style            WS_OVERLAPPEDWINDOW  WS_CHILDWINDOW | WS_VISIBLE
horizontal position     CW_USEDEFAULT        0
vertical position       CW_USEDEFAULT        0
width                   CW_USEDEFAULT        0
height                  CW_USEDEFAULT        0
parent window handle    NULL                 hwnd
menu handle / child ID  NULL                 y << 8 | x
instance handle         hInstance            GetWindowWord (hwnd,
GWW_HINSTANCE)
extra parameters        NULL                 NULL


Normally, the position, width, and height parameters are required for child
windows, but in CHECKER3 the child windows are positioned and resized later
in WndProc. The parent window handle is NULL for the main window because it
is the parent. The parent window handle is required when using the
CreateWindow call to create a child window.

The main window doesn't have a menu, so that parameter is NULL. For child
windows, the same parameter position is called a "child ID." This is a
number that uniquely identifies the child window. The child ID becomes much
more important when working with child window controls because messages to
the parent window are identified by this child ID. For CHECKER3, I've used
the child ID to identify the position in the 5-by-5 array that each child
window occupies within the main window.

The instance handle is hInstance in both cases. When the child window is
created, the hInstance value is extracted using the function GetWindowWord
from the structure that Windows maintains for the window. (Rather than use
GetWindowWord, I could have saved the value of hInstance in a global
variable and used it directly.)

Each child window has a different window handle that is stored in the
hwndChild array. When WndProc receives a WM_SIZE message, it calls
MoveWindow for each of the 25 child windows. The parameters indicate the
upper left corner of the child window relative to the parent window
client-area coordinates, the width and height of the child window, and
whether the child window needs repainting.

Now let's take a look at ChildWndProc. This window procedure processes
messages for all 25 child windows. The hwnd parameter to ChildWndProc is the
handle to the child window receiving the message. When ChildWndProc
processes a WM_CREATE message (which will happen 25 times because there are
25 child windows), it uses SetWindowWord to store a 0 in the extra area
reserved within the window structure. (Recall that we reserved this space by
using the cbWndExtra field when defining the window class structure.)
ChildWndProc uses this value to store the current state (X or no X) of the
rectangle. When the child window is clicked, the WM_LBUTTONDOWN logic simply
flips the value of this word (from 0 to 1 or from 1 to 0) and invalidates
the entire child window client area. This area is the single rectangle being
clicked. The WM_PAINT processing is trivial, because the size of the
rectangle it draws is the same size as the client window.

Because the C source code file and the .EXE file of CHECKER3 are larger than
those for CHECKER1 (to say nothing of my explanation of the programs), I
will not try to convince you that CHECKER3 is "simpler" than CHECKER1. But
note that we no longer have to do any mouse hit-testing! If a child window
in CHECKER3 gets a WM_LBUTTONDOWN message, the window has been hit, and
that's all it needs to know.

If you want to add a keyboard interface to CHECKER3, be aware that the main
window still gets keyboard messages because it has the input focus. We'll
explore child windows more in Chapter 6.



CAPTURING THE MOUSE

A window procedure normally receives mouse messages only when the mouse
cursor is positioned over the client or nonclient area of the window. A
program that needs to receive mouse messages when the mouse is outside the
window can "capture" the mouse.

Capturing the mouse is easier than baiting a mousetrap. You need only call:

SetCapture (hwnd) ;

After this function call, Windows sends all mouse messages to the window
procedure for the window whose handle is hwnd. The mouse messages are always
client-area messages, even when the mouse is in a nonclient area of the
window. The lParam parameter still indicates the position of the mouse in
client-area coordinates. These x- and y-coordinates, however,  can be
negative if the mouse is to the left of or above the client area.

During the time the mouse is captured, system keyboard functions are also
disabled. When you want to release the mouse, call:

ReleaseCapture () ;

This returns processing to normal.

The BLOWUP1 Program

The BLOWUP1 program, shown in Figure 4-9, uses SetCapture and ReleaseCapture
and a few other interesting techniques. The program lets you use the mouse
to block out any rectangular area of the screen. BLOWUP1 then copies the
contents of that rectangular area into its own client area, stretching or
compressing the image as appropriate. (See Figure 4-10 on page 169.)

 BLOWUP1.MAK

#-----------------------
# BLOWUP1.MAK make file
#-----------------------

blowup1.exe : blowup1.obj blowup1.def
     link blowup1, /align:16, NUL, /nod slibcew libw, blowup1
     rc blowup1.exe

blowup1.obj : blowup1.c
     cl -c -Gsw -Ow -W2 -Zp blowup1.c

 BLOWUP1.C

/*------------------------------------------------
   BLOWUP1.C -- Screen Capture Mouse Demo Program
               (c) Charles Petzold, 1990
  ------------------------------------------------*/

#include 

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

int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance,
                    LPSTR lpszCmdLine, int nCmdShow)


     {
     static char szAppName[] = "BlowUp1" ;
     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, "Blow-Up Mouse Demo",
                         WS_OVERLAPPEDWINDOW,
                         CW_USEDEFAULT, CW_USEDEFAULT,
                         CW_USEDEFAULT, CW_USEDEFAULT,
                         NULL, NULL, hInstance, NULL) ;

     ShowWindow (hwnd, nCmdShow) ;
     UpdateWindow (hwnd) ;

     while (GetMessage (&msg, NULL, 0, 0))
          {
          TranslateMessage (&msg) ;
          DispatchMessage (&msg) ;
          }
     return msg.wParam ;
     }

void InvertBlock (HWND hwnd, POINT ptBeg, POINT ptEnd)
     {
     HDC hdc ;

     hdc = CreateDC ("DISPLAY", NULL, NULL, NULL) ;
     ClientToScreen (hwnd, &ptBeg) ;
     ClientToScreen (hwnd, &ptEnd) ;
     PatBlt (hdc, ptBeg.x, ptBeg.y, ptEnd.x - ptBeg.x, ptEnd.y - ptBeg.y,
             DSTINVERT) ;
     DeleteDC (hdc) ;
     }
long FAR PASCAL WndProc (HWND hwnd, WORD message, WORD wParam, LONG lParam)
     {
     static BOOL  fCapturing, fBlocking ;
     static POINT ptBeg, ptEnd ;
     HDC          hdc ;
     RECT         rect ;

     switch (message)
          {
          case WM_LBUTTONDOWN :
               if (!fCapturing)
                    {
                    fCapturing = TRUE ;
                    SetCapture (hwnd) ;
                    SetCursor (LoadCursor (NULL, IDC_CROSS)) ;
                    }
               else if (!fBlocking)

                    {
                    fBlocking = TRUE ;
                    ptBeg = MAKEPOINT (lParam) ;
                    }
               return 0 ;

          case WM_MOUSEMOVE :
               if (fBlocking)
                    {
                    ptEnd = MAKEPOINT (lParam) ;
                    InvertBlock (hwnd, ptBeg, ptEnd) ;
                    InvertBlock (hwnd, ptBeg, ptEnd) ;
                    }
               return 0 ;

          case WM_LBUTTONUP :
               if (fBlocking)
                    {
                    fCapturing = fBlocking = FALSE ;
                    ptEnd = MAKEPOINT (lParam) ;
                    SetCursor (LoadCursor (NULL, IDC_WAIT)) ;

                    hdc = GetDC (hwnd) ;
                    GetClientRect (hwnd, &rect) ;
                    StretchBlt (hdc, 0, 0, rect.right, rect.bottom,
                                hdc, ptBeg.x, ptBeg.y,
                                ptEnd.x - ptBeg.x, ptEnd.y - ptBeg.y,
                                SRCCOPY) ;

                    ReleaseDC (hwnd, hdc) ;
                    SetCursor (LoadCursor (NULL, IDC_ARROW)) ;
                    ReleaseCapture () ;
                    }
               return 0 ;

          case WM_DESTROY :
               PostQuitMessage (0) ;
               return 0 ;
          }
     return DefWindowProc (hwnd, message, wParam, lParam) ;
     }

 BLOWUP1.DEF

;------------------------------------
; BLOWUP1.DEF module definition file
;------------------------------------

NAME           BLOWUP1

DESCRIPTION    'Blow-Up Mouse Demo Program (c) Charles Petzold, 1990'
EXETYPE        WINDOWS
STUB           'WINSTUB.EXE'
CODE           PRELOAD MOVEABLE DISCARDABLE
DATA           PRELOAD MOVEABLE MULTIPLE
HEAPSIZE       1024
STACKSIZE      8192
EXPORTS        WndProc

The job of stretching and compressing bitmapped images may seem complex, but
it's simplified for us by a Windows GDI function called StretchBlt. (The
abbreviation Blt is pronounced "blit." The function is related to the
Windows BitBlt function, which stands for "bit-block transfer." These
functions are discussed in more detail in Chapter 13.)

Here's how to use BLOWUP1:

  1.  Click the mouse in BLOWUP1's client area. The mouse cursor changes to
      a cross hair.

  2.  Position the mouse cursor over the upper left corner of the area of
      the screen you want to transfer.

  3.  Press the mouse button, drag the mouse to the lower right corner, and
      release the mouse button. The mouse cursor changes to an hourglass.

  4.  In a few seconds (or perhaps a little longer), the area that you
      blocked out is copied to BLOWUP1's client area, compressed or expanded
      appropriately.

If you block out a rectangle by moving from the upper right corner to the
lower left corner, BLOWUP1 displays a mirror image. If you move from the
lower left to the upper right, BLOWUP1 displays an upside-down image. And if
you move from lower right to upper left, the program combines the two
effects.

BLOWUP1 does not retain the captured image, and doesn't process the WM_PAINT
message. If you change the size of the window, the window will be erased.

  (Figure 4-10. may be found in the printed book.)


Changing the Mouse Cursor Shape

BLOWUP1 uses the SetCursor calls to change the cursor shape from an arrow to
a cross hair, then to an hourglass, and back to an arrow. All these are
stock cursors available in Windows. You obtain the handle to the cursor
using the LoadCursor function. In Chapter 8 we'll use LoadCursor to display
customized mouse cursors.

Many applications display an hourglass cursor (defined in WINDOWS.H as
IDC_WAIT) while doing processing that may take some time to complete. This
is fairly simple to implement. You can save the handle to the original
cursor by storing the return value from SetCursor and using it to reset the
cursor later. First, you'll need a variable of type HCURSOR (defined in
WINDOWS.H as a HANDLE, or 16-bit WORD) to store that value:

HCURSOR hCursor ;

Right before you start the lengthy processing, use the following two lines:

hCursor = SetCursor (LoadCursor (NULL, IDC_WAIT)) ;
ShowCursor (TRUE) ;

After you're done with the work, call:

ShowCursor (FALSE) ;
SetCursor (hCursor) ;

The two ShowCursor calls display and then hide the hourglass cursor if a
mouse is not actually present.

Normally, Windows changes the mouse cursor to the cursor included in the
window class structure whenever the window procedure receives a WM_MOUSEMOVE
message. If you use SetCursor to change the mouse cursor and then exit the
window procedure, the mouse cursor will be restored to the cursor in the
window class structure the next time the mouse is moved. This does not
happen in BLOWUP1, because the mouse is captured during the time the
cross-hair cursor (IDC_CROSS) is displayed. Windows will not change the
cursor to the window class cursor when the mouse is captured. Also, if you
need to display an hourglass cursor when doing some lengthy work (as BLOWUP1
does when it calls StretchBlt), you don't have to worry about the problem,
because you're not receiving any other messages during that time.

But if you want to use different mouse cursors for other purposes, you
should define a NULL cursor handle in your window class:

wndclass.hCursor = NULL ;

In your window function, you then call SetCursor for each WM_MOUSEMOVE
message. The SetCursor call is fast if the mouse cursor is not being
changed.


The StretchBlt Call

BLOWUP1 calls the StretchBlt function during processing of the WM_LBUTTONUP
message to transfer the blocked-out image to BLOWUP1's client area:

StretchBlt (hdc, 0, 0, rect.right, rect.bottom,
            hdc, ptBeg.x, ptBeg.y,
            ptEnd.x - ptBeg.x, ptEnd.y - ptBeg.y,
            SRCCOPY) ;

This function is discussed in more detail in Chapter 13, but let's take a
quick look at it here.

StretchBlt transfers a bitmapped image from a source to a destination. The
first five parameters are for the destination of the image, defining the
device context handle, the  x- and y-coordinates of the upper left corner,
and the width and height. The next five parameters give the same information
for the source of the image. The last parameter is the operation, which in
this case is a simple copy from source to destination.

The source of the image involves two POINT structures named ptBeg (beginning
of the block) and ptEnd (end of the block). These two points have negative
coordinate values if you block out an image to the left of or above the
client area. StretchBlt can read a bitmapped image that falls outside the
client area, but like all GDI functions it cannot write outside the client
area.


Drawing the Capture Block

But wait. When you use BLOWUP1 to block out an image outside its client
area, the program briefly displays the image in reverse video and then
restores it to normal. BLOWUP1 is apparently writing outside its client
area. Can that be so?

It certainly can. This little trick is carried off in BLOWUP1's InvertBlock
function. Rather than obtain a device context handle from GetDC, InvertBlock
uses CreateDC:

hdc = CreateDC ("DISPLAY", NULL, NULL, NULL) ;

This returns a device context handle for the entire display. Using this
device context handle, you can write outside your client area.

InvertBlock uses the GDI PatBlt function (a "pattern bit-block transfer") to
invert the blocked-out image. BLOWUP1 calls InvertBlock twice in succession.
When called the second time, the block is restored to normal. This means
that you can see the block briefly only when you move the mouse cursor.

Why do it like this? BLOWUP1 doesn't leave the block in an inverted state
because other Windows programs can receive messages between BLOWUP1's
WM_MOUSEMOVE messages. (For instance, the Windows CLOCK gets WM_TIMER
messages every second.) If BLOWUP1 left the block inverted when it exited
its window function, then the program with the altered client area could
write over the inverted block. When BLOWUP1 then reinverted the block--that
is, returned it to normal--the result would start looking like a mess. Keep
considerations like this in mind when you start working with powerful
functions like CreateDC. Windows gives you the power to do almost anything,
but your programs must share resources such as the display with other
programs. Try to exercise a little restraint.