Chapter 6  Child Window Controls
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ

Chapter 4 showed programs in the CHECKER series that display a grid of
rectangles. When you click the mouse in a rectangle, the program draws an X.
When you click again, the X disappears. As you played with the program, you
may have thought that the rectangle with the X inside looked vaguely
familiar. If the rectangle were reduced in size, it would resemble a "check
box" that Windows programs use in dialog boxes to allow the selection of
options.

Although the CHECKER1 and CHECKER2 versions of this program use only one
main window, the CHECKER3 version uses a child window for each rectangle.
The rectangles are maintained by a separate window procedure called
ChildWndProc. If we wanted to, we could add a facility to ChildWndProc to
send a message to its parent window procedure (WndProc) whenever a rectangle
is checked or unchecked.

Here's how: The child window procedure can determine the window handle of
its parent by calling GetParent:

hwndParent = GetParent (hwnd) ;

where hwnd is the window handle of the child window. It can then send a
message to the parent window procedure:

SendMessage (hwndParent, message, wParam, lParam) ;

Perhaps for this message the child window could set wParam to its child
window ID. The lParam could be set to a 1 if the child window were being
checked and a 0 if it were being unchecked.

This in effect creates a "child window control." The child window processes
mouse and keyboard messages and notifies the parent window when the child
window's state has changed. In this way, the child window becomes an input
device for the parent window.

Although you can create your own child window controls, you can also take
advantage of several predefined window classes (and window procedures) that
your program can use to create child window controls. These controls take
the form of buttons, check boxes, edit boxes, list boxes, combo boxes, text
strings, and scroll bars. For instance, if you want to put a button labeled
"Recalculate" in a corner of your spreadsheet program, you can create it
with a single CreateWindow call. You don't have to worry about the mouse
logic or button painting logic or about making the button "flash" when it's
clicked. That's all done in Windows. All you have to do is trap WM_COMMAND
messages--that's how the button informs your window procedure when it has
been triggered.

Is it really that simple? Well, almost.

Child window controls are used most often in dialog boxes. As you'll see in
Chapter 10, the position and size of the child window controls are defined
in a dialog box template contained in the program's resource script.
However, you can also use predefined child window controls on the surface of
a normal overlapped window's client area. You create each child window with
a CreateWindow call and adjust the position and size of the child windows
with calls to MoveWindow. The parent window procedure sends messages to the
child window controls, and the child window controls send messages back to
the parent window procedure.

When you bring up your normal window, you first define a window class and
register it with Windows using RegisterClass. You then create the window
based on that class using CreateWindow. When you use one of the predefined
controls, however, you do not register a window class for the child window.
The class already exists within Windows and has one of these names:
"button," "static," "scrollbar," "edit," "listbox," or "combobox." You
simply use the name as the window class parameter in CreateWindow. The
window style parameter to CreateWindow defines more precisely the appearance
and functionality of the child window control. Windows contains the window
procedures that process messages to the child windows based on these
classes.

Using child window controls directly on the surface of your window involves
tasks of a lower level than are required for using child window controls in
dialog boxes, where the dialog box manager adds a layer of insulation
between your program and the controls themselves. In particular, you'll
discover that the child window controls you create on the surface of your
window have no built-in facility to move the input focus from one control to
another using the Tab or cursor movement keys. A child window control can
obtain the input focus, but once it does, it won't relinquish the input
focus back to the parent window. This is a problem we'll struggle with
throughout this chapter.

THE BUTTON CLASS

We'll begin our exploration of the button window class with a program called
BTNLOOK ("button look"), which is shown in Figure 6-1. BTNLOOK creates 11
child window button controls, one for each of the 11 styles of buttons.

 BTNLOOK.MAK

#-----------------------
# BTNLOOK.MAK make file
#-----------------------

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

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

 BTNLOOK.C

/*----------------------------------------
   BTNLOOK.C -- Button Look Program
                (c) Charles Petzold, 1990
  ----------------------------------------*/

#include 
#include 

struct
     {
     long style ;
     char *text ;
     }
     button[] =
     {
     BS_PUSHBUTTON,      "PUSHBUTTON",
     BS_DEFPUSHBUTTON,   "DEFPUSHBUTTON",
     BS_CHECKBOX,        "CHECKBOX",
     BS_AUTOCHECKBOX,    "AUTOCHECKBOX",
     BS_RADIOBUTTON,     "RADIOBUTTON",
     BS_3STATE,          "3STATE",
     BS_AUTO3STATE,      "AUTO3STATE",
     BS_GROUPBOX,        "GROUPBOX",



     BS_USERBUTTON,      "USERBUTTON",
     BS_AUTORADIOBUTTON, "AUTORADIO",
     BS_PUSHBOX,         "PUSHBOX"
     } ;

#define NUM (sizeof button / sizeof button [0])

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

int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance,
                    LPSTR lpszCmdLine, int nCmdShow)
     {
     static char szAppName[] = "BtnLook" ;
     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, "Button Look",
                          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 char  szPrm []    = "wParam       LOWORD(lParam)
HIWORD(lParam)",
                  szTop []    = "Control ID   Window Handle   Notification",
                  szUnd []    = "__________   _____________   ____________",
                  szFormat [] = " %5u           %4X          %5u",
                  szBuffer [50] ;
     static HWND  hwndButton [NUM] ;
     static RECT  rect ;
     static int   cxChar, cyChar ;
     HDC          hdc ;
     PAINTSTRUCT  ps ;
     int          i ;
     TEXTMETRIC   tm ;

     switch (message)
          {
          case WM_CREATE :
               hdc = GetDC (hwnd) ;
               SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ;
               GetTextMetrics (hdc, &tm) ;
               cxChar = tm.tmAveCharWidth ;
               cyChar = tm.tmHeight + tm.tmExternalLeading ;
               ReleaseDC (hwnd, hdc) ;

               for (i = 0 ; i < NUM ; i++)
                    hwndButton [i] = CreateWindow ("button", button[i].text,
                              WS_CHILD | WS_VISIBLE | button[i].style,
                              cxChar, cyChar * (1 + 2 * i),
                              20 * cxChar, 7 * cyChar / 4,
                              hwnd, i,
                              ((LPCREATESTRUCT) lParam) -> hInstance, NULL)
;
               return 0 ;

          case WM_SIZE :
               rect.left   = 24 * cxChar ;
               rect.top    =  3 * cyChar ;
               rect.right  = LOWORD (lParam) ;
               rect.bottom = HIWORD (lParam) ;
               return 0 ;

          case WM_PAINT :
               InvalidateRect (hwnd, &rect, TRUE) ;

               hdc = BeginPaint (hwnd, &ps) ;
               SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ;
               SetBkMode (hdc, TRANSPARENT) ;
               TextOut (hdc, 24 * cxChar, 1 * cyChar, szPrm, sizeof szPrm -
1) ;
               TextOut (hdc, 24 * cxChar, 2 * cyChar, szTop, sizeof szTop -
1) ;
               TextOut (hdc, 24 * cxChar, 2 * cyChar, szUnd, sizeof szUnd -
1) ;

               EndPaint (hwnd, &ps) ;
               return 0 ;

          case WM_COMMAND :
               ScrollWindow (hwnd, 0, -cyChar, &rect, &rect) ;
               hdc = GetDC (hwnd) ;
               SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ;

               TextOut (hdc, 24 * cxChar, cyChar * (rect.bottom / cyChar -
1),
                        szBuffer, sprintf (szBuffer, szFormat, wParam,
                        LOWORD (lParam), HIWORD (lParam))) ;

               ReleaseDC (hwnd, hdc) ;
               ValidateRect (hwnd, NULL) ;
               return 0 ;

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

 BTNLOOK.DEF

;------------------------------------
; BTNLOOK.DEF module definition file
;------------------------------------

NAME           BTNLOOK

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

As you click on each button, it sends a WM_COMMAND message to the parent
window procedure, which is the familiar WndProc. BTNLOOK's WndProc displays
the wParam and lParam parameters of this message on the right half of the
client area, as shown in Figure 6-2.

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

Creating the Child Windows

BTNLOOK defines a structure called button that contains button window styles
and descriptive text strings for each of the 11 types of buttons. The button
window styles all begin with the letters BS, which stand for "button style."

The 11 button child windows are created in a for loop during WM_CREATE
message processing in WndProc. The CreateWindow call uses the following
parameters:


ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Class name           "button"
Window text          button[i].text
Window style         WS_CHILD | WS_VISIBLE | button[i].style
x position           cxChar
y position           cyChar * (1 + 2 * i)
Width                20 * xChar
Height               7 * yChar / 4
Parent window        hwnd


ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Child window ID        i
Instance handle        ((LPCREATESTRUCT) lParam) -> hInstance
Extra parameters       NULL

The class name parameter is the predefined name. The window style uses WS-
_CHILD, WS_VISIBLE, and one of the eleven button styles (BS_PUSHBUTTON, BS-
_DEFPUSHBUTTON, and so forth) in the button structure. The window text
parameter (which for a normal window is the text that appears in the caption
bar) is text that will be displayed with each button. I've simply used text
that identifies the button style.

The x position and y position parameters indicate the placement of the upper
left corner of the child window relative to the upper left corner of the
parent window's client area. The width and height parameters specify the
width and height of each child window.

The child window ID parameter should be unique for each child window. This
ID helps your window procedure identify the child window when processing
WM_COMMAND messages from it.

The instance handle parameter of the CreateWindow call looks a little
strange, but we're taking advantage of the fact that during a WM_CREATE
message lParam is actually a pointer to a structure of type CREATESTRUCT
("creation structure") that has a member hInstance. So we cast lParam into a
long (or far) pointer to a CREATESTRUCT structure and get hInstance out.

(Some Window programs use a global variable named hInst to give window
procedures access to the instance handle available in WinMain. In WinMain,
you need simply set:

hInst = hInstance ;

before creating the main window. In Chapter 4 we used GetWindowWord to
obtain the instance handle:

GetWindowWord (hwnd, GWW_HINSTANCE)

Any of these methods is fine.)

After the CreateWindow call, we don't have to do anything more with these
child windows. The button window procedure within Windows maintains them for
us and handles all repainting jobs. (The exception is the button with the
BS_USERBUTTON style; as I'll discuss shortly, this button style requires the
program to draw the button.) At the program's termination, Windows destroys
these child windows when the parent window is destroyed.


The Child Talks to Its Parent

When you run BTNLOOK, you see the different button types displayed on the
left side of the client area. (The BS_USERBUTTON button is not visible.) As
I mentioned earlier, when you click a button with the mouse, the child
window control sends a WM_COMMAND message to its parent window. BTNLOOK
traps the WM_COMMAND message and displays the values of wParam and lParam.
Here's what they mean:

ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
wParam                       Child window ID
LOWORD (lParam)              Child window handle
HIWORD (lParam)              Notification code

The child window ID is the value passed to CreateWindow when the child
window is created. In BTNLOOK these IDs are 0 through 10 for the 11 buttons
displayed in the client area. The child window handle is the value that
Windows returns from the CreateWindow call.

The notification code is a submessage code that the child window uses to
tell the parent window in more detail what the message means. The possible
values of button notification codes are defined in WINDOWS.H:

Button Notification
Code Identifier                      Value
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
BN_CLICKED                           0

BN_PAINT                             1

BN_HILITE                            2

BN_UNHILITE                          3

BN_DISABLE                           4

BN_DOUBLECLICKED                     5


For all button styles except BS_USERBUTTON, this notification code is always
BN_CLICKED, which simply tells the parent window that the button has been
clicked. The other notification codes are used for the BS_USERBUTTON style.

You'll notice that when you click a button with the mouse, a dashed line
surrounds the text of the button. This indicates that the button has the
input focus. All keyboard input now goes to the child window button control
rather than to the main window. However, when the button control has the
input focus, it ignores all keystrokes except the Spacebar, which now has
the same effect as a mouse click.


The Parent Talks to Its Child

Although BTNLOOK does not demonstrate this fact, a window procedure can also
send messages to the child window control. Five button-specific messages are
defined in WINDOWS.H; each begins with the letters "BM," which stand for
"button message." These messages are defined in WINDOWS.H in terms of the
WM_USER identifier:

#define BM_GETCHECK  (WM_USER+0)
#define BM_SETCHECK  (WM_USER+1)
#define BM_GETSTATE  (WM_USER+2)
#define BM_SETSTATE  (WM_USER+3)
#define BM_SETSTYLE  (WM_USER+4)

The WM_USER identifier is available for programs to define their own
messages beyond the predefined messages. Each window class can have its own
separate set of messages unique to that class. The other classes of
predefined child window controls also can have their own messages defined in
terms of WM_USER.

The BM_GETCHECK and BM_SETCHECK messages are sent by a parent window to a
child window control to get and set the check mark of check boxes and radio
buttons. The BM_GETSTATE and BM_SETSTATE messages refer to the normal or
"pushed" state of a window when you click it with the mouse or press it with
the Spacebar. We'll see how these messages work when we look at each type of
button. The BM_SETSTYLE message lets you change the button style after the
button is created.


Push Buttons

The first two buttons shown in BTNLOOK are "push" buttons. A push button is
a rectangle enclosing text specified in the window text parameter of the
CreateWindow call. The rectangle takes up the full height and width of the
dimensions given in the CreateWindow or MoveWindow call. The text is
centered within the rectangle.

Push-button controls are used mostly to trigger an immediate action without
retaining any type of on/off indication. The two types of push-button
controls have window styles called BS_PUSHBUTTON and BS_DEFPUSHBUTTON. The
"DEF" in BS_DEFPUSHBUTTON stands for "default." When used to design dialog
boxes, BS_PUSHBUTTON controls and BS_DEFPUSHBUTTON controls function
differently from one another. When used as child window controls, however,
the two types of push buttons function the same way, although
BS_DEFPUSHBUTTON has a heavier outline.

A push button looks best when its height is 7/4 times the height of a
SYSTEM_FONT character, which is what BTNLOOK uses. The push button's width
must accommodate at least the width of the text plus two additional
characters.

When the mouse cursor is inside the push button, pressing the mouse button
causes the button to repaint itself using 3D-style shading to appear as if
it's been depressed. Releasing the mouse button restores the original
appearance and sends a WM_COMMAND message to the parent window with
notification code BN_CLICKED. As with the other button types, when a push
button has the input focus, a dashed line surrounds the text, and pressing
and releasing the Spacebar has the same effect as pressing and releasing the
mouse button.

You can simulate a push-button flash by sending the window a BM_SETSTATE
message. This causes the button to be depressed:

SendMessage (hwndButton, BM_SETSTATE, 1, 0L) ;

This call causes the button to return to normal:

SendMessage (hwndButton, BM_SETSTATE, 0, 0L) ;

The hwndButton window handle is the value returned from the CreateWindow
call.

You can also send a BM_GETSTATE message to a push button. The child window
control returns the current state of the button--TRUE if the button is
depressed and FALSE (or 0) if normal. Most applications do not require this
information, however. And because push buttons do not retain any on/off
information, the BM_SETCHECK and BM_GETCHECK messages are not used.

Buttons created with the BS_PUSHBOX style are displayed only when the button
has the input focus. This style of button is rarely used by Windows
applications.


Check Boxes

A check box is a square box with text; the text usually appears to the right
of the check box. (If you include the BS_LEFTTEXT style when creating the
button, the text appears to the left.) Check boxes are usually incorporated
in an application to allow a user to select options. The check box commonly
functions as a toggle switch: Clicking the box once causes an X to appear;
clicking again toggles the X off.

The two most common styles for a check box are BS_CHECKBOX and
BS_AUTOCHECKBOX. When you use the BS_CHECKBOX style, you must set the X mark
yourself by sending the control a BM_SETCHECK message. The wParam parameter
is set to 1 to create an X and to 0 to remove it. You can obtain the current
check state of the box by sending the control a BM_GETCHECK message. You
might use code like this to toggle the X mark when processing a WM_COMMAND
message from the control:

SendMessage (LOWORD (lParam), BM_SETCHECK, (WORD)
          !SendMessage (LOWORD (lParam), BM_GETCHECK, 0, 0L), 0L) ;

Note the ! operator in front of the second SendMessage call. The low word of
lParam is the child window handle passed to your window procedure in the
WM_COMMAND message. When you later need to know the state of the button,
send it another BM_GETCHECK message. Or you can retain the current check
state in a static variable in your window procedure. You can also initialize
a BS_CHECKBOX check box with an X by sending it a BM_SETCHECK message:

SendMessage (hwndButton, BM_SETCHECK, 1, 0L) ;

For the BS_AUTOCHECKBOX style, the button control itself toggles the X on
and off. Your window procedure can ignore WM_COMMAND messages. When you need
the current state of the button, send the control a BM_GETCHECK message:

nCheck = (WORD) SendMessage (hwndButton, BM_GETCHECK, 0, 0L) ;

The value of nCheck is TRUE or nonzero if the button is checked, FALSE or
zero if not.

The other two check box styles are BS_3STATE and BS_AUTO3STATE. As their
names indicate, these styles can display a third state as well--a gray color
within the check box--which occurs when you send the control a WM_SETCHECK
message with wParam equal to 2. The gray color indicates to the user that
the box cannot be checked_ that is, that it's disabled. However, the check
box control continues to send messages to the parent when the box is
clicked. Better methods for disabling a check box are described later.

The check box is aligned with the rectangle's left edge and is centered
within the top and bottom dimensions of the rectangle that were specified
during the CreateWindow call. Clicking anywhere within the rectangle causes
a WM_COMMAND message to be sent to the parent. The minimum height for a
check box is one character height. The minimum width is the number of
characters in the text plus two.


Radio Buttons

A radio button looks very much like a check box except that it is shaped
like a circle rather than a box. A heavy dot within the circle indicates
that the radio button has been checked. The radio button has the window
style BS_RADIOBUTTON or BS_AUTORADIOBUTTON, but the latter is used only in
dialog boxes.

In dialog boxes, groups of radio buttons are conventionally used to indicate
mutually exclusive options. (For instance, look at the dialog box in the
Windows Terminal program that appears when you select Communications from
the Settings menu.) Unlike check boxes, radio buttons do not work as
toggles--that is, when you click a radio button a second time, its state
remains unchanged.

When you receive a WM_COMMAND message from a radio button, you should
display its check by sending it a BM_SETCHECK message with wParam equal to
1:

SendMessage (hwndButton, BM_SETCHECK, 1, 0L) ;

For all other radio buttons in the same group, you can turn off the checks
by sending them BM_SETCHECK messages with wParam equal to 0:

SendMessage (hwndButton, BM_SETCHECK, 0, 0L) ;


Group Boxes

The group box, style BS_GROUPBOX, is an oddity in the button class. It
neither processes mouse or keyboard input nor sends WM_COMMAND messages to
its parent. The group  box is a rectangular outline with its window text at
the top. Group boxes are often used to enclose other button controls.


User-Defined Buttons

The user-defined button, which has the style BS_USERBUTTON, is the only
button that sends WM_COMMAND messages to its parent with these notification
codes:

ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
BN_PAINT                 Button is normal
BN_HILITE                Button is being clicked
BN_UNHILITE              Clicking is finished
BN_DISABLE               Button is disabled

These notification codes indicate that the window must be painted. The
parent window is responsible for this painting. It can use the low word of
lParam to obtain the window handle of the button, GetClientRect to determine
the button's dimensions, and GetDC to get the button's device context in
preparation for painting. BTNLOOK doesn't process these notification codes,
so only a dotted outline appears when the button has the input focus.


Changing the Button Text

You can change the text in a button (or in any other window) by calling
SetWindowText:

SetWindowText (hwnd, lpszString) ;

where hwnd is a handle to the window whose text is being changed and
lpszString is a long (or far) pointer to a null-terminated string. For a
normal window, this text is the text of the caption bar. For a button
control, it's the text displayed with the button.

You can also obtain the current text of a window:

nLength = GetWindowText (hwnd, lpszBuffer, nMaxLength) ;

The nMaxLength parameter specifies the maximum number of characters to copy
into the buffer pointed to by lpszBuffer. The function returns the string
length copied. You can prepare your program for a particular text length by
first calling:

nLength = GetWindowTextLength (hwnd) ;


Visible and Enabled Buttons

To receive mouse and keyboard input, a child window must be both visible
(displayed) and enabled. When a child window is visible but not enabled,
Windows displays it in gray rather than black.

If you do not include WS_VISIBLE in the window class when creating the child
window, the child window will not be displayed until you make a call to
ShowWindow:

ShowWindow (hwndChild, SW_SHOWNORMAL) ;

If you include WS_VISIBLE in the window class, you do not need to call
ShowWindow. However, you can hide the child window by a call to ShowWindow:

ShowWindow (hwndChild, SW_HIDE) ;

You can determine if a child window is visible by a call to:

IsWindowVisible (hwndChild) ;

You can also enable and disable a child window. By default, a window is
enabled. You can disable it by calling:

EnableWindow (hwndChild, FALSE) ;

For button controls, this has the effect of graying the button text string.
The button no longer responds to mouse or keyboard input. This is the best
method for indicating that a button option is currently unavailable.

You can reenable a child window by calling:

EnableWindow (hwndChild, TRUE) ;

You can determine whether a child window is enabled by calling:

IsWindowEnabled (hwndChild) ;


Buttons and Input Focus

As I noted earlier in this chapter, push buttons, check boxes, radio
buttons, and user-defined buttons receive the input focus when they are
clicked with the mouse. The control indicates it has the input focus by a
dashed line surrounding the text. When the child window control gets the
input focus, the parent window loses it; all keyboard input then goes to the
control rather than to the parent window. However, the child window control
responds only to the Spacebar, which now functions like the mouse. This
situation presents an obvious problem: Your program has lost control of
keyboard processing. Let's see what we can do about it.

When Windows switches the input focus from one window (such as a parent) to
another (such as a child window control), it first sends a WM_KILLFOCUS
message to the window losing the input focus. The wParam parameter is the
handle of the window that is to receive the input focus. Windows then sends
a WM_SETFOCUS message to the window receiving the input focus, with wParam
the handle of the window losing the input focus. (In both cases, wParam may
be NULL, which indicates that no window has or is receiving the input
focus.)

A parent window can prevent a child window control from getting the input
focus by processing WM_KILLFOCUS messages. Assume that the array hwndChild
contains the window handles of all child windows. (These were saved in the
array during the CreateWindow calls that created the windows.) NUM is the
number of child windows:

case WM_KILLFOCUS :
     for (i = 0 ; i < NUM ; i++)
          if (hwndChild [i] == wParam)
               {
               SetFocus (hwnd) ;
               break ;
               }
     return 0 ;

In this code, when the parent window detects that it's losing the input
focus to one of its child window controls, it calls SetFocus to restore the
input focus to itself.

Here's a simpler (but less obvious) way of doing it:

case WM_KILLFOCUS :
     if (hwnd == GetParent (wParam))
          SetFocus (hwnd) ;
     return 0 ;

Both these methods have a shortcoming, however: They prevent the button from
responding to the Spacebar, because the button never gets the input focus. A
better approach would be to let the button get the input focus but also to
include the facility for the user to move from button to button using the
Tab key. At first this sounds impossible, but I'll show you how to
accomplish it with a technique called "window subclassing" in the COLORS1
program shown later in this chapter.



CONTROLS AND COLORS

I deliberately put a little "gotcha" into BTNLOOK. There is something wrong
with the program. It may not be immediately apparent, but here's how to see
it: Run BTNLOOK and bring up the Control Panel program included with
Windows. Select the Colors icon; this brings up a dialog box that lets you
change system colors. Select Color Palette and change the colors of Window
Background and Window Text, and save the new settings by clicking the OK
button: The background and text of the buttons (with the exception of the
push buttons) in BTNLOOK changes to reflect the new colors, but the
background color and text color of the rest of BTNLOOK's client area remain
the same--black text on a white background. It looks dreadful.

What happened? Simple--the button colors change because they are based on
the system colors you set in Control Panel, but BTNLOOK's client-area
background remains white because white is specified in the window class:

wndclass.hbrBackground = GetStockObject (WHITE_BRUSH) ;

When BTNLOOK writes text to the display, it uses the text color and
background color defined in the default device context. These are always
black and white, regardless of the system colors set with Control Panel.

Let's fix this problem. I discussed Windows' use of color in Chapter 5, but
this problem involves Windows "system colors."

System Colors

Windows maintains 19 system colors for painting various parts of the
display. You can obtain and set these colors using GetSysColor and
SetSysColor. Identifiers defined in WINDOWS.H specify the system color.
Setting a system color with SetSysColor changes it only for the current
Windows session.

You can set system colors for future Windows sessions using the Windows
Control Panel program. You can also modify the [colors] section in the
WIN.INI file. The [colors] section uses keywords for the 19 system colors
(different from the GetSysColor and SetSysColor identifiers) followed by
red, green, and blue values that can range from 0 to 255. The following
table shows how the 19 system colors are identified using the WINDOWS.H
identifiers for GetSysColor and SetSysColor, the WIN.INI keywords, and the
Control Panel terms:


GetSysColor & SetSysColor  WIN.INI         Control Panel
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
COLOR_SCROLLBAR            Scrollbar       Scroll Bars
COLOR_BACKGROUND           Background      Desktop Background
COLOR_ACTIVECAPTION        ActiveTitle     Active Title Bar
COLOR_INACTIVECAPTION      InactiveTitle   Inactive Title Bar
COLOR_MENU                 Menu            Menu Bar
COLOR_WINDOW               Window          Window Background
COLOR_WINDOWFRAME          WindowFrame     Window Frame
COLOR_MENUTEXT             MenuText        Menu Text
COLOR_WINDOWTEXT           WindowText      Window Text
COLOR_CAPTIONTEXT          TitleText       Title Bar Text
COLOR_ACTIVEBORDER         ActiveBorder    Active Border
COLOR_INACTIVEBORDER       InactiveBorder  Inactive Border
COLOR_APPWORKSPACE         AppWorkspace    Application Workspace
COLOR_HIGHLIGHT            Highlight
COLOR_HIGHLIGHTTEXT        HighlightText
COLOR_BTNFACE              ButtonFace
COLOR_BTNSHADOW            ButtonShadow
COLOR_GRAYTEXT             GrayText
COLOR_BTNTEXT              ButtonText


Most of these are self-explanatory. COLOR_BACKGROUND is the color of the
desktop area behind all the windows. The COLOR_WINDOWFRAME color is the
color used for lines drawn between many of the sections of the display, such
as between a menu and a client area. The last six system colors cannot be
changed from the Control Panel: The two "Highlight" colors involve selected
options in menus and list boxes. The last four system colors determine the
colors used in push buttons.

Default values for these 19 colors are provided by the display driver.
Windows uses these default values unless they are overriden by the [colors]
section of WIN.INI.


The Button Colors

COLOR_WINDOW and COLOR_WINDOWTEXT are used by many windows to color
themselves. The button controls (with the exception of push buttons) use
COLOR_WINDOW to color the background behind the button. (For a group box,
COLOR_WINDOW is used only for the background behind the text.) The button
controls use COLOR_WINDOWTEXT for text, for the box in a check box control,
and for the round button in a radio-button control. The outline of push
buttons and group boxes is defined by using COLOR_WINDOWFRAME.

You can use one of two methods to make your main window and the child window
control consistent in their use of colors. The first method is to use system
colors for your main window. To begin, you use COLOR_WINDOW for the
background of your client area when defining the window class:

wndclass.hbrBackground = COLOR_WINDOW + 1 ;

(Windows requires that you add 1 when you use these identifiers in your
wndclass structure, but doing so has no profound purpose other than to
prevent the value from being 0.) But that causes another problem. When you
display text using TextOut, Windows uses values defined in the device
context for the text background color (which erases the background behind
the text) and the text color. The default values are white (background) and
black (text) regardless of both the system colors and the hbrBackground
field of the window class structure. So you need to use SetTextColor and
SetBkColor to change your text and text background colors to the system
colors. You do this after you obtain the handle to a device context:

SetBkColor (hdc, GetSysColor (COLOR_WINDOW)) ;
SetTextColor (hdc, GetSysColor (COLOR_WINDOWTEXT)) ;

Now the client-area background, text background, and text color are all
consistent with button colors. That's the first method.

The second method is to force the child window controls to use the colors
you want to use. This method is a little more involved; it requires
processing WM_CTLCOLOR messages.


The WM_CTLCOLOR Messages

WM_CTLCOLOR is a message that a predefined child window control sends to its
parent window procedure when the child window is about to paint its client
area. The parent window can use this opportunity to alter the colors that
the child window procedure will use for painting.

When the parent window procedure receives a WM_CTLCOLOR message, the wParam
and lParam parameters have the following meaning:

ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
wParam                Handle to child window's device context
LOWORD (lParam)       Handle to child window
HIWORD (lParam)       Type of window

The high word of lParam can be one of the following:

HIWORD (lParam)                Type of Window
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
CTLCOLOR_MSGBOX                Message box
CTLCOLOR_EDIT                  Edit control
CTLCOLOR_LISTBOX               List box control
CTLCOLOR_BTN                   Button control
CTLCOLOR_DLG                   Dialog box
CTLCOLOR_SCROLLBAR             Scroll bar control
CTLCOLOR_STATIC                Static control

Right now, we're interested in CTLCOLOR_BTN, the WM_CTLCOLOR message from a
button control. When the parent window procedure gets this message, the
child window control has already obtained its device context. The handle to
this device context is in wParam. Any GDI (Graphics Device Interface) calls
you make using this device context will affect the painting that the child
window does when you pass control back to the child window.

You must perform three actions when processing a WM_CTLCOLOR message:

  þ   Set a text color using SetTextColor.

  þ   Set a background color using SetBkColor.

  þ   Return a handle to a brush to the child window.

A "brush" is a GDI object that defines a bitmapped pattern of pixels.
Windows uses brushes to fill areas with color. You can get a handle to a
brush using GetStockOb- ject, CreateSolidBrush, CreateHatchBrush, or
CreatePatternBrush. For processing the WM_CTLCOLOR message, you'll probably
use CreateSolidBrush. Before your program terminates, you must explicitly
delete any brushes you create. A good time to do this is while processing
the WM_DESTROY message.

For most child window controls, the color you set in SetBkColor should be
the same as the color of the brush you return from the WM_CTLCOLOR message.
For instance, button controls use the brush to color the background of the
entire child window client area. The text background color is used only for
the background behind the text. These two colors should be the same. To see
how this works, let's take an example of processing a WM- _CTLCOLOR message
for button controls where the window procedure simply sets the normal
default colors. During initialization (probably when processing a WM_CREATE
message), you can create a brush:

hBrush = CreateSolidBrush (GetSysColor (COLOR_WINDOW)) ;

The hBrush brush handle should be stored in a static variable. Here's what
the WM- _CTLCOLOR processing looks like:

case WM_CTLCOLOR :
     if (HIWORD (lParam) == CTLCOLOR_BTN)
          {
          SetBkColor (wParam, GetSysColor (COLOR_WINDOW)) ;
          SetTextColor (wParam, GetSysColor (COLOR_WINDOWTEXT)) ;

          UnrealizeObject (hBrush) ;
          point.x = point.y = 0 ;
          ClientToScreen (hwnd, &point) ;
          SetBrushOrg (wParam, point.x, point.y) ;

          return ((DWORD) hBrush) ;
          }
     break ;

Note that wParam is the device context handle of the button control. The
four statements that culminate in the SetBrushOrg call require some further
explanation.

As noted earlier, a brush defines a bitmapped pattern of pixels. When
Windows uses this brush to fill an area with color, the pattern of the brush
is repeated horizontally and vertically until the area is filled. The origin
of this brush--the place where Windows assumes the repeating pattern
begins--is the upper left corner of the client area associated with the
device context.

But if you color both the client area of a parent window and the client area
of a child window with this same brush, the pattern won't merge correctly at
the edge of the child window because Windows is using two different origins
for the same brush. To avoid this problem, you call UnrealizeObject. This
function causes Windows to reset the origin of the brush the next time it is
selected into a device context (which will follow the return from the
WM_CTLCOLOR processing). The origin Windows will use is the one you set with
SetBrushOrg; in this example, the function sets the brush origin to the
screen origin of the parent window. (Don't use UnrealizeObject for a stock
brush handle that you obtain from GetStockObject, and don't worry if this
sounds a bit obscure right now. We'll cover the issues in more depth in
Chapter 12.)

The brush we created in our example is based on the system color
COLOR_WINDOW. If this color changes while the program is running, the window
procedure receives a WM_SYSCOLORCHANGE message. The program deletes the
brush and creates a new one:

case WM_SYSCOLORCHANGE :
     DeleteObject (hBrush) ;
     hBrush = CreateSolidBrush (GetSysColor (COLOR_WINDOW)) ;
     return 0 ;

Finally, when the program is about to terminate, the brush should be
deleted:

case WM_DESTROY :
     DeleteObject (hBrush) ;
     PostQuitMessage (0) ;
     return 0 ;

I've shown here how you can reproduce the default processing of WM_CTLCOLOR
messages for button controls. Using your own colors is much the same. You
would not need to trap WM_SYSCOLORCHANGE messages unless you wanted to base
the brush on a system color. We'll come back to WM_CTLCOLOR messages later
in this chapter, when we use the COLORS1 program. For now, let's explore
another class of child window controls.



THE STATIC CLASS

You create a static child window control using "static" as the window class
in the CreateWindow function. These are fairly benign child windows. They do
not accept mouse or keyboard input, and they do not send WM_COMMAND messages
back to the parent window. (When you move or click the mouse over a static
child window, the child window traps the WM_NCHITTEST message and returns a
value of HTTRANSPARENT to Windows. This causes Windows to send the same
WM_NCHITTEST message to the underlying window, which is usually the parent.
The parent usually passes the message to DefWindowProc, where it is
converted into a client-area mouse message.)

The first six static window styles simply draw a rectangle or a frame in the
client area of the child window. The three "RECT" static styles (left column
below) are filled-in rectangles; the three "FRAME" styles (right column) are
rectangular outlines that are not filled in:

ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
SS_BLACKRECT                 SS_BLACKFRAME
SS_GRAYRECT                  SS_GRAYFRAME
SS_WHITERECT                 SS_WHITEFRAME

"BLACK," "GRAY," and "WHITE" do not mean the colors are black, gray, and
white. Rather, the colors are based on system colors as shown here:

Static Control               System Color
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
BLACK                        COLOR_WINDOWFRAME
GRAY                         COLOR_BACKGROUND
WHITE                        COLOR_WINDOW

Most display drivers define default settings of black for COLOR_WINDOWFRAME
and white for COLOR_WINDOW. (Of course, a user can change any of these
colors using the Control Panel program in Windows.) The colors used in the
"RECT" and "FRAME" static styles cannot be changed by trapping WM_CTLCOLOR
messages. The window text field of the CreateWindow call is ignored for
these styles. The upper left corner of the rectangle begins at the x
position and y position coordinates relative to the parent window.

The static class also includes three text styles: SS_LEFT, SS_RIGHT, and
SS_CENTER. These create left-justified, right-justified, and centered text.
The text is given in the window text parameter of the CreateWindow call, and
it can be changed later using SetWindowText. When the window procedure for
static controls displays this text, it uses the DrawText function with
DT_WORDBREAK, DT_NOCLIP, and DT_EXPANDTABS parameters. The text is
wordwrapped within the rectangle of the child window. The background of
these three text-style child windows is normally COLOR_WINDOW, and the text
itself is COLOR_WINDOWTEXT. When you intercept WM_CTLCOLOR messages, you can
change the text color by calling SetTextColor and the background color by
calling SetBkColor and by returning the handle to the background brush.

Finally, the static class also includes the window styles SS_ICON and
SS_USERITEM. However, these have no meaning when used as child window
controls. We'll look at them again when discussing dialog boxes.


THE SCROLLBAR CLASS

When the subject of scroll bars first came up in Chapter 2 while I was
designing the SYSMETS series of programs, I discussed some of the
differences between "window scroll bars" and "scroll bar controls." SYSMETS
uses window scroll bars, which appear at the right and bottom of the window.
You add window scroll bars to a window by including the identifier
WS_VSCROLL or WS_HSCROLL or both in the window style when creating the
window. Now we're ready to make some scroll bar controls, which are child
windows that can appear anywhere in the client area of the parent window.
You create child window scroll bar controls by using the predefined window
class "scrollbar" and one of the two scroll bar styles SBS_VERT and
SBS_HORZ.

Unlike the button controls (and the edit and list box controls to be
discussed later), scroll bar controls do not send WM_COMMAND messages to the
parent window. Instead, they send WM_VSCROLL and WM_HSCROLL messages, just
like window scroll bars. When  processing the scroll bar messages, you can
differentiate between window scroll bars and scroll bar controls by the high
word of the lParam parameter:

Scroll Bar Type              HIWORD (lParam)
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Window scroll bar            0
Scroll bar control           Window handle of control

The wParam parameter and the low word of lParam have the same meaning for
window scroll bars and scroll bar controls.

Although window scroll bars have a fixed width, Windows uses the full
rectangle dimensions given in the CreateWindow call (or later in the
MoveWindow call) to size scroll bar controls. You can make long, thin scroll
bar controls or short, pudgy scroll bar controls. If you want to create
scroll bar controls that have the same dimensions as window scroll bars, you
can use GetSystemMetrics to obtain the height of a horizontal scroll bar:

GetSystemMetrics (SM_CYHSCROLL) ;

or the width of a vertical scroll bar:

GetSystemMetrics (SM_CXVSCROLL) ;

(The scroll bar window style identifiers SBS_LEFTALIGN, SBS_RIGHTALIGN,
SBS_TOPALIGN, and SBS_BOTTOMALIGN are documented to give standard dimensions
to scroll bars. However, these styles work only for scroll bars in dialog
boxes.)

You can set the range and position of a scroll bar control with the same
calls used for window scroll bars:

SetScrollRange (hwndScroll, SB_CTL, nMin, nMax, bRedraw) ;
SetScrollPos (hwndScroll, SB_CTL, nPos, bRedraw) ;

The difference is that window scroll bars use a handle to the parent window
as the first parameter and SB_VERT or SB_HORZ as the second parameter.

The interior bar of the scroll bar is COLOR_SCROLLBAR. The thumb and arrow
colors are based on the push button colors. If you trap WM_CTLCOLOR
messages, you can return a brush from the message to override this color.
Let's do it.

The COLORS1 Program

To see some uses of scroll bars and static child windows--and also to
explore color in more depth--we'll use the COLORS1 program, shown in Figure
6-3. COLORS1 displays three scroll bars in the left half of the client area
labeled "Red," "Green," and "Blue." As you scroll the scroll bars, the right
half of the client area changes to the composite color indicated by the mix
of the three primary colors. The numeric values of the three primary colors
are displayed under the three scroll bars.

 COLORS1.MAK

#-----------------------
# COLORS1.MAK make file
#-----------------------

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

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

 COLORS1.C

/*----------------------------------------
   COLORS1.C -- Colors Using Scroll Bars
                (c) Charles Petzold, 1990
  ----------------------------------------*/

#include 
#include 

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

FARPROC lpfnOldScr[3] ;
HWND    hwndScrol[3], hwndLabel[3], hwndValue[3], hwndRect ;
short   color[3], nFocus ;

int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance,
                    LPSTR lpszCmdLine, int nCmdShow)
     {
     static char szAppName[] = "Colors1" ;
     static char *szColorLabel[] = { "Red", "Green", "Blue" } ;
     FARPROC     lpfnScrollProc ;
     HWND        hwnd ;
     MSG         msg ;
     short       n ;
     WNDCLASS    wndclass ;

     if (hPrevInstance)
          return FALSE ;



     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 = CreateSolidBrush (0L) ;
     wndclass.lpszMenuName  = NULL ;
     wndclass.lpszClassName = szAppName ;

     RegisterClass (&wndclass) ;

     hwnd = CreateWindow (szAppName, "Color Scroll",
                          WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          NULL, NULL, hInstance, NULL) ;

     hwndRect = CreateWindow ("static", NULL,
                              WS_CHILD | WS_VISIBLE | SS_WHITERECT,
                              0, 0, 0, 0,
                              hwnd, 9, hInstance, NULL) ;

     lpfnScrollProc = MakeProcInstance ((FARPROC) ScrollProc, hInstance) ;

     for (n = 0 ; n < 3 ; n++)
          {
          hwndScrol[n] = CreateWindow ("scrollbar", NULL,
                              WS_CHILD | WS_VISIBLE | WS_TABSTOP | SBS_VERT,
                              0, 0, 0, 0,
                              hwnd, n, hInstance, NULL) ;

          hwndLabel[n] = CreateWindow ("static", szColorLabel[n],
                              WS_CHILD | WS_VISIBLE | SS_CENTER,
                              0, 0, 0, 0,
                              hwnd, n + 3, hInstance, NULL) ;

          hwndValue[n] = CreateWindow ("static", "0",
                              WS_CHILD | WS_VISIBLE | SS_CENTER,
                              0, 0, 0, 0,
                              hwnd, n + 6, hInstance, NULL) ;

          lpfnOldScr[n] = (FARPROC) GetWindowLong (hwndScrol[n],
GWL_WNDPROC) ;
          SetWindowLong (hwndScrol[n], GWL_WNDPROC, (LONG) lpfnScrollProc) ;
          SetScrollRange (hwndScrol[n], SB_CTL, 0, 255, FALSE) ;
          SetScrollPos   (hwndScrol[n], SB_CTL, 0, 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 HBRUSH hBrush[3] ;
     char          szbuffer[10] ;
     HDC           hdc ;
     POINT         point ;
     short         n, cxClient, cyClient, cyChar ;
     TEXTMETRIC    tm ;

     switch (message)
          {
          case WM_CREATE :
               hBrush[0] = CreateSolidBrush (RGB (255, 0, 0)) ;
               hBrush[1] = CreateSolidBrush (RGB (0, 255, 0)) ;
               hBrush[2] = CreateSolidBrush (RGB (0, 0, 255)) ;
               return 0 ;

          case WM_SIZE :
               cxClient = LOWORD (lParam) ;
               cyClient = HIWORD (lParam) ;

               hdc = GetDC (hwnd) ;
               GetTextMetrics (hdc, &tm) ;
               cyChar = tm.tmHeight ;
               ReleaseDC (hwnd, hdc) ;

               MoveWindow (hwndRect, 0, 0, cxClient / 2, cyClient, TRUE) ;

               for (n = 0 ; n < 3 ; n++)
                    {
                    MoveWindow (hwndScrol[n],
                         (2 * n + 1) * cxClient / 14, 2 * cyChar,
                         cxClient / 14, cyClient - 4 * cyChar, TRUE) ;
                    MoveWindow (hwndLabel[n],
                         (4 * n + 1) * cxClient / 28, cyChar / 2,
                         cxClient / 7, cyChar, TRUE) ;

                    MoveWindow (hwndValue[n],
                         (4 * n + 1) * cxClient / 28, cyClient - 3 * cyChar
/ 2,
                         cxClient / 7, cyChar, TRUE) ;
                    }
               SetFocus (hwnd) ;
               return 0 ;

          case WM_SETFOCUS :
               SetFocus (hwndScrol[nFocus]) ;
               return 0 ;

          case WM_VSCROLL :
               n = GetWindowWord (HIWORD (lParam), GWW_ID) ;

               switch (wParam)
                    {
                    case SB_PAGEDOWN :
                         color[n] += 15 ;         /* fall through */
                    case SB_LINEDOWN :
                         color[n] = min (255, color[n] + 1) ;
                         break ;
                    case SB_PAGEUP :
                         color[n] -= 15 ;         /* fall through */
                    case SB_LINEUP :
                         color[n] = max (0, color[n] - 1) ;
                         break ;
                    case SB_TOP :
                         color[n] = 0 ;
                         break ;
                    case SB_BOTTOM :
                         color[n] = 255 ;
                         break ;
                    case SB_THUMBPOSITION :
                    case SB_THUMBTRACK :
                         color[n] = LOWORD (lParam) ;
                         break ;
                    default :
                         break ;
                    }
               SetScrollPos  (hwndScrol[n], SB_CTL, color[n], TRUE) ;
               SetWindowText (hwndValue[n], itoa (color[n], szbuffer, 10)) ;
               DeleteObject (GetClassWord (hwnd, GCW_HBRBACKGROUND)) ;
               SetClassWord (hwnd, GCW_HBRBACKGROUND,
                    CreateSolidBrush (RGB (color[0], color[1], color[2]))) ;

               InvalidateRect (hwnd, NULL, TRUE) ;
               return 0 ;

          case WM_CTLCOLOR :
               if (HIWORD (lParam) == CTLCOLOR_SCROLLBAR)
                    {
                    SetBkColor (wParam, GetSysColor (COLOR_CAPTIONTEXT)) ;
                    SetTextColor (wParam, GetSysColor (COLOR_WINDOWFRAME)) ;

                    n = GetWindowWord (LOWORD (lParam), GWW_ID) ;
                    point.x = point.y = 0 ;
                    ClientToScreen (hwnd, &point) ;
                    UnrealizeObject (hBrush[n]) ;
                    SetBrushOrg (wParam, point.x, point.y) ;
                    return ((DWORD) hBrush[n]) ;
                    }
               break ;

          case WM_DESTROY :
               DeleteObject (GetClassWord (hwnd, GCW_HBRBACKGROUND)) ;
               for (n = 0 ; n < 3 ; DeleteObject (hBrush [n++])) ;
               PostQuitMessage (0) ;
               return 0 ;
          }
     return DefWindowProc (hwnd, message, wParam, lParam) ;
     }

long FAR PASCAL ScrollProc (HWND hwnd, WORD message, WORD wParam, LONG
lParam)
     {
     short n = GetWindowWord (hwnd, GWW_ID) ;

     switch (message)
          {
          case WM_KEYDOWN :
               if (wParam == VK_TAB)
                    SetFocus (hwndScrol[(n +
                         (GetKeyState (VK_SHIFT) < 0 ? 2 : 1)) % 3]) ;
               break ;

          case WM_SETFOCUS :
               nFocus = n ;
               break ;
          }
     return CallWindowProc (lpfnOldScr[n], hwnd, message, wParam, lParam) ;
     }

 COLORS1.DEF

;------------------------------------
; COLORS1.DEF module definition file
;------------------------------------

NAME           COLORS1

DESCRIPTION    'Colors Using Scroll Bars (c) Charles Petzold, 1990'
EXETYPE        WINDOWS
STUB           'WINSTUB.EXE'
CODE           PRELOAD MOVEABLE DISCARDABLE
DATA           PRELOAD MOVEABLE MULTIPLE
HEAPSIZE       1024
STACKSIZE      8192
EXPORTS        WndProc
               ScrollProc

COLORS1 puts its children to work. The program uses 10 child window
controls: 3 scroll bars, 6 windows of static text, and 1 static rectangle.
COLORS1 traps WM_CTLCOLOR messages to color the interior sections of the
three scroll bars red, green, and blue. You can scroll the scroll bars using
either the mouse or the keyboard. You can use COLORS1 as a development tool
in experimenting with color and choosing attractive (or, if you prefer,
ugly) colors for your own Windows programs. A monochrome version of the
COLORS1 display is shown in Figure 6-4; obviously, to take advantage of the
program's manipulation of color, you'll need to use a color monitor.

COLORS1 doesn't process WM_PAINT messages, and the program obtains a device
context handle only for determining the height of a character. Most of the
work in COLORS1 is done by the child windows.

The color shown on the right half of the client area is actually the
background color of the parent window. A static child window with style
SS_WHITERECT blocks out the left half of the client area. The three scroll
bars are child window controls with the style SBS_VERT placed on top of the
SS_WHITERECT child. Six more static child windows of style SS_CENTER
(centered text) provide the labels and the color values. COLORS1 creates its
normal overlapped window and the ten child windows within the WinMain
function using CreateWindow. The SS_WHITERECT and SS_CENTER static windows
use the window class "static," and the three scroll bars use the window
class "scrollbar."

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

The x position, y position, width, and height parameters of the CreateWindow
call are initially set to 0 because the position and sizing depend on the
size of the client area, which is not yet known. COLORS1's window procedure
resizes all ten child windows using MoveWindow when it receives a WM_SIZE
message. So whenever you resize the COLORS1 window, the size of the scroll
bars changes proportionally.

When the WndProc window procedure receives a WM_VSCROLL message, the high
word of the lParam parameter is the handle to the child window. We can use
GetWindowWord to get the window ID number:

n = GetWindowWord (HIWORD (lParam), GWW_ID) ;

For the three scroll bars, we have conveniently set the ID numbers to 0, 1,
and 2, so WndProc can tell which scroll bar is generating the message.

Because the handles to the child windows were saved in arrays when the
windows were created, WndProc can process the scroll bar message and set the
new value of the appropriate scroll bar using the SetScrollPos call:

SetScrollPos (hwndScrol[n], SB_CTL, color[n], TRUE) ;

WndProc also changes the text of the child window at the bottom of the
scroll bar:

SetWindowText (hwndValue[n], itoa (color[n], szbuffer, 10)) ;


The Automatic Keyboard Interface

Scroll bar controls can also process keystrokes, but only if they have the
input focus. The following table shows how keyboard cursor keys translate
into scroll bar messages:

Cursor Key             Scroll Bar Message wParam Value
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Home                   SB_TOP
End                    SB_BOTTOM
Page Up                SB_PAGEUP
Page Down              SB_PAGEDOWN
Left or Up             SB_LINEUP
Right or Down          SB_LINEDOWN

In fact, the SB_TOP and SB_BOTTOM scroll bar messages can be generated only
by using the keyboard. If you want a scroll bar control to obtain the input
focus when the scroll bar is clicked with the mouse, you must include the
WS_TABSTOP identifier in the window class parameter of the CreateWindow
call. When a scroll bar has the input focus, a blinking gray block is
displayed on the scroll bar thumb.

To provide a full keyboard interface to the scroll bars, however, some more
work is necessary. First, the WndProc window procedure must specifically
give a scroll bar the input focus. It does this by processing the
WM_SETFOCUS message, which the parent window receives when it obtains the
input focus. WndProc simply sets the input focus to one of the scroll bars:

SetFocus (hwndScrol[nFocus]) ;

But you also need some way to get from one scroll bar to another by using
the keyboard, preferably by using the Tab key. This is more difficult,
because once a scroll bar has the input focus, it processes all keystrokes.
But the scroll bar cares only about the cursor keys; it ignores the Tab key.
The way out of this dilemma lies in a technique called "window subclassing."
We'll use it to add a facility to COLORS1 to jump from one scroll bar to
another using the Tab key.


Window Subclassing

The window procedure for the scroll bar controls is somewhere inside
Windows. However, you can obtain the address of this window procedure by a
call to GetWindowLong using the GWL_WNDPROC identifier as a parameter.
Moreover, you can set a new window procedure for the scroll bars by calling
SetWindowLong. This technique, called "window subclassing," is very
powerful. It lets you hook into existing window procedures, process some
messages within your own program, and pass all other messages to the old
window procedure.

The window procedure that does preliminary scroll bar message processing in
COLORS1 is called ScrollProc; it is toward the end of the COLORS1.C listing.
Because ScrollProc is a function within COLORS1 that is called by Windows,
it must be defined as FAR PASCAL and must be listed under EXPORTS in the
COLORS1.DEF module definition file.

First, to ensure that ScrollProc accesses the proper data segment, COLORS1
must obtain a far address for the function using MakeProcInstance:

lpfnScrollProc = MakeProcInstance ((FARPROC) ScrollProc, hInstance);

For each of the three scroll bars, COLORS1 uses GetWindowLong to obtain and
save the address of the existing scroll bar window procedure:

lpfnOldScr[n] = (FARPROC) GetWindowLong (hwndScrol[n], GWL_WNDPROC) ;

Next, the program sets the new scroll bar window procedure:

SetWindowLong (hwndScrol[n], GWL_WNDPROC, (LONG) lpfnScrollProc) ;

Now the function ScrollProc gets all messages that Windows sends to the
scroll bar window procedure for the three scroll bars in COLORS1 (but not,
of course, for scroll bars in other programs). The ScrollProc window
procedure simply changes the input focus to the next (or previous) scroll
bar when it receives a Tab or Shift-Tab keystroke. It calls the old scroll
bar window procedure using CallWindowProc.


Coloring the Background

When COLORS1 defines its window class, it gives the background of its client
area a solid black brush:

wndclass.hbrBackground = CreateSolidBrush (0L) ;

When you change the settings of COLORS1's scroll bars, the program must
create a new brush and put the new brush handle in the window class
structure. Just as we were able to get and set the scroll bar window
procedure using GetWindowLong and SetWindowLong, we can get and set the
handle to this brush using GetClassWord and SetClassWord.

First you must delete the existing brush:

DeleteObject (GetClassWord (hwnd, GCW_HBRBACKGROUND)) ;

Then you can create the new brush and insert the handle in the window class
structure:

SetClassWord (hwnd, GCW_HBRBACKGROUND,
     CreateSolidBrush (RGB (color[0], color[1], color[2]))) ;

The next time Windows recolors the background of the window, Windows will
use this new brush. To force Windows to erase the background, we invalidate
the entire client area:

InvalidateRect (hwnd, NULL, TRUE) ;

The TRUE (nonzero) value as the third parameter indicates that we want the
background erased before repainting.

InvalidateRect causes Windows to put a WM_PAINT message in the message queue
of the window procedure. Because WM_PAINT messages are low priority, this
message will not be processed immediately if you are still moving the scroll
bar with the mouse or the cursor keys. Alternatively, if you want the window
to be updated immediately after the color is changed, you can add the
statement:

UpdateWindow (hwnd) ;

after the InvalidateRect call. But this slows down keyboard and mouse
processing.

COLORS1's WndProc function doesn't process the WM_PAINT message but passes
it to DefWindowProc. Window's default processing of WM_PAINT messages simply
involves calling BeginPaint and EndPaint to validate the window. Because we
specified in the InvalidateRect call that the background should be erased,
the BeginPaint call causes Windows to generate a WM_ERASEBKGND (erase
background) message. WndProc ignores this message also. Windows processes it
by erasing the background of the client area using the brush specified in
the window class.

Normally, Windows would erase the entire client area using the window class
brush. Doing so would erase the 10 child windows, however, and Windows would
then have to send WM_PAINT messages to all the child windows so they could
repaint themselves_ very annoying. We avoid the problem by using the
WS_CLIPCHILDREN style value when first creating the parent window using
CreateWindow; this style prevents the parent window from painting over its
children. Take the WS_CLIPCHILDREN style out of CreateWindow, and you'll see
a big difference in how COLORS1 works.

Like all GDI objects, the brushes created by a program using
CreateSolidBrush are not automatically deleted by Windows when the program
terminates. We've been good about deleting each brush before creating a new
one, but when the program is about to terminate, one last brush in the
window class still should be discarded. Thus, during processing of the
WM_DESTROY message, DeleteObject is called once more:

DeleteObject (GetClassWord (hwnd, GCW_HBRBACKGROUND)) ;


Coloring the Scroll Bars

On a color display, the interiors of the three scroll bars in COLORS1 are
red, green, and blue. This coloring is accomplished by processing
WM_CTLCOLOR messages.

In WndProc we define a static array of three handles to brushes:

static HBRUSH hBrush [3] ;

During processing of WM_CREATE, we create the three brushes:

hBrush[0] = CreateSolidBrush (RGB (255, 0, 0)) ;
hBrush[1] = CreateSolidBrush (RGB (0, 255, 0)) ;
hBrush[2] = CreateSolidBrush (RGB (0, 0, 255)) ;

During the WM_CTLCOLOR processing, the text and text background colors are
set to the normal values for scroll bars. The brush that's returned from
this message is one of the three brushes created earlier:

case WM_CTLCOLOR :
     if (HIWORD (lParam) == CTLCOLOR_SCROLLBAR)
          {
          SetBkColor (wParam, GetSysColor (COLOR_CAPTIONTEXT)) ;
          SetTextColor (wParam, GetSysColor (COLOR_WINDOWFRAME)) ;

          n = GetWindowWord (LOWORD (lParam), GWW_ID) ;
          point.x = point.y = 0 ;
          ClientToScreen (hwnd, &point) ;
          UnrealizeObject (hBrush[n]) ;
          SetBrushOrg (wParam, point.x, point.y) ;

          return ((DWORD) hBrush[n]) ;
          }
     break ;

These brushes must be destroyed during processing of the WM_DESTROY message:

for (n = 0 ; n < 3 ; DeleteObject (hBrush [n++])) ;


Dealing with Multiple Instances

Normally, Windows programs reuse the same window class when you load
multiple instances of the program. The window class is registered only if
the previous instance is NULL:

if (!hPrevInstance)
     {
     wndclass.style = CS_HREDRAW | CS_VREDRAW ;
[more program lines]

But COLORS1 can't do this, because the background color is specified in the
window class. If all instances of COLORS1 used the same window class, then
each instance would use (and change) the same background color. We can avoid
this problem entirely by allowing only one instance of COLORS1 to run:

if (hPrevInstance)
     return FALSE ;


COLORS1 as an Icon

When you make COLORS1 into an icon, the entire surface of the icon--rather
than only the right half--is the color of the parent window's background.
Yet COLORS1 doesn't seem to have any separate icon logic.

You'll note that COLORS1 specifies a NULL icon in the window class:

wndclass.hIcon = NULL ;

This indicates that COLORS1 is responsible for painting its icon. The entire
icon appears as the background color because Windows hides child windows
when a program becomes an icon, and thus the colored background is
completely uncovered.



THE EDIT CLASS

The edit class is in some ways the simplest predefined window class and in
other ways the most complex. When you create a child window using the class
name "edit," you define a rectangle based on the x position, y position,
width, and height parameters of the CreateWindow call. This rectangle
contains editable text. When the child window control has the input focus,
you can type text, move the cursor around, select portions of text using
either the mouse or the Shift key and a cursor key, delete selected text to
the clipboard by pressing Shift-Del, or insert text from the clipboard by
pressing Shift-Ins.

One of the simplest uses of edit controls is for single-line entry fields.
For instance, the Windows PIF Editor program uses edit controls in this way
on its main window. But edit controls are not limited to single lines. For
example, the Windows Notepad program uses a multiline edit control. The file
size of the Notepad program is surprisingly small_ less than 32 KB. Most of
the editing logic is not in Notepad at all; it's in the edit control logic
within Windows.

To give you an idea of the power of edit controls, we'll write a "Notepad
clone" program called POPPAD1. We'll begin the program in this chapter and
continue it in Chapters 9 (when we'll add a menu) and 10 (when we'll use
dialog boxes to load and save files). POPPAD1 is shown in Figure 6-5.

 POPPAD1.MAK

#-----------------------
# POPPAD1.MAK make file
#-----------------------

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

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


 POPPAD1.C

/*-------------------------------------------------------
   POPPAD1.C -- Popup Editor Using Child Window Edit Box
                (c) Charles Petzold, 1990
  -------------------------------------------------------*/

#include 

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

char szAppName[] = "PopPad1" ;

int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance,
                    LPSTR lpszCmdLine, int nCmdShow)
     {
     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, szAppName,
                          WS_OVERLAPPEDWINDOW,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          GetSystemMetrics (SM_CXSCREEN) / 2,
                          GetSystemMetrics (SM_CYSCREEN) / 2,
                          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 hwndEdit ;

     switch (message)
          {
          case WM_CREATE :
               hwndEdit = CreateWindow ("edit", NULL,
                         WS_CHILD | WS_VISIBLE | WS_HSCROLL | WS_VSCROLL |
                              WS_BORDER | ES_LEFT | ES_MULTILINE |
                              ES_AUTOHSCROLL | ES_AUTOVSCROLL,
                         0, 0, 0, 0,
                         hwnd, 1,
                         ((LPCREATESTRUCT) lParam) -> hInstance, NULL) ;
               return 0 ;

          case WM_SETFOCUS :
               SetFocus (hwndEdit) ;
               return 0 ;

          case WM_SIZE :
               MoveWindow (hwndEdit, 0, 0, LOWORD (lParam),
                                           HIWORD (lParam), TRUE) ;
               return 0 ;

          case WM_COMMAND :
               if (wParam == 1 && HIWORD (lParam) == EN_ERRSPACE)
                    MessageBox (hwnd, "Edit control out of space.",
                              szAppName, MB_OK | MB_ICONSTOP) ;
               return 0 ;

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

 POPPAD1.DEF

;------------------------------------
; POPPAD1.DEF module definition file
;------------------------------------

NAME           POPPAD1

DESCRIPTION    'Popup Editor Version 1 (c) Charles Petzold, 1990'
EXETYPE        WINDOWS
STUB           'WINSTUB.EXE'
CODE           PRELOAD MOVEABLE DISCARDABLE
DATA           PRELOAD MOVEABLE MULTIPLE
HEAPSIZE       1024
STACKSIZE      8192
EXPORTS        WndProc

POPPAD1 is a multiline editor (without any file I/O just yet) in less than
100 lines of C. As you can see, POPPAD1 itself doesn't do very much. The
predefined edit control is doing quite a lot. In this form, the program lets
you explore what edit controls can do with- out any help from a program.

The Edit Class Styles

As noted earlier, you create an edit control using "edit" as the window
class in the CreateWindow call. The window style is WS_CHILD plus several
options. As in static child window controls, the text in edit controls can
be either left-justified, right-justified, or centered. You specify this
formatting with the window styles ES_LEFT, ES_RIGHT, and ES_CENTER.

By default, an edit control has a single line. You can create a multiline
edit control with the window style ES_MULTILINE. For a single-line edit
control, you can normally enter text only to the end of the edit control
rectangle. To create an edit control that automatically scrolls
horizontally, you use the style ES_AUTOHSCROLL. For a multiline edit
control, text wordwraps unless you use the ES_AUTOHSCROLL style, in which
case you must press the Enter key to start a new line. You can also include
vertical scrolling in a multiline edit control by using the style
ES_AUTOVSCROLL.

When you include these scrolling styles in multiline edit controls, you
might also want to add scroll bars to the edit control. You do so by using
the same window style identifiers as for nonchild windows: WS_HSCROLL and
WS_VSCROLL.

By default, an edit control has no border. You can add one by using the
style WS_BORDER.

When you select text in an edit control, Windows displays it in reverse
video. When the edit control loses the input focus, however, the selected
text is no longer highlighted. If  you want the selection to be highlighted
even when the edit control does not have the input focus, you can use the
style ES_NOHIDESEL.

When POPPAD1 creates its edit control, the style is given in the
CreateWindow call as:

WS_CHILD | WS_VISIBLE | WS_HSCROLL | WS_VSCROLL |
     WS_BORDER | ES_LEFT | ES_MULTILINE |
     ES_AUTOHSCROLL | ES_AUTOVSCROLL

In POPPAD1 the dimensions of the edit control are later defined by a call to
MoveWindow when WndProc receives a WM_SIZE message. The size of the edit
control is simply set to the size of the main window:

MoveWindow (hwndEdit, 0, 0, LOWORD (lParam),
                            HIWORD (lParam), TRUE) ;

For a single-line edit control, the height of the control must accommodate
the height of a character. If the edit control has a border (as most do),
use 1-1/2 times the height of a character (including external leading).


Edit Control Notification

Edit controls send WM_COMMAND messages to the parent window procedure. The
meanings of the wParam and lParam variables are the same as for button
controls:

Parameter                    Description
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
wParam                       Child window ID
LOWORD (lParam)              Child window handle
HIWORD (lParam)              Notification code

The notification codes are shown below:


ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
EN_SETFOCUS    Edit control has gained the input focus
EN_KILLFOCUS   Edit control has lost the input focus
EN_CHANGE      Edit control's contents will change
EN_UPDATE      Edit control's contents have changed
EN_ERRSPACE    Edit control has run out of space
EN_MAXTEXT     Edit control has run out of space on insertion
EN_HSCROLL     Edit control's horizontal scroll bar has been clicked
EN_VSCROLL     Edit control's vertical scroll bar has been clicked


POPPAD1 traps only EN_ERRSPACE notification codes and displays a message
box.

The edit control stores text in the local heap of its parent window's
program. The contents of an edit control are limited to about 32 KB. You'll
note that POPPAD1 reserves only 1 KB of space for its local heap in the
module definition file. As we'll see in Chapter 7, this is not a problem.
Windows will expand the program's local heap if an edit control needs more
space.


Using the Edit Controls

If you use several single-line edit controls on the surface of your main
window (as PIFEDIT does), you'll need to use window subclassing to move the
input focus from one control to another. You can accomplish this much as
COLORS1 does, by intercepting Tab and Shift-Tab keystrokes. (Another example
of window subclassing is shown later in this chapter in the HEAD program.)
How you handle the Enter key is up to you. You can use it the same way as
the Tab key or as a signal to your program that all the edit fields are
ready.

If you want to insert text into an edit field, you can do so using
SetWindowText. Getting text out of an edit control involves
GetWindowTextLength and GetWindowText. We'll see examples of these
facilities in our later revisions to the POPPAD1 program.


Messages to an Edit Control

We won't cover all the messages you can send to an edit control using
SendMessage, because there are quite a few of them, and several will be used
in the later POPPAD1 revisions. Here's a broad overview.

These messages let you cut, copy, or clear the current selection. A user
selects the text to be acted upon by using the mouse or the Shift key and a
cursor key, thus highlighting the selected text in the edit control.

SendMessage (hwndEdit, WM_CUT, 0, 0L) ;
SendMessage (hwndEdit, WM_COPY, 0, 0L) ;
SendMessage (hwndEdit, WM_CLEAR, 0, 0L) ;

WM_CUT removes the current selection from the edit control and sends it to
the clipboard. WM_COPY copies the selection to the clipboard but leaves it
intact in the edit control. WM_CLEAR deletes the selection from the edit
control without passing it to the clipboard.

You can also insert clipboard text into the edit control at the cursor
position:

SendMessage (hwndEdit, WM_PASTE, 0, 0L) ;

You can obtain the starting and ending positions of the current selection:

lSelect = SendMessage (hwndEdit, EM_GETSEL, 0, 0L) ;

The low word of lSelect has the starting position. The high word has the end
position plus 1.

You can select text:

SendMessage (hwndEdit, EM_SETSEL, 0, MAKELONG (wBegin, wEnd)) ;

You can also replace a current selection with other text:

SendMessage (hwndEdit, EM_REPLACESEL, 0, (LONG) lpszString) ;

For multiline edit controls, you can obtain the number of lines:

nCount = SendMessage (hwndEdit, EM_GETLINECOUNT, 0, 0L) ;

For any particular line, you can obtain an offset from the beginning of the
edit buffer text:

nOffset = SendMessage (hwndEdit, EM_LINEINDEX, wLine, 0L) ;

Lines are numbered starting at 0. A wLine value of -1 returns the offset of
the line containing the cursor. You obtain the length of the line from:

nOffset = SendMessage (hwndEdit, EM_LINELENGTH, wLine, 0L) ;

and copy the line itself into a buffer using:

nLength = SendMessage (hwndEdit, EM_GETLINE, wLine, lpszBuffer) ;



THE LISTBOX CLASS

The final predefined child window control I'll discuss in this chapter is
the list box. (The combo box is a combination of a list box and an edit
field.) A list box is a collection of text strings displayed as a scrollable
columnar list within a rectangle. A program can add or remove strings in the
list by sending messages to the list box window procedure. The list box
control sends WM_COMMAND messages to its parent window when an item in the
list is selected. The parent window can then determine which item has been
selected.

List boxes are most commonly used in dialog boxes called up by selecting
Open from the File menu. The list box displays files in the current
directory and can also display other subdirectories and disk drives. List
boxes are also used in the CONTROL program for changing colors and in WRITE
for selecting fonts. A list box can be either single selection or multiple
selection. The latter allows the user to select more than one item from the
list box. When a list box has the input focus, it displays a dashed line
surrounding an item in the list box. This cursor does not indicate the
selected item in the list box. The selected item is indicated by
highlighting, which displays the item in reverse video.

In a single-selection list box, the user can select the item that the cursor
is positioned on by pressing the Spacebar. The arrow keys move both the
cursor and the current selection and can scroll contents of the list box.
The Page Up and Page Down keys also scroll the list box by moving the cursor
but not the selection. Pressing a letter key moves the cursor  and the
selection to the first (or next) item that begins with that letter. An item
can also be selected by clicking or double-clicking the mouse on the item.

In a multiple-selection list box, the Spacebar toggles the selection state
of the item where the cursor is positioned. (If the item is already
selected, it is deselected.) The arrow keys deselect all previously selected
items and move the cursor and selection just as in single-selection list
boxes. However, the Ctrl key and the arrow keys can move the cursor without
moving the selection. The Shift key and arrow keys can extend a selection.

Clicking or double-clicking an item in a multiple-selection list box
deselects all previously selected items and selects the clicked item.
However, clicking an item while pressing the Shift key toggles the selection
state of the item without changing the selection state of any other item.

List Box Styles

You create a list box child window control with CreateWindow using "listbox"
as the window class and WS_CHILD as the window style. However, this default
list box style does not send WM_COMMAND messages to its parent, meaning that
a program would have to interrogate the list box (via messages to the list
box controls) regarding the selection of items within the list box.
Therefore, list box controls almost always include the list box style
identifier LBS_NOTIFY, which allows the parent window to receive WM_COMMAND
messages from the list box. If you want the list box control to sort the
items in the list box, you can also use LBS_SORT, another common style.

By default, list boxes are single selection. Multiple-selection list boxes
are relatively rare. If you want to create one, you use the style
LBS_MULTIPLESEL.

Normally, a list box updates itself when a new item is added to the scroll
box list. You can prevent this by including the style LBS_NOREDRAW. You will
probably not want to use this style, however. Instead, you can temporarily
prevent repainting of a list box control by using the WM_SETREDRAW message
that I'll describe a little later.

By default, the list box window procedure displays only the list of items
without any border around it. You can add a border with the window style
identifier WS_BORDER. And to add a vertical scroll bar for scrolling through
the list with the mouse, you use the window style identifier WS_VSCROLL.

WINDOWS.H defines a list box style called LBS_STANDARD that includes the
most commonly used styles. It is defined as:

(LBS_NOTIFY | LBS_SORT | WS_VSCROLL | WS_BORDER)

You can also use the WS_SIZEBOX and WS_CAPTION identifiers, but these will
allow the user to resize the list box and to move it around its parent's
client area.

The width of a list box should accommodate the width of the longest string
plus the width of the scroll bar. You can get the width of the vertical
scroll bar using:

GetSystemMetrics (SM_CXVSCROLL) ;

You can calculate the height of the list box by multiplying the height of a
character by the number of items you want to appear in view. A list box does
not use tmExternalLeading when spacing lines of text.


Putting Strings in the List Box

After you've created the list box, the next step is to put text strings in
it. You do this by sending messages to the list box window procedure using
the SendMessage call. The text strings are generally referenced by an index
number that starts at 0 for the topmost item. In the examples that follow,
hwndList is the handle to the child window list box control, and wIndex is
the index value.

In cases where you pass a text string in the SendMessage call, the lParam
parameter is a far pointer to a null-terminated string. To avoid error
messages during compilation, cast this pointer to a LONG. Note that when you
cast a near pointer to a LONG, the C compiler will first cast the near
pointer to a far pointer.

In most of these examples, the SendMessage call returns LB_ERRSPACE (defined
as -2) if the window procedure runs out of available memory space to store
the contents of the list box. SendMessage returns LB_ERR (-1) if an error
occurs for other reasons and LB_OKAY (0) if the operation is successful. You
can test SendMessage for a nonzero value to detect either of the two errors.
The list box allocates global memory (outside your program's data segment)
for the list box contents.

If you use the LBS_SORT style (or if you are placing strings in the list box
in the order that you want them to appear), then the easiest way to fill up
a list box is with the LB- _ADDSTRING message:

SendMessage (hwndList, LB_ADDSTRING, 0, (LONG) szString) ;

If you do not use LBS_SORT, you can insert strings into your list box by
specifying an index value with LB_INSERTSTRING:

SendMessage (hwndList, LB_INSERTSTRING, wIndex, (LONG) szString) ;

For instance, if wIndex is equal to 4, szString becomes the new string with
an index value of 4--the fifth string from the top because counting starts
at 0. Any strings below this point are pushed down. A wIndex value of -1
adds the string to the bottom. You can use LB_INSERTSTRING with list boxes
that have the LBS_SORT style, but the list box contents will not be
re-sorted. (You can also insert strings into a list box using the LB_DIR
message, which is discussed in detail toward the end of this chapter.)

You can delete a string from the list box by specifying the index value with
the LB_DELETESTRING message:

SendMessage (hwndList, LB_DELETESTRING, wIndex, 0L) ;

You can clear out the list box using LB_RESETCONTENT:

SendMessage (hwndList, LB_RESETCONTENT, 0, 0L) ;

The list box window procedure updates the display when an item is added to
or deleted from the list box. If you have a number of strings to add or
delete, you may want to temporarily inhibit this action by turning off the
control's redraw flag:

SendMessage (hwndList, WM_SETREDRAW, FALSE, 0L) ;

After you've finished, you can turn the redraw flag back on:

SendMessage (hwndList, WM_SETREDRAW, TRUE, 0L) ;

A list box created with the LBS_NOREDRAW style begins with the redraw flag
turned off.


Selecting and Extracting Entries

The SendMessage calls that carry out the tasks shown below usually return a
value. If an error occurs, this value is set to LB_ERR (defined as -1). Note
that the return value from SendMessage is normally a signed long (LONG), but
the values are unsigned integers (WORD), so some casting is necessary.

After you've put some items into a list box, you can find out how many items
are in the list box:

nCount = (WORD) SendMessage (hwndList, LB_GETCOUNT, 0, 0L) ;

Some of the other calls are different for single-selection and
multiple-selection list boxes. Let's first look at single-selection list
boxes.

Normally, you'll let a user select from a list box. But if you want to
highlight a default selection, you can use:

SendMessage (hwndList, LB_SETCURSEL, nIndex, 0L) ;

Setting lParam to -1 in this call deselects all items.

You can also select an item based on its initial characters:

nIndex = (WORD) SendMessage (hwndList, LB_SELECTSTRING, wIndex,
                         (LONG) szSearchString) ;

The wIndex given as the wParam parameter to the SendMessage call is the
index following which the search begins for an item with initial characters
that match szSearchString. A wIndex value of -1 starts the search from the
top. SendMessage returns the index of the selected item, or LB_ERR if no
initial characters match szSearchString.

When you get a WM_COMMAND message from the list box (or at any other time),
you can determine the index of the current selection using LB_GETCURSEL:

nIndex = (WORD) SendMessage (hwndList, LB_GETCURSEL, 0, 0L) ;

The nIndex value returned from the call is LB_ERR if no item is selected.

You can determine the length of any string in the list box:

nLength = (WORD) SendMessage (hwndList, LB_GETTEXTLEN, nIndex, 0L) ;

and copy the item into the text buffer:

nLength = (WORD) SendMessage (hwndList, LB_GETTEXT, nIndex,
                          (LONG) szBuffer) ;

In both cases, the nLength value returned from the call is the length of the
string. The szBuffer array must be large enough for the length of the string
and a terminating NULL. You may want to use LB_GETTEXTLEN to first allocate
some local memory to hold the string (which you'll learn how to do in
Chapter 8).

For a multiple-selection list box, you cannot use LB_SETCURSEL,
LB_GETCURSEL, or LB_SELECTSTRING. Instead, you use LB_SETSEL to set the
selection state of a particular item without affecting other items that may
also be selected:

SendMessage (hwndList, LB_SETSEL, wParam, (LONG) wIndex) ;

The wParam parameter is nonzero to select and highlight the item and 0 to
deselect it. If the lParam parameter is -1, all items are either selected or
deselected. You can also determine the selection state of a particular item
using:

wSelect = (WORD) SendMessage (hwndList, LB_GETSEL, wIndex, 0L) ;

where wSelect is set to nonzero if the item indexed by wIndex is selected
and 0 if it is not.


Receiving Messages from List Boxes

When a user clicks on a list box with the mouse, the list box receives the
input focus. A parent window can give the input focus to a list box control
by using:

SetFocus (hwndList) ;

When a list box has the input focus, the cursor movement keys, letter keys,
and Spacebar can also be used to select items from the list box.

A list box control sends WM_COMMAND messages to its parent. The meanings of
the wParam and lParam variables are the same as for the button and edit
controls:

ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
wParam                       Child window ID
LOWORD (lParam)              Child window handle
HIWORD (lParam)              Notification code

The notification codes and their values are as follows:


ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
LBN_ERRSPACE                     -2
LBN_SELCHANGE                    1
LBN_DBLCLK                       2
LBN_SELCANCEL                    3
LBN_SETFOCUS                     4
LBN_KILLFOCUS                    5


The list box control sends the parent window LBN_SELCHANGE and LBN_DBLCLK
codes only if the list box window style includes LBS_NOTIFY.

The LBN_ERRSPACE code indicates that the list box control has run out of
space. The LBN_SELCHANGE code indicates that the current selection has
changed; these messages occur as the user moves the highlight through the
list box, toggles the selection state with the Spacebar, or clicks an item
with the mouse. The LBN_DBLCLK code indicates that a list box item has been
double-clicked with the mouse. (The notification code values for
LBN_SELCHANGE and LBN_DBLCLK refer to the number of mouse clicks.)

Depending on your application, you may want to use either LBN_SELCHANGE or
LBN_DBLCLK messages or both. Your program will get many LBN_SELCHANGE
messages, but LBN_DBLCLK messages occur only when the user double-clicks
with the mouse. If your program uses double-clicks, you'll need to provide a
keyboard interface that duplicates LBN_DBLCLK.


A Simple List Box Application

Now that you know how to create a list box, fill it with text items, receive
messages from the list box, and extract strings, it's time to program an
application. The ENVIRON program, shown in Figure 6-6, uses a list box in
its client area to display the name of your current MS-DOS environment
variables (such as PATH, COMSPEC, and PROMPT). As you select a variable, the
name and the environment string are displayed across the top of the client
area.

 ENVIRON.MAK

#-----------------------
# ENVIRON.MAK make file
#-----------------------

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

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

 ENVIRON.C

/*----------------------------------------
   ENVIRON.C -- Environment List Box
                (c) Charles Petzold, 1990
  ----------------------------------------*/



#include 
#include 
#include 
#define  MAXENV  4096

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

int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance,
                    LPSTR lpszCmdLine, int nCmdShow)
     {
     static char szAppName[] = "Environ" ;
     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 = COLOR_WINDOW + 1 ;
          wndclass.lpszMenuName  = NULL ;
          wndclass.lpszClassName = szAppName ;

          RegisterClass (&wndclass) ;
          }

     hwnd = CreateWindow (szAppName, "Environment List Box",
                          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 char szBuffer [MAXENV + 1] ;
     static HWND hwndList, hwndText ;
     HDC         hdc ;
     TEXTMETRIC  tm ;
     WORD        n ;

     switch (message)
          {
          case WM_CREATE :
               hdc = GetDC (hwnd) ;
               GetTextMetrics (hdc, &tm) ;
               ReleaseDC (hwnd, hdc) ;

               hwndList = CreateWindow ("listbox", NULL,
                              WS_CHILD | WS_VISIBLE | LBS_STANDARD,
                              tm.tmAveCharWidth, tm.tmHeight * 3,
                              tm.tmAveCharWidth * 16 +
                                   GetSystemMetrics (SM_CXVSCROLL),
                              tm.tmHeight * 5,
                              hwnd, 1,
                              GetWindowWord (hwnd, GWW_HINSTANCE), NULL) ;

               hwndText = CreateWindow ("static", NULL,
                              WS_CHILD | WS_VISIBLE | SS_LEFT,
                              tm.tmAveCharWidth,          tm.tmHeight,
                              tm.tmAveCharWidth * MAXENV, tm.tmHeight,
                              hwnd, 2,
                              GetWindowWord (hwnd, GWW_HINSTANCE), NULL) ;

               for (n = 0 ; environ[n] ; n++)
                    {
                    if (strlen (environ [n]) > MAXENV)
                         continue ;
                    *strchr (strcpy (szBuffer, environ [n]), '=') = '\0' ;
                    SendMessage (hwndList, LB_ADDSTRING, 0,
                                 (LONG) (LPSTR) szBuffer) ;
                    }
               return 0 ;

          case WM_SETFOCUS :
               SetFocus (hwndList) ;
               return 0 ;
          case WM_COMMAND :
               if (wParam == 1 && HIWORD (lParam) == LBN_SELCHANGE)
                    {
                    n = (WORD) SendMessage (hwndList, LB_GETCURSEL, 0, 0L) ;
                    n = (WORD) SendMessage (hwndList, LB_GETTEXT, n,
                                            (LONG) (LPSTR) szBuffer) ;

                    strcpy (szBuffer + n + 1, getenv (szBuffer)) ;
                    *(szBuffer + n) = '=' ;

                    SetWindowText (hwndText, szBuffer) ;
                    }
               return 0 ;

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

 ENVIRON.DEF

;------------------------------------
; ENVIRON.DEF module definition file
;------------------------------------

NAME           ENVIRON

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

ENVIRON creates two child windows: a list box with the style LBS_STANDARD
and a static window with the style SS_LEFT (left-justified text). ENVIRON
uses the environ variable (declared external in STDLIB.H) to obtain the list
of environment strings, and it uses the message LB_ADDSTRING to direct the
list box window procedure to place each string in the list box.

When you run ENVIRON, you can select an environment variable using the mouse
or the keyboard. Each time you change the selection, the list box sends a
WM_COMMAND message to the parent window, which is WndProc. When WndProc
receives a WM_COMMAND message, it checks to see if wParam is 1 (the child ID
of the list box) and if the high word of lParam (the notification code) is
equal to LBN_SELCHANGE. If so, it obtains the index of the selection using
the LB_GETCURSEL message and the text itself--the environment variable
name--using LB_GETTEXT. ENVIRON uses the C function getenv to obtain the
environment string corresponding to that variable and SetWindowText to pass
this string to the static child window control, which displays the text.

Note that ENVIRON cannot use the index returned from LB_GETCURSEL to index
the environ variable and obtain the environment string. Because the list box
has an LBS_SORT style (included in LBS_STANDARD), the indices no longer
match.


Listing Files

I've been saving the best for last: LB_DIR, the most powerful list box
message. This fills the list box with a file directory list, optionally
including subdirectories and valid disk drives:

SendMessage (hwndList, LB_DIR, wAttr, (LONG) lpszFileSpec) ;

Using file attribute codes

The wAttr parameter is a file attribute code. The least significant byte is
the normal file attribute code when making MS-DOS function calls:

wAttr                Attribute
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
0x0000               Normal file
0x0001               Read-only file
0x0002               Hidden file
0x0004               System file
0x0010               Subdirectory
0x0020               File with archive bit set

The high byte provides some additional control over the items desired:

wAttr                 Option
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
0x4000                Include drive letters
0x8000                Exclusive search only

When the wAttr value of the LB_DIR message is 0x0000, the list box lists
normal files, read-only files, and files with the archive bit set. This is
consistent with the logic used by MS-DOS function calls to find files. When
the value is 0x0010, the list includes child  subdirectories in addition to
these files; this list is the equivalent of that displayed by the Directory
command or by Windows' File Manager. A value of 0x4010 expands the 0x0010
list to include all valid drives; for many Windows programs, this is the
list in the dialog box called up by selecting Open from the program's File
menu. To list all files, child subdirectories, and drives, you set the wAttr
value to 0x4037.

Setting the topmost bit of wAttr lists the files with the indicated flag
while excluding normal files. For a Windows file backup program, for
instance, you might want to list only files that have been modified since
the last backup. Such files have their archive bits set, so you would use
0x8020. A value of 0x8010 lists only subdirectories; 0xC000, only valid disk
drives; and 0xC010, subdirectories and valid disk drives but no files.


Ordering file lists

The lParam parameter is a far pointer to a file specification string such as
"*.*". This file specification does not affect the subdirectories that the
list box includes.

You'll want to use the LBS_SORT message for list boxes with file lists. The
list box will first list files satisfying the file specification and then
(optionally) list valid disk drives in the form:

[-A-]

and (also optionally) subdirectory names. The first subdirectory listing
will take the form:

[..]

This "double-dot" subdirectory entry lets the user back up one level toward
the root directory. (The entry will not appear if you're listing files in
the root directory.) Finally, the specific subdirectory names are listed in
the form:

[SUBDIR]

If you do not use LBS_SORT, the filenames and subdirectory names are
intermixed and the drive letters appear at the bottom of the list box.



A head for Windows

A well-known UNIX utility called head displays the beginning lines of a
file. Let's use a list box to write a similar program for Windows. HEAD,
shown in Figure 6-7, lists all files and child subdirectories in the list
box. You can choose a file to display by double-clicking on the filename
with the mouse or by pressing the Enter key when the filename is selected.
You can also change the subdirectory using either of these methods. The
program displays up to 2 KB of the beginning of the file in the right side
of the client area of HEAD's window.

 HEAD.MAK

#--------------------
# HEAD.MAK make file
#--------------------

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

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

 HEAD.C

/*---------------------------------------------
   HEAD.C -- Displays Beginning (Head) of File
             (c) Charles Petzold, 1990
  ---------------------------------------------*/

#include 
#include 
#include 
#include 

#define  MAXPATH     100
#define  MAXREAD    2048

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

char    sReadBuffer [MAXREAD] ;
FARPROC lpfnOldList ;

int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance,
                    LPSTR lpszCmdLine, int nCmdShow)
     {
     static char szAppName [] = "Head" ;
     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 = COLOR_WINDOW + 1 ;
          wndclass.lpszMenuName  = NULL ;
          wndclass.lpszClassName = szAppName ;

          RegisterClass (&wndclass) ;
          }

     hwnd = CreateWindow (szAppName, "File Head",
                          WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
                          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     bValidFile ;
     static char     szFile [16] ;
     static HWND     hwndList, hwndText ;
     static OFSTRUCT ofs ;
     static RECT     rect ;
     char            szBuffer [MAXPATH + 1] ;
     HDC             hdc ;
     int             iHandle, i, iCount ;
     PAINTSTRUCT     ps ;
     TEXTMETRIC      tm ;

     switch (message)
          {
          case WM_CREATE :
               hdc = GetDC (hwnd) ;
               SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ;
               GetTextMetrics (hdc, &tm) ;
               ReleaseDC (hwnd, hdc) ;
               rect.left = 20 * tm.tmAveCharWidth ;
               rect.top  =  3 * tm.tmHeight ;

               hwndList = CreateWindow ("listbox", NULL,
                              WS_CHILDWINDOW | WS_VISIBLE | LBS_STANDARD,
                              tm.tmAveCharWidth, tm.tmHeight * 3,
                              tm.tmAveCharWidth * 13 +
                                   GetSystemMetrics (SM_CXVSCROLL),
                              tm.tmHeight * 10,
                              hwnd, 1,
                              GetWindowWord (hwnd, GWW_HINSTANCE), NULL) ;

               hwndText = CreateWindow ("static", getcwd (szBuffer,
MAXPATH),
                              WS_CHILDWINDOW | WS_VISIBLE | SS_LEFT,
                              tm.tmAveCharWidth,           tm.tmHeight,
                              tm.tmAveCharWidth * MAXPATH, tm.tmHeight,
                              hwnd, 2,
                              GetWindowWord (hwnd, GWW_HINSTANCE), NULL) ;

               lpfnOldList = (FARPROC) GetWindowLong (hwndList, GWL_WNDPROC)
;

               SetWindowLong (hwndList, GWL_WNDPROC,
                         (LONG) MakeProcInstance ((FARPROC) ListProc,
                                   GetWindowWord (hwnd, GWW_HINSTANCE))) ;

               SendMessage (hwndList, LB_DIR, 0x37, (LONG) (LPSTR) "*.*") ;
               return 0 ;

          case WM_SIZE :
               rect.right  = LOWORD (lParam) ;
               rect.bottom = HIWORD (lParam) ;
               return 0 ;

          case WM_SETFOCUS :
               SetFocus (hwndList) ;
               return 0 ;

          case WM_COMMAND :
               if (wParam == 1 && HIWORD (lParam) == LBN_DBLCLK)
                    {
                    if (LB_ERR == (i = (WORD) SendMessage (hwndList,
                                                  LB_GETCURSEL, 0, 0L)))
                         break ;

                    SendMessage (hwndList, LB_GETTEXT, i,
                                        (LONG) (char far *) szBuffer) ;
                    if (-1 != OpenFile (szBuffer, &ofs, OF_EXIST | OF_READ))
                         {
                         bValidFile = TRUE ;
                         strcpy (szFile, szBuffer) ;
                         getcwd (szBuffer, MAXPATH) ;
                         if (szBuffer [strlen (szBuffer) - 1] != '\\')
                              strcat (szBuffer, "\\") ;
                         SetWindowText (hwndText, strcat (szBuffer, szFile))
;
                         }
                    else
                         {
                         bValidFile = FALSE ;
                         szBuffer [strlen (szBuffer) - 1] = '\0' ;
                         chdir (szBuffer + 1) ;
                         getcwd (szBuffer, MAXPATH) ;
                         SetWindowText (hwndText, szBuffer) ;
                         SendMessage (hwndList, LB_RESETCONTENT, 0, 0L) ;
                         SendMessage (hwndList, LB_DIR, 0x37,
                                      (LONG) (LPSTR) "*.*") ;
                         }
                    InvalidateRect (hwnd, NULL, TRUE) ;
                    }
               return 0 ;

          case WM_PAINT :
               hdc = BeginPaint (hwnd, &ps) ;
               SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ;
               SetTextColor (hdc, GetSysColor (COLOR_WINDOWTEXT)) ;
               SetBkColor   (hdc, GetSysColor (COLOR_WINDOW)) ;

               if (bValidFile && -1 != (iHandle =
                         OpenFile (szFile, &ofs, OF_REOPEN | OF_READ)))
                    {
                    i = read (iHandle, sReadBuffer, MAXREAD) ;
                    close (iHandle) ;
                    DrawText (hdc, sReadBuffer, i, &rect, DT_WORDBREAK |
                                   DT_EXPANDTABS | DT_NOCLIP | DT_NOPREFIX)
;
                    }
               else
                    bValidFile = FALSE ;

               EndPaint (hwnd, &ps) ;
               return 0 ;

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

long FAR PASCAL ListProc (HWND hwnd, WORD message, WORD wParam, LONG lParam)
     {
     if (message == WM_KEYDOWN && wParam == VK_RETURN)

          SendMessage (GetParent (hwnd), WM_COMMAND, 1,
                         MAKELONG (hwnd, LBN_DBLCLK)) ;

     return CallWindowProc (lpfnOldList, hwnd, message, wParam, lParam) ;
     }

 HEAD.DEF

;---------------------------------
; HEAD.DEF module definition file
;---------------------------------

NAME           HEAD

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

In ENVIRON, when we selected an environment variable--either with a mouse
click or with the keyboard--the program displayed an environment string. If
we used this select-display approach in HEAD, however, the program would be
too slow because it would continually need to open and close files as you
moved the selection through the list box. Instead, HEAD requires that the
file or subdirectory be double-clicked. This presents a bit of a problem
because list box controls have no automatic keyboard interface that
corresponds to a mouse double-click. As we know, we shouldn't write Windows
programs that require a mouse.

The solution? Window subclassing, of course. The list box subclass function
in HEAD is called ListProc. It simply looks for a WM_KEYDOWN message with
wParam equal to VK_RETURN and sends a WM_COMMAND message with an LBN_DBLCLK
notification code back to the parent. The WM_COMMAND processing in WndProc
uses the Windows function OpenFile to check for the selection from the list.
If OpenFile returns an error, the  selection is not a file, so it's probably
a subdirectory. HEAD then uses chdir to change the subdirectory. It sends a
LB_RESETCONTENT message to the list box to clear out the contents and a
LB_DIR message to fill the list box with files from the new subdirectory.

The WM_PAINT message processing in WndProc opens the file using the Windows
OpenFile function. This returns an MS-DOS handle to the file that can be
passed to the normal C functions read and close. The contents of the file
are displayed using DrawText.


2 KB of Wasted Space

HEAD includes a 2-KB array called sReadBuffer that is needed only briefly,
when the contents of the file are read and passed to DrawText. But this
array remains in the program's data segment during the entire time this
program is running. Wouldn't it make more sense to allocate that memory
before the read call and free it up after DrawText?

Yes, it would. For that reason we can no longer avoid the subject of Windows
memory management. It's not an easy subject, but let's begin.