Chapter 10  Dialog Boxes
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ

Dialog boxes are most often used for obtaining additional input from the
user beyond what can be easily managed through a menu. The programmer
indicates that a menu item invokes a dialog box by adding an ellipsis (...)
to the menu item.

A dialog box generally takes the form of a popup window containing various
child window controls. The size and placement of these controls are
specified in a "dialog box template" in the program's resource script file.
Windows is responsible for creating the dialog box popup window and the
child window controls and for providing a window procedure to process dialog
box messages (including all keyboard and mouse input). The code within
Windows that does all this is sometimes referred to as the "dialog box
manager."

Many of the messages that are processed by the dialog box window procedure
within Windows are also passed to a function within your own program, called
a "dialog box procedure" or "dialog procedure." This function is similar to
a normal window procedure, but with some important differences. Generally,
you will not be doing very much within the dialog procedure except
initializing the child window controls when the dialog box is created,
processing messages from the child window controls, and ending the dialog
box.

The subject of dialog boxes would normally be a big one, because it involves
the use of child window controls. However, we have already explored child
window controls in Chapter 6. When you use child window controls in dialog
boxes, the Windows dialog box manager picks up many of the responsibilities
that we assumed in Chapter 6. In particular, the problems we encountered
with passing the input focus between the scroll bars in the COLORS1 program
do not occur with dialog boxes. Windows handles all the logic necessary to
shift input focus between controls in a dialog box.

However, adding a dialog box to a program is not a trivial undertaking. It
involves changes to several files--the dialog box template goes in the
resource script file, the dialog box procedure goes in the source code file,
the name of the dialog box procedure goes in the module definition file, and
identifiers used in the dialog box often go in the program's header file.
We'll begin with a simple dialog box so that you get a feel for the
interconnections between these various pieces.

MODAL DIALOG BOXES

Dialog boxes are either "modal" or "modeless." The modal dialog box is the
most common. When your program displays a modal dialog box, the user cannot
switch between the dialog box and another window in your program. The user
must explicitly end the dialog box, usually by clicking a push button marked
either OK or Cancel. The user can, however, generally switch to another
program while the dialog box is still displayed. Some dialog boxes (called
"system modal") do not allow even this. System modal dialog boxes must be
ended before the user does anything else in Windows.

Creating an "About" Dialog Box

Even if a Windows program requires no user input, it will often have a
dialog box that is invoked by an About option on the menu. This dialog box
displays the name and icon of the program, a copyright notice, a push button
labeled OK, and perhaps other information. The first program we'll look at
does nothing except display an About dialog box. The ABOUT1 program is shown
in Figure 10-1.

 ABOUT1.MAK

#----------------------
# ABOUT1.MAK make file
#----------------------

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

about1.obj : about1.c about1.h
     cl -c -Gsw -Ow -W2 -Zp about1.c

about1.res : about1.rc about1.h about1.ico
     rc -r about1.rc


 ABOUT1.C

/*------------------------------------------
   ABOUT1.C -- About Box Demo Program No. 1
               (c) Charles Petzold, 1990
  ------------------------------------------*/

#include 
#include "about1.h"

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

int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance,
                    LPSTR lpszCmdLine, int nCmdShow)
     {
     static char szAppName [] = "About1" ;
     MSG         msg ;
     HWND        hwnd ;
     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 (hInstance, szAppName) ;
          wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;
          wndclass.hbrBackground = GetStockObject (WHITE_BRUSH) ;
          wndclass.lpszMenuName  = szAppName ;
          wndclass.lpszClassName = szAppName ;

          RegisterClass (&wndclass) ;
          }

     hwnd = CreateWindow (szAppName, "About Box Demo Program",
                          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 ;
     }

BOOL FAR PASCAL AboutDlgProc (HWND hDlg, WORD message, WORD wParam, LONG
lParam)
     {
     switch (message)
          {
          case WM_INITDIALOG :
               return TRUE ;

          case WM_COMMAND :
               switch (wParam)
                    {
                    case IDOK :
                    case IDCANCEL :
                         EndDialog (hDlg, 0) ;
                         return TRUE ;
                    }
               break ;
          }
     return FALSE ;
     }

long FAR PASCAL WndProc (HWND hwnd, WORD message, WORD wParam, LONG lParam)
     {
     static FARPROC lpfnAboutDlgProc ;
     static HANDLE  hInstance ;

     switch (message)
          {
          case WM_CREATE :
               hInstance = ((LPCREATESTRUCT) lParam)->hInstance ;

               lpfnAboutDlgProc = MakeProcInstance (AboutDlgProc, hInstance)
;
               return 0 ;

          case WM_COMMAND :
               switch (wParam)
                    {
                    case IDM_ABOUT :
                         DialogBox (hInstance, "AboutBox", hwnd,
                                        lpfnAboutDlgProc) ;
                         return 0 ;
                    }
               break ;
          case WM_DESTROY :
               PostQuitMessage (0) ;
               return 0 ;
          }
     return DefWindowProc (hwnd, message, wParam, lParam) ;
     }

 ABOUT1.RC

/*---------------------------
   ABOUT1.RC resource script
  ---------------------------*/

#include 
#include "about1.h"

About1 ICON about1.ico

About1 MENU
     {
     POPUP "&Help"
          {
          MENUITEM "&About About1...",       IDM_ABOUT
          }
     }

AboutBox DIALOG  20, 20, 160, 80
     STYLE WS_POPUP | WS_DLGFRAME
     {
     CTEXT "About1"                      -1,   0, 12, 160,  8
     ICON  "About1"                      -1,   8,  8,   0,  0
     CTEXT "About Box Demo Program"      -1,   0, 36, 160,  8
     CTEXT "(c) Charles Petzold, 1990"   -1,   0, 48, 160,  8
     DEFPUSHBUTTON "OK"                IDOK,  64, 60,  32, 14, WS_GROUP
     }

 ABOUT1.H

/*----------------------
   ABOUT1.H header file
  ----------------------*/

#define IDM_ABOUT      1

 ABOUT1.ICO  -- Please refer to the book.

FIG 10-1 partial screen dump

 ABOUT1.DEF

;-----------------------------------
; ABOUT1.DEF module definition file
;-----------------------------------

NAME           ABOUT1

DESCRIPTION    'About Box Demo No. 1 (c) Charles Petzold, 1990'
EXETYPE        WINDOWS
STUB           'WINSTUB.EXE'
CODE           PRELOAD MOVEABLE DISCARDABLE
DATA           PRELOAD MOVEABLE MULTIPLE
HEAPSIZE       1024
STACKSIZE      8192
EXPORTS        WndProc
               AboutDlgProc


The Dialog Box Template

The first job involved in adding a dialog box to a program is designing the
dialog box template. This template can go directly in the resource script
file, or it can be in a separate file that by convention uses the extension
.DLG (for "dialog"). If you put the template in a separate file, you include
the line:

rcinclude filename.dlg

in the resource script file.

You can create the dialog box template by hand in a text editor, or you can
use the DIALOG program included with the Windows Software Development Kit.
Because the output from DIALOG is virtually unreadable, I'll be showing
dialog box templates that look as if they were created by hand. A discussion
of DIALOG concludes this chapter.

The dialog box template for ABOUT1 looks like this:

AboutBox DIALOG  20, 20, 160, 80
     STYLE WS_POPUP | WS_DLGFRAME
     {
     CTEXT "About1"                      -1,   0, 12, 160,  8
     ICON  "About1"                      -1,   8,  8,   0,  0
     CTEXT "About Box Demo Program"      -1,   0, 36, 160,  8
     CTEXT "(c) Charles Petzold, 1990"   -1,   0, 48, 160,  8
     DEFPUSHBUTTON "OK"                IDOK,  64, 60,  32, 14, WS_GROUP
     }

The first line gives the dialog box a name (in this case, AboutBox). As is
the case for other resources, you can use a number instead. The name is
followed by the keyword DIALOG and four numbers. The first two numbers are
the x- and y-coordinates of the upper left corner of the dialog box,
relative to the client area of its parent when the dialog box is invoked by
the program. The second two numbers are the width and height of the dialog
box.

These coordinates and sizes are not in units of pixels. They are instead
based on a special coordinate system used only for dialog box templates. The
numbers are based on the size of a system font character: x-coordinates and
width are expressed in units of 1/4 of an average character width;
y-coordinates and height are expressed in units of 1/8 of a character
height. Thus for this particular dialog box, the upper left corner of the
dialog box is 5 characters from the left of the main window's client area
and 2-1/2 characters from the top. It is 40 characters wide and 10
characters high.

This coordinate system allows you to use coordinates and sizes that will
retain the general dimensions and look of the dialog box regardless of the
resolution of the video display. Because system font characters are often
approximately twice as high as they are wide, the units on both the x- and
y-axes are about the same.

The DIALOG statement can also include load options (PRELOAD and LOADONCALL)
and memory options (FIXED, MOVEABLE, and DISCARDABLE) immediately following
the word DIALOG. The defaults are LOADONCALL and MOVEABLE. The STYLE
statement in the template is similar to the style field of a CreateWindow
call. Using WS_POPUP and WS_DLGFRAME is normal for modal dialog boxes, but
we'll explore some alternatives later on.

Within the left and right brackets, you define the child window controls
that will appear in the dialog box. This dialog box uses three types of
child window controls: CTEXT (centered text), ICON (an icon), and
DEFPUSHBUTTON (a default push button). The format of these statements is:

control-type "text" nID, xPos, yPos, xWidth, yHeight, dwStyle

The dwStyle value at the end is optional; it specifies additional window
styles using identifiers defined in WINDOWS.H.

These CTEXT, ICON, and DEFPUSHBUTTON identifiers are used only in dialog
boxes. They are shorthand for a particular window class and window style.
For example, CTEXT indicates that the class of the child window control is
"static" and that the style is:

WS_CHILD | SS_CENTER | WS_VISIBLE | WS_GROUP

Although this is the first time we've encountered the WS_GROUP identifier,
we used  the WS_CHILD, SS_CENTER, and WS_VISIBLE window styles when creating
static child window text controls in the COLORS1 program in Chapter 6.

For the icon, the text field is the name of the program's icon resource,
which is also defined in the ABOUT1 resource script. For the push button,
the text field is the text that appears inside the push button. This text is
equivalent to the text specified as the second parameter to a CreateWindow
call when you create a child window control in a program.

The nID field is a value that the child window uses to identify itself when
sending messages (usually WM_COMMMAND messages) to its parent. The parent
window of these child window controls is the dialog box window itself, which
sends these messages to a window procedure in Windows. However, this window
procedure also sends these messages to the dialog box procedure that you'll
include in your program. The nID values are equivalent to the child window
IDs used in the CreateWindow function when we created child windows in
Chapter 6. Because the text and icon controls do not send messages back to
the parent window, these values are set to -1. The nID value for the push
button is IDOK, which is defined in WINDOWS.H as 1.

The next four numbers set the position of the child window control (relative
to the upper left corner of the dialog box's client area) and the size. The
position and size are expressed in units of 1/4 the average width and 1/8
the height of a system font character. The width and height values are
ignored for the ICON statement.

The DEFPUSHBUTTON statement in the dialog box template includes the window
style WS_GROUP in addition to the window style implied by the DEFPUSHBUTTON
keyword. I'll have more to say about WS_GROUP (and the related WS_TABSTOP
style) when discussing the second version of this program, ABOUT2, a bit
later.


The Dialog Box Procedure

The dialog box procedure within your program handles messages to the dialog
box. Although it looks very much like a window procedure, it is not a true
window procedure. The window procedure for the dialog box is within Windows.
That window procedure calls your dialog box procedure with many of the
messages that it receives. Here's the dialog box procedure for ABOUT1:

BOOL FAR PASCAL AboutDlgProc (HWND hDlg, WORD message,
                              WORD wParam, LONG lParam)
     {
     switch (message)
          {
          case WM_INITDIALOG :
              return TRUE ;

          case WM_COMMAND :
               switch (wParam)
                    {
                    case IDOK :
                    case IDCANCEL :
                         EndDialog (hDlg, 0) ;
                         return TRUE ;
                    }
               break ;
          }
     return FALSE ;
     }

The parameters to this function are the same as those for a normal window
procedure. (Although I've used hDlg for the handle to the dialog box window,
you can use hwnd instead if you like.) Let's note first the differences
between this function and a window procedure:

  þ   A window procedure returns a long; a dialog box procedure returns a
      BOOL (which is defined in WINDOWS.H as an int).

  þ   A window procedure calls DefWindowProc if it does not process a
      particular message; a dialog box procedure returns TRUE (nonzero) if
      it processes a message and FALSE (0) if it does not.

  þ   A dialog box procedure does not need to process WM_PAINT or WM_DESTROY
      messages. A dialog box procedure will not receive a WM_CREATE message;
      instead, the dialog box procedure performs initialization during the
      special WM_INITDIALOG message.

The WM_INITDIALOG message is the first message the dialog box procedure re-
ceives. This message is sent only to dialog box procedures. If the dialog
box procedure  returns TRUE, then Windows sets the input focus to the first
child window control in the dialog box that has a WS_TABSTOP style (which
I'll explain in the discussion of ABOUT2). In this dialog box, the first
child window control that has a WS_TABSTOP style is the push button.
Alternatively, during processing of WM_INITDIALOG the dialog box procedure
can use SetFocus to set the focus to one of the child window controls in the
dialog box and then return FALSE.

The only other message this dialog box processes is WM_COMMAND. This is the
message the push-button control sends to its parent window either when the
button is clicked with the mouse or when the Spacebar is pressed while the
button has the input focus. The ID of the control (which we set to IDOK in
the dialog box template) is in wParam. For this message, the dialog box
procedure calls EndDialog, which tells Windows to destroy the dialog box.
For all other messages, the dialog box procedure returns FALSE to tell the
dialog box window procedure within Windows that our dialog box procedure did
not process the message.

The messages for a modal dialog box don't go through your program's message
queue, so you needn't worry about the effect of keyboard accelerators within
the dialog box.


Exporting the Dialog Box Procedure

Because this dialog box procedure is called from outside the program, it
must be included in the EXPORTS section of the module definition file:

EXPORTS   WndProc
          AboutDlgProc

This is the easiest part of the job but also the easiest to forget. I forget
to export the dialog box procedure about one time in four. Often the
function will seem to work (more or less), but because it's not using the
program's data segment, it could be altering data inside Windows' data
segment. Our simple dialog box procedure doesn't reference anything in
ABOUT1's data segment, so strictly speaking, exporting the function is not
required. But get into the habit of exporting the function; I hope you
achieve a better track record than mine.


Invoking the Dialog Box

During processing of WM_CREATE, the program's instance handle is obtained
(and stored in a static variable) and MakeProcInstance is called to create
an instance thunk for the dialog procedure. The pointer to the instance
thunk is also stored in a static variable:

hInstance = ((LPCREATESTRUCT) lParam)->hInstance ;
lpfnAboutDlgProc = MakeProcInstance (AboutDlgProc, hInstance) ;

The MakeProcInstance function assures that AboutDlgProc obtains the correct
data segment address for this instance of ABOUT1.

The program checks for WM_COMMAND messages where wParam is equal to
IDM_ABOUT. When it gets one, the program calls DialogBox:

DialogBox (hInstance, "AboutBox", hwnd, lpfnAboutDlgProc) ;

This function requires the instance handle (saved during WM_CREATE), the
name of the dialog box (as defined in the resource script), the parent of
the dialog box (which is the program's main window), and the address of the
instance thunk return from MakeProcInstance. If you use a number rather than
a name for the dialog box template, you can convert it to a string using the
MAKEINTRESOURCE macro.

Selecting "About About1..." from the menu displays the dialog box, as shown
in Figure 10-2. You can end this dialog box by clicking the OK button with
the mouse, by pressing the Spacebar, or by pressing Enter. For any dialog
box that contains a default push button, Windows sends a WM_COMMAND message
to the dialog box, with wParam equal to the ID of the default push button
when Enter or the Spacebar is pressed.

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

The DialogBox function you call to display the dialog box will not return
control to WndProc until the dialog box is ended. The value returned from
DialogBox is the second parameter to the EndDialog function called within
the dialog box procedure. (This value is not used in ABOUT1 but is used in
ABOUT2.) WndProc can then return control to Windows.

Even when the dialog box is displayed, WndProc can continue to receive
messages. In fact, you can send messages to WndProc from within the dialog
box procedure. ABOUT1's main window is the parent of the dialog box popup
window, so the SendMessage call in AboutDlgProc would start off like this:

SendMessage (GetParent (hDlg),  . . . ) ;

If you have a lot of dialog boxes within your program, you may not want to
create and save instance thunks for all of them. You can instead create
instance thunks as needed and free them after DialogBox returns:

lpfnDlgProc = MakeProcInstance (AboutDlgProc, hInstance) ;
DialogBox (hInstance, "AboutBox", hwnd, lpfnDlgProc) ;
FreeProcInstance (lpfnDlgProc) ;


More on the Dialog Box Style

The window style of the dialog box is specified in the STYLE line of the
dialog box template. For ABOUT1, we used a style that is most common for
modal dialog boxes:

STYLE WS_POPUP | WS_DLGFRAME

However, you can also experiment with other styles. For example, you can
try:

STYLE WS_POPUP | WS_CAPTION

This creates a dialog box with a caption bar and a normal window border. The
caption bar allows the user to move the dialog box around the display by
using the mouse. When you use WS_CAPTION, the x- and y-coordinates specified
in the DIALOG statement are the coordinates of the dialog box's client area,
relative to the upper left corner of the parent window's client area. The
caption bar will be shown above the y-coordinate.

If you have a caption bar, you can put text in it using the CAPTION
statement in the dialog box template:

CAPTION "Dialog Box Caption"

following the STYLE statement.  Or while processing the WM_INITDIALOG
message in the dialog procedure, you can use:

SetWindowText (hDlg, "Dialog Box Caption") ;

If you use the WS_CAPTION style, you can also add a system menu box with the
WS_SYSMENU style:

STYLE WS_POPUP | WS_CAPTION | WS_SYSMENU

This style allows the user to select Move or Close from the system menu.

Adding WS_THICKFRAME to the style allows the user to resize the dialog box,
although resizing is unusual for a dialog box. If you don't mind being a
little unusual, you can also try adding WS_MAXIMIZEBOX to the STYLE
statement.

The STYLE statement is not required. If you do not include a STYLE or
CAPTION statement in the template, the default style is:

WS_POPUP | WS_BORDER

But this is rather dull looking. WS_DLGFRAME produces much more attractive
results. If you include a CAPTION statement with a STYLE statement, the
default style is:

WS_POPUP | WS_CAPTION | WS_SYSMENU

You can also add a menu to a dialog box by specifying:

MENU menu-name

in the dialog box template. The argument is either the name or number of a
menu in the resource script. Menus are highly uncommon for modal dialog
boxes. If you use one, be sure that all the ID numbers in the menu and the
dialog box controls are unique.

Although the dialog box window procedure is normally within Windows, you can
use one of your own window procedures to process dialog box messages. To do
so, you specify a window class name in the dialog box template:

CLASS "class-name"

This approach is rare, but we'll use it in the HEXCALC program shown later
in this chapter.

When you call DialogBox specifying the name of a dialog box template,
Windows has almost everything it needs to create a popup window by calling
the normal CreateWindow function. Windows obtains the coordinates and size
of the window, the window style, the caption, and the menu from the dialog
box template. Windows gets the instance handle and the parent window handle
from the parameters to DialogBox. The only other piece of information it
needs is a window class (assuming the dialog box template does not specify
one). Windows registers a special window class for dialog boxes. The window
procedure for this window class has access to the pointer to your dialog box
procedure (which you provide in the DialogBox call), so it can keep your
program informed of messages that this popup window receives. Of course, you
can create and maintain your own dialog box by creating the popup window
yourself. Using DialogBox is simply an easier approach.


More on Defining Controls

In the dialog box template in ABOUT1.RC, we used the shorthand notation
CTEXT, ICON, and DEFPUSHBUTTON to define the 3 types of child window
controls we wanted in the dialog box. There are 10 others you can use. Each
type implies a particular predefined window class and a window style. The
following table shows the equivalent window class and window style for each
of the 13 control types:


Control Type   Window Class  Window Style
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
PUSHBUTTON     button        BS_PUSHBUTTON | WS_TABSTOP
DEFPUSHBUTTON  button        BS_DEFPUSHBUTTON | WS_TABSTOP
CHECKBOX       button        BS_CHECKBOX | WS_TABSTOP
RADIOBUTTON    button        BS_RADIOBUTTON | WS_TABSTOP
GROUPBOX       button        BS_GROUPBOX | WS_TABSTOP
LTEXT          static        SS_LEFT | WS_GROUP
CTEXT          static        SS_CENTER | WS_GROUP
RTEXT          static        SS_RIGHT  | WS_GROUP
ICON           static        SS_ICON
EDITTEXT       edit          ES_LEFT | WS_BORDER | WS_TABSTOP
SCROLLBAR      scrollbar     SBS_HORZ
LISTBOX        listbox       LBS_NOTIFY | WS_BORDER | WS_VSCROLL
COMBOBOX       combobox      CBS_SIMPLE | WS_TABSTOP


The RC resource compiler is the only program that understands this shorthand
notation. In addition to the window styles shown above, each of these
controls has the style:

WS_CHILD | WS_VISIBLE

For all these control types except EDITTEXT, SCROLLBAR, LISTBOX, and
COMBOBOX, the format of the control statement is:

control-type "text", nID, xPos, yPos, xWidth, yHeight, dwStyle

For EDITTEXT, SCROLLBAR, LISTBOX, and COMBOBOX, the format is:

control-type nID, xPos, yPos, xWidth, yHeight, dwStyle

which excludes the text field. In both statements, the dwStyle parameter is
optional.

In Chapter 6, I discussed rules for determining the width and height of
predefined child window controls. You might want to refer back to that
chapter for these rules, keeping in mind that sizes specified in dialog box
templates are always in terms of 1/4 the average character width and 1/8 the
character height.

The "style" field of the control statements is optional. It allows you to
include other window style identifiers. For instance, if you wanted to
create a check box consisting of text to the left of a square box, you could
use:

CHECKBOX "text", nID, xPos, yPos, xWidth, yHeight, BS_LEFTTEXT

While the shorthand notation for child window controls is convenient, it is
also incomplete. You can't create a child window edit control without a
border, for example.  For this reason, the RC resource compiler also
recognizes a generalized control statement that looks like this:

CONTROL "text", nID, "class", dwStyle, xPos, yPos, xWidth, yHeight

This statement allows you to create any type of child window control by
specifying the window class and the complete window style. For example,
instead of using:

PUSHBUTTON "OK", IDOK, 10, 20, 32, 14

you can use:

CONTROL "OK", IDOK, "button", WS_CHILD | WS_VISIBLE |
          BS_PUSHBUTTON | WS_TABSTOP, 10, 20, 32, 14

When the resource script is compiled, these two statements are encoded
identically in the .RES file and the .EXE file.

When you use CONTROL statements in a dialog box template, you don't need to
include the WS_CHILD and WS_VISIBLE styles. Windows includes these in the
window style when creating the child windows. The format of the CONTROL
statement also clarifies what the Windows dialog manager does when it
creates a dialog box. First, as I described earlier, it creates a popup
window whose parent is the window handle that was provided in the DialogBox
function. Then for each control in the dialog template, the dialog box
manager creates a child window. The parent of each of these controls is the
popup dialog box. The CONTROL statement shown above is translated into a
CreateWindow call that looks like this:

CreateWindow ("button", "OK",
     WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
     10 * cxChar / 4, 20 * cyChar / 8,
     32 * cxChar / 4, 14 * cyChar / 8,
     hDlg, nID, hInstance, NULL) ;

where cxChar and cyChar are the width and height of a system font character
in pixels. The hDlg parameter is returned from the CreateWindow call that
creates the dialog box window. The hInstance parameter is obtained from the
original DialogBox call.


A More Complex Dialog Box

The simple dialog box in ABOUT1 demonstrates the basics of getting a dialog
box up and running; now let's try something a little more complex. The
ABOUT2 program, shown in Figure 10-3, demonstrates how to manage controls
(in this case, radio buttons) within a dialog box procedure and also how to
paint on the client area of the dialog box.

 ABOUT2.MAK

#----------------------
# ABOUT2.MAK make file
#----------------------

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

about2.obj : about2.c about2.h
     cl -c -Gsw -Ow -W2 -Zp about2.c

about2.res : about2.rc about2.h about2.ico
     rc -r about2.rc

 ABOUT2.C

/*------------------------------------------
   ABOUT2.C -- About Box Demo Program No. 2
               (c) Charles Petzold, 1990
  ------------------------------------------*/

#include 
#include "about2.h"

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

short nCurrentColor  = IDD_BLACK,
      nCurrentFigure = IDD_RECT ;

int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance,
                    LPSTR lpszCmdLine, int nCmdShow)
     {
     static char szAppName [] = "About2" ;
     MSG         msg ;
     HWND        hwnd ;
     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 (hInstance, szAppName) ;
          wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;
          wndclass.hbrBackground = GetStockObject (WHITE_BRUSH) ;
          wndclass.lpszMenuName  = szAppName ;
          wndclass.lpszClassName = szAppName ;

          RegisterClass (&wndclass) ;
          }

     hwnd = CreateWindow (szAppName, "About Box Demo Program",
                          WS_OVERLAPPEDWINDOW,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          NULL, NULL, hInstance, NULL) ;

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

     while (GetMessage (&msg, NULL, 0, 0))
          {
          TranslateMessage (&msg) ;
          DispatchMessage (&msg) ;
          }
     return msg.wParam ;
     }
void PaintWindow (HWND hwnd, short nColor, short nFigure)
     {
     static DWORD dwColor [8] = { RGB (0,     0, 0), RGB (  0,   0, 255),
                                  RGB (0,   255, 0), RGB (  0, 255, 255),
                                  RGB (255,   0, 0), RGB (255,   0, 255),
                                  RGB (255, 255, 0), RGB (255, 255, 255) } ;
     HBRUSH       hBrush ;
     HDC          hdc ;
     RECT         rect ;

     hdc = GetDC (hwnd) ;
     GetClientRect (hwnd, &rect) ;
     hBrush = CreateSolidBrush (dwColor [nColor - IDD_BLACK]) ;
     hBrush = SelectObject (hdc, hBrush) ;

     if (nFigure == IDD_RECT)
          Rectangle (hdc, rect.left, rect.top, rect.right, rect.bottom) ;
     else
          Ellipse   (hdc, rect.left, rect.top, rect.right, rect.bottom) ;

     DeleteObject (SelectObject (hdc, hBrush)) ;
     ReleaseDC (hwnd, hdc) ;
     }

void PaintTheBlock (HWND hCtrl, short nColor, short nFigure)
     {
     InvalidateRect (hCtrl, NULL, TRUE) ;
     UpdateWindow (hCtrl) ;
     PaintWindow (hCtrl, nColor, nFigure) ;
     }

BOOL FAR PASCAL AboutDlgProc (HWND hDlg, WORD message, WORD wParam, LONG
lParam)
     {
     static HWND  hCtrlBlock ;
     static short nColor, nFigure ;

     switch (message)
          {
          case WM_INITDIALOG :
               nColor  = nCurrentColor ;
               nFigure = nCurrentFigure ;

               CheckRadioButton (hDlg, IDD_BLACK, IDD_WHITE, nColor) ;
               CheckRadioButton (hDlg, IDD_RECT,  IDD_ELL,   nFigure) ;

               hCtrlBlock = GetDlgItem (hDlg, IDD_PAINT) ;
               SetFocus (GetDlgItem (hDlg, nColor)) ;
               return FALSE ;
     switch (message)
          {
          case WM_CREATE :
               hInstance = ((LPCREATESTRUCT) lParam)->hInstance ;

               lpfnAboutDlgProc = MakeProcInstance (AboutDlgProc, hInstance)
;
               return 0 ;

          case WM_COMMAND :
               switch (wParam)
                    {
                    case IDM_ABOUT :
                         if (DialogBox (hInstance, "AboutBox", hwnd,
                                        lpfnAboutDlgProc))
                              InvalidateRect (hwnd, NULL, TRUE) ;
                         return 0 ;
                    }
               break ;

          case WM_PAINT :
               BeginPaint (hwnd, &ps) ;
               EndPaint (hwnd, &ps) ;
               PaintWindow (hwnd, nCurrentColor, nCurrentFigure) ;
               return 0 ;

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

 ABOUT2.RC

/*---------------------------
   ABOUT2.RC resource script
  ---------------------------*/

#include 
#include "about2.h"

about2 ICON about2.ico

About2 MENU
     {
     POPUP "&Help"
          {
          MENUITEM "&About About2...",       IDM_ABOUT
          }
     }

#define TABGRP (WS_TABSTOP | WS_GROUP)

AboutBox DIALOG 20, 20, 140, 188
     STYLE WS_POPUP | WS_DLGFRAME
     {
     CTEXT       "About2"       -1,           0,  12, 140,   8
     ICON        "About2"       -1,           8,   8,   0,   0
     CTEXT       "About Box Demo Program" -1, 4,  36, 130,   8
     CTEXT       ""             IDD_PAINT,   68,  54,  60,  60
     GROUPBOX    "&Color"       -1,           4,  50,  54, 112
     RADIOBUTTON "&Black"       IDD_BLACK,    8,  60,  40,  12, TABGRP
     RADIOBUTTON "B&lue"        IDD_BLUE,     8,  72,  40,  12
     RADIOBUTTON "&Green"       IDD_GREEN,    8,  84,  40,  12
     RADIOBUTTON "Cya&n"        IDD_CYAN,     8,  96,  40,  12
     RADIOBUTTON "&Red"         IDD_RED,      8, 108,  40,  12
     RADIOBUTTON "&Magenta"     IDD_MAGENTA,  8, 120,  40,  12
     RADIOBUTTON "&Yellow"      IDD_YELLOW,   8, 132,  40,  12
     RADIOBUTTON "&White"       IDD_WHITE,    8, 144,  40,  12
     GROUPBOX    "&Figure"      -1,          68, 120,  60,  40, WS_GROUP
     RADIOBUTTON "Rec&tangle"   IDD_RECT,    72, 134,  50,  12, TABGRP
     RADIOBUTTON "&Ellipse"     IDD_ELL,     72, 146,  50,  12
     DEFPUSHBUTTON "OK"         IDOK,        20, 168,  40,  14, WS_GROUP
     PUSHBUTTON  "Cancel"       IDCANCEL,    80, 168,  40,  14, WS_GROUP
     }

 ABOUT2.H

/*----------------------
   ABOUT2.H header file
  ----------------------*/

#define IDM_ABOUT      1

#define IDD_BLACK     10
#define IDD_BLUE      11
#define IDD_GREEN     12
#define IDD_CYAN      13
#define IDD_RED       14
#define IDD_MAGENTA   15
#define IDD_YELLOW    16
#define IDD_WHITE     17
#define IDD_RECT      20
#define IDD_ELL       21

#define IDD_PAINT     30

 ABOUT2.ICO  -- Please refer to the book.

FIG 1003.EPB

 ABOUT2.DEF

;-----------------------------------
; ABOUT2.DEF module definition file
;-----------------------------------

NAME           ABOUT2

DESCRIPTION    'About Box Demo No. 2 (c) Charles Petzold, 1990'
EXETYPE        WINDOWS
STUB           'WINSTUB.EXE'
CODE           PRELOAD MOVEABLE DISCARDABLE
DATA           PRELOAD MOVEABLE MULTIPLE
HEAPSIZE       1024
STACKSIZE      8192
EXPORTS        WndProc
               AboutDlgProc

The About box in ABOUT2 has two groups of radio buttons. One group is used
to select a color, and the other group is used to select either a rectangle
or an ellipse. The rectangle or ellipse is shown in the dialog box with the
interior colored with the current color selection. If you press the OK
button, the dialog box is ended, and the program's window procedure draws
the selected figure on its own client area. If you press Cancel, the client
area of the main window remains the same. The dialog box is shown in Figure
10-4 on the following page. Although the ABOUT2 dialog box uses the
predefined identifiers IDOK and IDCANCEL for the two push buttons, each of
the radio buttons has its own identifier beginning with the letters IDD ("ID
for dialog box control"). These identifiers are defined in ABOUT2.H.

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


Working with Dialog Box Controls

In Chapter 6, you discovered that most child window controls send WM_COMMAND
messages to the parent window. (The exception is scroll bar controls.) You
also saw that the parent window can alter child window controls (for
instance, checking or unchecking radio buttons or check boxes) by sending
messages to the controls. You can similarly alter controls in a dialog box
procedure. If you have a series of radio buttons, for example, you can check
and uncheck the buttons by sending them messages. However, Windows also
provides several shortcuts when working with controls in dialog boxes. Let's
look at the way in which the dialog box procedure and the child window
controls communicate.

The dialog box template for ABOUT2 is shown in the ABOUT2.RC resource script
in Figure 10-3. The GROUPBOX control is simply a frame with a title (either
Color or Figure) that surrounds each of the two groups of radio buttons. The
eight radio buttons in the first group are mutually exclusive, as are the
two radio buttons in the second group.

When one of the radio buttons is clicked with the mouse (or when the
Spacebar is pressed while the radio button has the input focus), the child
window sends its parent a WM_COMMAND message with wParam set to the ID of
the control. The low word of lParam is the window handle of the control, and
the high word of lParam is a notification code. For a radio button, this
notification code is BN_CLICKED, or 0. The dialog box window procedure in
Windows then passes this WM_COMMAND message to the dialog box procedure
within ABOUT2.C. When the dialog box procedure receives a WM_COMMAND message
for one of the radio buttons, it turns on the check mark for that button and
turns off the check marks for all the other buttons in the group.

You may recall from Chapter 6 that checking and unchecking a button requires
that you send the child window control a BM_CHECK message. To turn on a
button check, you use:

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

To turn off the check, you use:

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

The hwndCtrl parameter is the window handle of the child window button
control.

But this method presents a little problem in the dialog box procedure,
because you don't know the window handles of all radio buttons. You know
only the one you're getting the message from. Fortunately, Windows provides
you with a function to obtain the window handle of a dialog box control
using the dialog box window handle and the control ID:

hwndCtrl = GetDlgItem (hDlg, nID) ;

(You can also obtain the ID value of a control from the window handle by
using this function:

nID = GetWindowWord (hwndCtrl, GWW_ID) ;

but this is rarely necessary.)

You'll notice in the ABOUT2.H header file shown in Figure 10-3 that the ID
values for the eight colors are sequential from IDD_BLACK to IDD_WHITE. This
arrangement helps in processing the WM_COMMAND messages from the radio
buttons. For a first attempt at checking and unchecking the radio buttons,
you might try something like the following in the dialog box procedure:

static short nColor ;
[other program lines]
case WM_COMMAND :
     switch (wParam)
          {
[other program lines]
          case IDD_BLACK :
          case IDD_RED :
          case IDD_GREEN :
          case IDD_YELLOW :
          case IDD_BLUE :
          case IDD_MAGENTA :
          case IDD_CYAN :
          case IDD_WHITE :
               nColor = wParam ;

               for (n = IDD_BLACK, n <= IDD_WHITE, n++)
                    SendMessage (GetDlgItem (hDlg, n),
                         BM_SETCHECK, n == wParam, 0L) ;
               return TRUE ;
[other program lines]

This approach works satisfactorily. You've saved the new color value in
nColor, and you've also set up a loop that cycles through all the ID values
for the eight colors. You obtain the window handle of each of these eight
radio button controls and use SendMessage to send each handle a BM_SETCHECK
message. The wParam value of this message is set to 1 only for the button
that sent the WM_COMMAND message to the dialog box window procedure.

The first shortcut is the special dialog box procedure SendDlgItemMessage:

SendDlgItemMessage (hDlg, nCtrlID, message, wParam, lParam) ;

It is equivalent to:

SendMessage (GetDlgItem (hDlg, nCtrlID), message, wParam, lParam) ;

Now the loop would look like this:

for (n = IDD_BLACK, n <= IDD_WHITE, n++)
     SendDlgItemMessage (hDlg, n, BM_SETCHECK, n == wParam, 0L) ;

That's a little better. But the real breakthrough comes when you discover
the CheckRadioButton function:

CheckRadioButton (hDlg, nIDFirst, nIDLast, nIDCheck) ;

This function turns off the checks on all radio button controls with IDs
from nIDFirst to nIDLast except for the radio button with an ID of nIDCheck,
which is checked. The IDs must be sequential. So we can get rid of the loop
entirely and use:

CheckRadioButton (hDlg, IDD_BLACK, IDD_WHITE, wParam) ;

That's how it's done in the dialog box procedure in ABOUT2.

A similar shortcut function is provided for working with check boxes. If you
create a CHECKBOX dialog window control, you can turn the check mark on and
off using the function:

CheckDlgButton (hDlg, nIDCheckbox, wCheck) ;

If wCheck is set to 1, the button is checked; if it's set to 0, the button
is unchecked. You can obtain the status of a check box in a dialog box
using:

wCheck = IsDlgButtonChecked (hDlg, nIDCheckbox) ;

You can either retain the current status of the check mark as a static
variable within the dialog box procedure, or you can do something like this
to toggle the button on a WM- _COMMAND message:

CheckDlgButton (hDlg, nIDCheckbox,
     !IsDlgButtonChecked (hDlg, nIDCheckbox)) ;

If you define a BS_AUTOCHECKBOX control, then you don't need to process the
WM_COMMAND message at all. You can simply obtain the current status of the
button using IsDlgButtonChecked before terminating the dialog box.


The OK and Cancel Buttons

ABOUT2 has two push buttons, labeled OK and Cancel. In the dialog box
template in ABOUT2.RC, the OK button has an ID of IDOK (defined in WINDOWS.H
as 1) and the Cancel button an ID of IDCANCEL (defined in WINDOWS.H as 2).
The OK button is the default:

DEFPUSHBUTTON "OK"     IDOK,     20, 168, 40, 14, WS_GROUP
PUSHBUTTON    "Cancel" IDCANCEL, 80, 168, 40, 14, WS_GROUP

This arrangement is normal for OK and Cancel buttons in dialog boxes; having
the OK button as the default helps out with the keyboard interface. Here's
how: Normally, you would end the dialog box by clicking one of these buttons
with the mouse or pressing the Spacebar when the desired button has the
input focus. However, the dialog box window procedure also generates a
WM_COMMAND message when the user presses Enter, regardless of which control
has the input focus. The value of wParam is set to the ID value of the
default push button in the dialog box unless another push button has the
input focus. In that case, wParam is set to the ID of the push button with
the input focus. If no push button in the dialog box is a default push
button, then Windows sends the dialog box procedure a WM_COMMAND message
with wParam equal to IDOK. If the user presses the Esc key or Ctrl-Break,
Windows sends the dialog box procedure a WM_COMMAND message with wParam
equal to IDCANCEL. So you don't have to add separate keyboard logic to the
dialog box procedure, because the keystrokes that normally terminate a
dialog box are translated by Windows into WM_COMMAND messages for these two
push buttons.

The AboutDlgProc function handles these two WM_COMMAND messages by calling
EndDialog:

switch (wParam)
     {
     case IDOK :
          nCurrentColor  = nColor ;
          nCurrentFigure = nFigure ;
          EndDialog (hDlg, TRUE) ;
          return TRUE ;

     case IDCANCEL :
          EndDialog (hDlg, FALSE) ;
          return TRUE ;

ABOUT2's window procedure uses the global variables nCurrentColor and
nCurrentFigure when drawing the rectangle or ellipse in the program's client
area. AboutDlgProc uses the static local variables nColor and nFigure when
drawing the figure within the dialog box.

Notice the different values in the second parameter of EndDialog. This is
the value that is passed back as the return value from the original
DialogBox function in WndProc:

case IDM_ABOUT :
     if (DialogBox (hInstance, "AboutBox", hwnd, lpfnAboutDlgProc))
          InvalidateRect (hwnd, NULL, TRUE) ;
     return 0 ;

If DialogBox returns TRUE (nonzero), meaning that the OK button was pressed,
then the WndProc client area needs to be updated with the new figure and
color. These were saved in the global variables nCurrentColor and
nCurrentFigure by AboutDlgProc when it received a WM_COMMAND message with
wParam equal to IDOK. If DialogBox returns FALSE, the main window continues
to use the original settings of nCurrentColor and nCurrentFigure.

TRUE and FALSE are commonly used in EndDialog calls to signal to the main
window procedure whether the user ended the dialog box with OK or Cancel.
However, the parameter to EndDialog is actually an int, and DialogBox
returns an int, so it's possible to return more information in this way than
simply TRUE or FALSE.


Tab Stops and Groups

In Chapter 6, we used window subclassing to add a facility to COLORS1 that
let us move from one scroll bar to another by pressing the Tab key. In a
dialog box, window subclassing is unnecessary: Windows does all the logic
for moving the input focus from one control to another. However, you have to
help out by using the WS_TABSTOP and WS_GROUP window styles in the dialog
box template. For all controls that you want to access using the Tab key,
specify WS_TABSTOP in the window style. If you refer back to the table on
page 415, you'll notice that many of the controls include WS_TABSTOP as a
default, while others do not. Generally the controls that do not include
WS_TABSTOP style (particularly the static controls) should not get the input
focus because they can't do anything with it. Unless you set the input focus
to a specific control in a dialog box during processing of the WM_INITDIALOG
message and return FALSE from the message, Windows sets the input focus to
the first control in the dialog box that has the WS_TABSTOP style.

The second keyboard interface that Windows adds to a dialog box involves the
cursor movement keys. This interface is of particular importance with radio
buttons. After you use the Tab key to move to the currently checked radio
button within a group, you need to use the cursor movement keys to change
the input focus from that radio button to other radio buttons within the
group. You accomplish this by using the WS_GROUP window style. For a
particular series of controls in the dialog box template, Windows will use
the cursor movement keys to shift the input focus from the first control
that has the WS_GROUP style up to (but not including) the next control that
has the WS_GROUP style. Windows will cycle from the last control in a dialog
box to the first control if necessary to find the end of the group.

By default, the controls LTEXT, CTEXT, RTEXT, and ICON include the WS_GROUP
style, which conveniently marks the end of a group. You often have to add
WS_GROUP styles to other types of controls.

Let's look at the dialog box template in ABOUT2.RC:

AboutBox DIALOG 20, 20, 140, 188
     STYLE WS_POPUP | WS_DLGFRAME
     {
     CTEXT       "About2"       -1,           0,  12, 140,   8
     ICON        "About2"       -1,           8,   8,   0,   0
     CTEXT       "About Box Demo Program" -1, 4,  36, 130,   8
     CTEXT       ""             IDD_PAINT,   68,  54,  60,  60
     GROUPBOX    "&Color"       -1,           4,  50,  54, 112
     RADIOBUTTON "&Black"       IDD_BLACK,    8,  60,  40,  12, TABGRP
     RADIOBUTTON "B&lue"        IDD_BLUE,     8,  72,  40,  12
     RADIOBUTTON "&Green"       IDD_GREEN,    8,  84,  40,  12
     RADIOBUTTON "Cya&n"        IDD_CYAN,     8,  96,  40,  12
     RADIOBUTTON "&Red"         IDD_RED,      8, 108,  40,  12
     RADIOBUTTON "&Magenta"     IDD_MAGENTA,  8, 120,  40,  12
     RADIOBUTTON "&Yellow"      IDD_YELLOW,   8, 132,  40,  12
     RADIOBUTTON "&White"       IDD_WHITE,    8, 144,  40,  12
     GROUPBOX    "&Figure"      -1,          68, 120,  60,  40, WS_GROUP
     RADIOBUTTON "Rec&tangle"   IDD_RECT,    72, 134,  50,  12, TABGRP
     RADIOBUTTON "&Ellipse"     IDD_ELL,     72, 146,  50,  12
     DEFPUSHBUTTON "OK"         IDOK,        20, 168,  40,  14, WS_GROUP
     PUSHBUTTON  "Cancel"       IDCANCEL,    80, 168,  40,  14, WS_GROUP
     }

To simplify the appearance of the template, an identifier is defined in
ABOUT2.RC that combines WS_TABSTOP and WS_GROUP:

#define TABGRP (WS_TABSTOP | WS_GROUP)

The four controls that have the WS_TABSTOP style are the first radio buttons
of each group (explicitly included) and the two push buttons (by default).
When you first invoke the dialog box, these are the four controls you can
move among using the Tab key.

Within each group of radio buttons, you use the cursor movement keys to
change the input focus and the check mark. For example, the first radio
button of the Color group (Black) and the group box labeled Figure have the
WS_GROUP style. This means that you can use the cursor movement keys to move
the focus from the Black radio button up to (but not including) the Figure
group box. Similarly, the first radio button of the Figure group (Rectangle)
and DEFPUSHBUTTON have the WS_GROUP style, so you can use the cursor
movement keys to move between the two radio buttons in this group: Rectangle
and  Ellipse. Both push buttons get a WS_GROUP style to prevent the cursor
movement keys from doing anything when the push buttons have the input
focus.

You'll notice when using ABOUT2 that the dialog box manager in Windows
performs some magic in the two groups of radio buttons. As expected, the
cursor movement keys within a group of radio buttons shift the input focus
and send a WM_COMMAND message to the dialog box procedure. But when you
change the checked radio button within the group, Windows also assigns the
newly checked radio button the WS_TABSTOP style. The next time you tab to
that group, Windows will set the input focus to the checked radio button.

An ampersand (&) causes the letter that follows to be underlined and adds
another keyboard interface. You can move the input focus to any of the radio
buttons by pressing the underlined letter. By pressing C (for the Color
group box) or F (for the Figure group box), you can move the input focus to
the currently checked radio button in that group.

Although programmers normally let the dialog box manager take care of all
this, Windows includes two functions that let you search for the next or
previous tab stop or group item. These functions are:

hwndCtrl = GetNextDlgTabItem (hDlg, hwndCtrl, bPrevious) ;

and:

hwndCtrl = GetNextDlgGroupItem (hDlg, hwndCtrl, bPrevious) ;

If bPrevious is TRUE, the functions return the previous tab stop or group
item; if FALSE, they return the next tab stop or group item.


Painting on the Dialog Box

ABOUT2 also does something relatively unusual: It paints on the dialog box.
Let's see how this works. Within the dialog box template in ABOUT2.RC, a
blank text control is defined with a position and size for the area we want
to paint:

CTEXT  ""  IDD_PAINT, 68, 54, 60, 60

This area is 15 characters wide and 7-1/2 characters high. Because this
control has no text, all that the window procedure for the "static" class
does is erase the background when the child window control has to be
repainted.

When the current color or figure selection changes or when the dialog box
itself gets a WM_PAINT message, the dialog box procedure calls
PaintTheBlock, which is a function in ABOUT2.C:

PaintTheBlock (hCtrlBlock, nColor, nFigure) ;

The window handle hCtrlBlock had been set during processing of the
WM_INITDIALOG message:

hCtrlBlock = GetDlgItem (hDlg, IDD_PAINT) ;

Here's the PaintTheBlock function:

void PaintTheBlock (HWND hCtrl, short nColor, short nFigure)
     {
     InvalidateRect (hCtrl, NULL, TRUE) ;
     UpdateWindow (hCtrl) ;
     PaintWindow (hCtrl, nColor, nFigure) ;
     }

This invalidates the child window control, updates it, and then calls
another function in ABOUT2 called PaintWindow.

The PaintWindow function obtains a device context handle for hCtrl and draws
the selected figure, filling it with a colored brush based on the selected
color. The size of the child window control is obtained from GetClientRect.
Although the dialog box template defines the size of the control in terms of
characters, GetClientRect obtains the dimensions in pixels. You can also use
the function MapDialogRect to convert the character coordinates in the
dialog box to pixel coordinates in the client area.

We're not really painting the dialog box's client area--we're actually
painting the client area of the child window control. Whenever the dialog
box gets a WM_PAINT message, the child window control is invalidated and
then updated to make it believe that its client area is now valid. We then
paint on top of it.


Using Other Functions with Dialog Boxes

Most functions that you can use with child windows you can also use with
controls in a dialog box. For instance, if you're feeling devious, you can
use MoveWindow to move the controls around the dialog box and have the user
chase them around with the mouse.

Sometimes you need to dynamically enable or disable certain controls in a
dialog box, depending on the settings of other controls. This call:

EnableWindow (hwndCtrl, bEnable) ;

enables the control where bEnable is TRUE (nonzero) and disables it where
bEnable is FALSE (0). When a control is disabled, it receives no keyboard or
mouse input. Don't disable a control that has the input focus.


Defining Your Own Controls

Although Windows assumes much of the responsibility for maintaining the
dialog box and child window controls, various methods let you slip some of
your own code into this process. We've already seen a method that allows you
to paint on the surface of a dialog box. You can also use window subclassing
(discussed in Chapter 6) to alter the operation of child window controls.

You can also define your own child window controls and use them in a dialog
box. For example, suppose you don't particularly care for the normal
rectangular push buttons and would prefer to create elliptical push buttons.
You can do this by registering a window class and using your own window
procedure to process messages for your customized child window. You then
specify this window class in a CONTROL statement in the dialog box template.
The ABOUT3 program, shown in Figure 10-5, does exactly that.

 ABOUT3.MAK

#----------------------
# ABOUT3.MAK make file
#----------------------

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

about3.obj : about3.c about3.h
     cl -c -Gsw -Ow -W2 -Zp about3.c

about3.res : about3.rc about3.h about3.ico
     rc -r about3.rc

 ABOUT3.C

/*------------------------------------------
   ABOUT3.C -- About Box Demo Program No. 3
               (c) Charles Petzold, 1990
  ------------------------------------------*/

#include 
#include "about3.h"

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

int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance,
                    LPSTR lpszCmdLine, int nCmdShow)
     {
     static char szAppName [] = "About3" ;
     MSG         msg ;
     HWND        hwnd ;
     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 (hInstance, szAppName) ;
          wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;
          wndclass.hbrBackground = GetStockObject (WHITE_BRUSH) ;
          wndclass.lpszMenuName  = szAppName ;
          wndclass.lpszClassName = szAppName ;

          RegisterClass (&wndclass) ;

          wndclass.style         = CS_HREDRAW | CS_VREDRAW ;
          wndclass.lpfnWndProc   = EllipPushWndProc ;
          wndclass.cbClsExtra    = 0 ;
          wndclass.cbWndExtra    = 0 ;
          wndclass.hInstance     = hInstance ;
          wndclass.hIcon         = NULL ;
          wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;
          wndclass.hbrBackground = COLOR_WINDOW + 1 ;
          wndclass.lpszMenuName  = NULL ;
          wndclass.lpszClassName = "EllipPush" ;

          RegisterClass (&wndclass) ;
          }

     hwnd = CreateWindow (szAppName, "About Box Demo Program",
                          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 ;
     }

BOOL FAR PASCAL AboutDlgProc (HWND hDlg, WORD message, WORD wParam, LONG
lParam)
     {
     switch (message)
          {
          case WM_INITDIALOG :
               return TRUE ;
          case WM_COMMAND :
               switch (wParam)
                    {
                    case IDOK :
                         EndDialog (hDlg, 0) ;
                         return TRUE ;
                    }
               break ;
          }
     return FALSE ;
     }

long FAR PASCAL WndProc (HWND hwnd, WORD message, WORD wParam, LONG lParam)
     {
     static FARPROC lpfnAboutDlgProc ;
     static HANDLE  hInstance ;

     switch (message)
          {
          case WM_CREATE :
               hInstance = ((LPCREATESTRUCT) lParam)->hInstance ;

               lpfnAboutDlgProc = MakeProcInstance (AboutDlgProc, hInstance)
;
               return 0 ;

          case WM_COMMAND :
               switch (wParam)
                    {
                    case IDM_ABOUT :
                         DialogBox (hInstance, "AboutBox", hwnd,
                                        lpfnAboutDlgProc) ;
                         return 0 ;
                    }
               break ;

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

long FAR PASCAL EllipPushwndProc (HWND hwnd, WORD message,
                                  WORD wParam, LONG lParam)
     {
     char        szText [40] ;
     HBRUSH      hBrush ;
     HDC         hdc ;
     PAINTSTRUCT ps ;
     RECT        rect ;
     switch (message)
          {
          case WM_PAINT :
               GetClientRect (hwnd, &rect) ;
               GetWindowText (hwnd, szText, sizeof szText) ;

               hdc = BeginPaint (hwnd, &ps) ;

               hBrush = CreateSolidBrush (GetSysColor (COLOR_WINDOW)) ;
               hBrush = SelectObject (hdc, hBrush) ;
               SetBkColor (hdc, GetSysColor (COLOR_WINDOW)) ;
               SetTextColor (hdc, GetSysColor (COLOR_WINDOWTEXT)) ;

               Ellipse (hdc, rect.left, rect.top, rect.right, rect.bottom) ;
               DrawText (hdc, szText, -1, &rect,
                              DT_SINGLELINE | DT_CENTER | DT_VCENTER) ;

               DeleteObject (SelectObject (hdc, hBrush)) ;

               EndPaint (hwnd, &ps) ;
               return 0 ;

          case WM_KEYUP :
               if (wParam != VK_SPACE)
                    break ;
                                        // fall through
          case WM_LBUTTONUP :
               SendMessage (GetParent (hwnd), WM_COMMAND,
                    GetWindowWord (hwnd, GWW_ID), (LONG) hwnd) ;
               return 0 ;
          }
     return DefWindowProc (hwnd, message, wParam, lParam) ;
     }

 ABOUT3.RC

/*---------------------------
   ABOUT3.RC resource script
  ---------------------------*/

#include 
#include "about3.h"

about3 ICON about3.ico
About3 MENU
     {
     POPUP "&Help"
          {
          MENUITEM "&About About3...",       IDM_ABOUT
          }
     }

#define TABGRP (WS_TABSTOP | WS_GROUP)

AboutBox DIALOG  20, 20, 160, 80
     STYLE WS_POPUP | WS_DLGFRAME
     {
     CTEXT   "About3"                         -1,  0, 12, 160,  8
     ICON    "About3"                         -1,  8,  8,   0,  0
     CTEXT   "About Box Demo Program"         -1,  0, 36, 160,  8
     CTEXT   "(c) Charles Petzold, 1990"      -1,  0, 48, 160,  8
     CONTROL "OK" IDOK, "EllipPush", TABGRP,      64, 60,  32, 14
     }

 ABOUT3.H

/*----------------------
   ABOUT3.H header file
  ----------------------*/

#define IDM_ABOUT      1

 ABOUT3.ICO  -- Please refer to the book.

FIG 10-05.epb

 ABOUT3.DEF

;-----------------------------------
; ABOUT3.DEF module definition file
;-----------------------------------

NAME           ABOUT3

DESCRIPTION    'About Box Demo No. 3 (c) Charles Petzold, 1990'
EXETYPE        WINDOWS
STUB           'WINSTUB.EXE'
CODE           PRELOAD MOVEABLE DISCARDABLE
DATA           PRELOAD MOVEABLE MULTIPLE
HEAPSIZE       1024
STACKSIZE      8192
EXPORTS        WndProc
               AboutDlgProc
               EllipPushWndProc

The window class we'll be registering is called "EllipPush" ("elliptical
push button"). Rather than use a DEFPUSHBUTTON statement in the dialog box
template, we use a CONTROL statement that specifies this window class:

CONTROL "OK" IDOK, "EllipPush", TABGRP, 64, 60, 32, 14

The dialog box manager uses this window class in a CreateWindow call when
creating the child window control in the dialog box.

The ABOUT3.C program registers the "EllipPush" window class in WinMain:

wndclass.style         = CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc   = EllipPushWndProc ;
wndclass.cbClsExtra    = 0 ;
wndclass.cbWndExtra    = 0 ;
wndclass.hInstance     = hInstance ;
wndclass.hIcon         = NULL ;
wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground = COLOR_WINDOW + 1 ;
wndclass.lpszMenuName  = NULL ;
wndclass.lpszClassName = "EllipPush" ;

RegisterClass (&wndclass) ;

The window class specifies that the window procedure is EllipPushWndProc,
which is also in ABOUT3.C.

The EllipPushWndProc window procedure processes only three messages:
WM_PAINT, WM_KEYUP, and WM_LBUTTONUP. During the WM_PAINT message, it
obtains the size of its window from GetClientRect and obtains the text that
appears in the push button from GetWindowText. It uses the Windows functions
Ellipse and DrawText to draw the ellipse and the text.

The processing of the WM_KEYUP and WM_LBUTTONUP messages is very simple:

case WM_KEYUP :
     if (wParam != VK_SPACE)
          break ;
                              // fall through
case WM_LBUTTONUP :
     SendMessage (GetParent (hwnd), WM_COMMAND,
          GetWindowWord (hwnd, GWW_ID), (LONG) hwnd) ;
     return 0 ;

The window procedure obtains the handle of its parent window (the dialog
box) using GetParent and sends a WM_COMMAND message with wParam equal to the
control's ID. The ID is obtained using GetWindowWord. The dialog box window
procedure then passes this message on to the dialog box procedure within
ABOUT3. The result is a customized push button, as shown in Figure 10-6. You
can use this same method to create other customized controls for dialog
boxes.

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

Is that all there is to it? Well, not really. EllipPushWndProc is a
bare-bones version of the logic generally involved in maintaining a child
window control. For instance, the button doesn't flash like normal push
buttons. To invert the colors on the interior of the push button, the window
procedure would have to process WM_KEYDOWN (from the Spacebar) and
WM_LBUTTONDOWN messages. The window procedure should also capture the mouse
on a WM_LBUTTONDOWN message and release the mouse capture (and return the
button's interior color to normal) if the mouse is moved outside the child
window's client area while the button is still depressed. Only if the button
is released while the mouse is captured should the child window send a
WM_COMMAND message back to its parent.

EllipPushWndProc also does not process WM_ENABLE messages. As mentioned
above, a dialog box procedure can disable a window using the EnableWindow
function. The child window would then display gray rather than black text to
indicate that it has been disabled and cannot receive messages.

If the window procedure for a child window control needs to store data that
are different for each created window, then it can do so by using a positive
value of cbWndExtra in the window class structure. This reserves space in
the internal window structure that can be accessed by using SetWindowWord,
SetWindowLong, GetWindowWord, and GetWindowLong.



THE MESSAGE BOX

Let's take a breather here. We've been looking at ways to customize dialog
boxes. Now let's look at an alternative to dialog boxes, which is the
message box. We began using message boxes way back in Chapter 5, but we
haven't yet examined them in detail.

The message box is an appropriate and easy-to-use alternative to a dialog
box when you need a simple response from the user. The general syntax is:

nItem = MessageBox (hwndParent, lpszText, lpszCaption, nType) ;

The message box has a caption (the character string lpszCaption), one or
more lines of text (lpszText), one or more buttons, and (optionally) a
predefined icon. One of the buttons is a default. The nItem value returned
from MessageBox indicates the button that was pressed.

The hwndParent parameter is generally the handle to the window that creates
the message box. The input focus will be set to this window when the message
box is destroyed. If you don't have a window handle available or you don't
want the input focus to go to one of your windows, you can use NULL for the
handle. If you use a message box within a dialog box, use the dialog box
window handle (which we've been calling hDlg) for this parameter.

The lpszText parameter is a long pointer to NULL-terminated text that
appears in the body of the message box. Windows breaks this text into
several lines if necessary. You can also include tab characters in the text,
and you can define your own line breaks using carriage returns or linefeeds
or both. The lpszCaption string is generally the name of the application.

The nType parameter is a collection of flags joined by the C bitwise OR
operator. The first group of flags specifies the push buttons to appear at
the bottom of the message box: MB_OK (the default), MB_OKCANCEL, MB_YESNO,
MB_YESNOCANCEL, MB_RETRYCANCEL, and MB_ABORTRETRYIGNORE. As you can see,
these flags allow for a maximum of three buttons. The second group of flags
specifies which of the buttons is the default: MB_DEFBUTTON1 (the default),
MB_DEFBUTTON2, and MB_DEFBUTTON3.

The third group of flags specifies an icon to appear in the message box:
MB_ICONINFORMATION, MB_ICONEXCLAMATION, MB_ICONSTOP, and MB_ICONQUESTION.
There is no default. If you omit one of these flags, the message box has no
icon. You should use the information icon for a status message, the
exclamation point for a reminder, the question mark for a warning of the
consequences of an action, and the stop icon for a signal of serious
problems.

The fourth set of flags governs whether the message box is application
modal, in which case the user can switch to another application without
ending the message box, or system modal, which requires the user to end the
message box before doing anything else. The flags are MB_APPLMODAL (the
default) and MB_SYSTEMMODAL. Finally, you can use a fifth flag, MB_NOFOCUS,
which displays the message box but does not give it the input focus.

Depending on which button is pressed, the message box returns one of the
following identifiers: IDOK, IDCANCEL, IDYES, IDNO, IDRETRY, and IDABORT.

The Assertion Message Box

Although message boxes are customarily used to convey messages and ask
questions in finished programs, they are also helpful in debugging. For
instance, you may be familiar with the assert macro included in the ASSERT.H
header file with the Microsoft C Compiler. This macro is used for testing
various conditions during a program's execution. If the condition does not
hold, the macro displays the current source code filename and line number
and terminates the program. You can create a similar macro for Windows:

#ifndef NDEBUG

#define WinAssert(exp)\
               {\
               if (!(exp))\
                    {\
                    char szBuffer [40] ;\
                    sprintf (szBuffer, "File %s, Line %d",\
                         __FILE__, __LINE__) ;\
                    MessageBox (NULL, szBuffer,\
                         "Assertion Error",\
                         MB_OK | MB_ICONSTOP) ;\
                    }\
               }

#else

#define WinAssert(exp)

#endif

You can then make various assertions in your code. For instance, to be sure
that the value of hwnd in a certain section of your program is never NULL,
you can do this:

WinAssert (hwnd != NULL) ;

If hwnd is NULL when this statement is executed, the message box will be
displayed to alert you to the problem. A macro is used rather than a
function, because the predefined identifiers __FILE__ and __LINE__ must
equal the source code filename and line number where the assertion failed.
If you used a function, these identifiers would always be set to the
filename and line number where the function was located.

Unlike assert, the WinAssert macro shown above doesn't terminate the program
if the assertion fails. If you use a debugging terminal, you can use a
different version of this macro shown below. When you select the Abort
button, the FatalExit function is called to display a stack trace on the
debugging terminal.

if (IDABORT == MessageBox (NULL, szBuffer,
                           "Assertion Error",
                           MB_ABORTRETRYIGNORE | MB_ICONSTOP))
     FatalExit (-1) ;

Once you've finished debugging the program, you can compile with the
identifier NDEBUG, defined by using the compiler switch -D NDEBUG. The
WinAssert macro will then be defined as nothing.


Popup Information

Another handy use of a message box during program development is to provide
information to you while the program is executing. It would be ideal if you
could use a message box much as you use printf in C programs for MS-DOS,
with a formatting string and a variable number of arguments. And in fact,
you can create a function that lets you do this:

void OkMsgBox (char *szCaption, char *szFormat, ...)
     {
     char szBuffer [256] ;
     char *pArguments ;

     pArguments = (char *) &szFormat + sizeof szFormat ;
     vsprintf (szBuffer, szFormat, pArguments) ;
     MessageBox (NULL, szBuffer, szCaption, MB_OK) ;
     }

The vsprintf function is similar to sprintf except that it uses a pointer to
a series of arguments (pArguments) rather than the arguments themselves.
OkMsgBox sets pArguments to the arguments on the stack when OkMsgBox is
called. The first parameter to OkMsgBox is the message box caption, the
second parameter is a format string, and the third and subsequent parameters
are values to be displayed. Let's say you want a message box to appear every
time the window procedure gets a WM_SIZE message. Your code might look like
this:

case WM_SIZE :
     OkMsgBox ("WM_SIZE Message",
          "wParam = %04X, lParam = %04X-%04X",
          wParam, HIWORD (lParam), LOWORD (lParam)) ;
[other program lines]
     return 0 ;

This displays the values of wParam and lParam within the message box.



WORKING WITH FILES: POPPAD REVISITED

When we added a menu to POPPAD in Chapter 9, several menu options were left
unimplemented. We are now almost ready to add logic to POPPAD to open files,
read them in, and save the edited files to disk.

Working with files in Windows is no great joy. Although the standard dialog
box template to open a file is fairly simple, the dialog box procedure
itself is one of the most difficult you'll encounter. Before tackling that
function, let's investigate methods of file I/O in Windows programs.

The OpenFile Function Call

Windows includes a function to open a file and return an MS-DOS file handle:

hFile = OpenFile (lpszFileName, &of, wFunction) ;

The OpenFile funtion call returns a -1 if an error is encountered. For most
uses of OpenFile, a return value other than -1 will be an MS-DOS file handle
that you can use to read from and write to the file.

The lpszFileName parameter is a long (or far) pointer to the filename. A
disk drive and subdirectory are optional. The wFunction parameter tells
Windows what to do with this file (open it, create it, delete it, and so
forth). I'll describe this parameter in more detail shortly.

The &of parameter is a far pointer to a structure of type OFSTRUCT ("open
file structure"). You don't need to set any of the fields of this structure
before calling OpenFile: They are filled in by the first OpenFile function
call you make and are then used in subsequent OpenFile calls for the same
file. The OFSTRUCT fields are shown below:

Field            Data Type  Description
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
cBytes           BYTE       Length of structure in bytes
fFixedDisk       BYTE       Nonzero for fixed-disk file
nErrCode         WORD       MS-DOS error code
reserved[4]      BYTE       File date and time
szPathName[128]  BYTE       Fully qualified pathname and filename

If the OpenFile call is successful, the szPathName field is filled with the
fully qualified filename, including the current disk drive and subdirectory
path. Under current versions of MS-DOS, the 128 characters allowed for this
name are about 40 more than needed. A fully qualified filename has 2 bytes
for the drive letter and colon, up to 64 bytes for the directory path
starting with the initial backslash, another byte for the backslash
following the path, up to 12 bytes for the filename (8-character name,
period, and 3-character extension),  and a terminating 0.

The OpenFile function has three advantages over opening a file by other
means:

  þ   The lpszFileName parameter is assumed to contain characters from the
      ANSI character set. OpenFile does an AnsiToOem conversion on the name
      before trying to open the file. You would have to convert the filename
      yourself if you opened the file by other means.

  þ   If the file is not found in the current directory, OpenFile searches
      for the file on all directories listed in the MS-DOS environment PATH
      string.

  þ   Windows determines the fully qualified filename and inserts it in the
      szPathName field of the structure.

This last item is the most important feature of OpenFile. Generally, the
lpszFileName parameter you pass to OpenFile when first opening or creating a
file is only a filename. When you use OpenFile subsequently to open the same
file, Windows uses the fully qualified szPathName field of the OFSTRUCT
structure.

Here's why that's important: Although each program instance in Windows has a
current disk drive and subdirectory associated with it, the DlgDirList
function (which we'll use later in this chapter) can change the current
drive and subdirectory associated with a program instance. Let's say you use
some means other than OpenFile to obtain a filename (without a drive or
directory path) located in the current directory and that you successfully
open and close the file. The user then uses the dialog box to get a new
file. In the process, the dialog box calls DlgDirList to change the current
drive or subdirectory. The user then cancels the dialog box, and your
program tries to open the original file again. It's gone! Well, it's not
gone, but the current drive or subdirectory is different, so your program
can't find the file. You avoid this problem by using OpenFile.

The wFunction parameter of the OpenFile call comprises one or more flags
joined by the C bitwise OR operator. First, you use one of the following
four flags to open an existing file or create a new file:

ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
OF_READ                           Opens an existing file for reading only

OF_WRITE                          Opens an existing file for writing only

OF_READWRITE                      Opens an existing file for reading and
                                  writing

OF_CREATE                         Creates a new file, or opens an existing
                                  file and truncates the size of the file
                                  to 0


Following the OpenFile call, the file is open and ready for reading and
writing. The value returned from OpenFile is the MS-DOS file handle (unless
this value is -1, in which case the file could not be opened or created).

If you prefer that the file be closed following the OpenFile call, you can
add the flag OF_EXIST. This flag is generally used with OF_READ, OF_WRITE,
or OF_READWRITE to see if the specified file exists. You can also use this
flag with OF_CREATE to create the file and immediately close it. The file
can be reopened later. When you use OF_EXIST, the value returned from
OpenFile is -1 if the file does not exist or (with OF_CREATE) if the file
could not be created. Any file handle returned from OpenFile with an
OF_EXIST flag should be ignored, because the file has been closed.

With OF_READ, OF_WRITE, or OF_READWRITE, you can also use the flag
OF_PROMPT. If Windows can't find the file in the current directory or in one
of the directories in the MS-DOS environment PATH string, this flag causes
Windows to display the infamous "Insert [filename] disk in drive A:" message
box. Windows users appreciate this message box about as much as MS-DOS users
like the "Abort, Retry, Ignore" message, so use this flag with discretion.
Also keep in mind that the OF_PROMPT message box has only an OK button,
which means that fixed-disk users must scrounge up a disk to put in drive A
before proceeding. If you must use the OF_PROMPT flag, also use the
OF_CANCEL flag, which adds a Cancel button to the message box. If the user
selects Cancel, the OpenFile function returns -1.

That's it for the flags to open a file for the first time. The file is open
when the OpenFile function returns, unless you've included the OF_EXIST flag
(in which case the file has been closed) or OpenFile returns -1 (in which
case an error occurred). You can now read from or write to this file and
then close it. How you perform these actions is discussed in the next
section.

When you want to reopen the file, you use the OF_READ, OF_WRITE,
OF_READWRITE, or OF_CREATE flag in combination with the OF_REOPEN flag. The
OF_REOPEN flag causes Windows to use the szPathName field in the OFSTRUCT
structure to obtain the original disk drive, subdirectory, and filename of
the file. Even if the current drive or subdirectory has changed since the
file was first opened, this flag ensures that Windows will use the same
drive and subdirectory as in the first OpenFile call. If you use OF_REOPEN
with OF_CREATE, the size of the file will be truncated to 0.

When you use the OF_READ flag to open a file for reading only, you can also
use the OF_VERIFY flag. This flag causes Windows to use the reserved field
in the OFSTRUCT structure to verify that the date and time of the file being
reopened are the same as those stored during the original OpenFile call.
This works only for files opened with OF_READ, because the value for the
file date and time is updated when a write-only or read-write file is
closed.

Also available are two OpenFile flags that do not open files. The OF_DELETE
flag deletes a file. If the file cannot be found in the current
subdirectory, OpenFile uses the MS-DOS environment PATH string to search for
a file to delete. Perhaps you're feeling cruel, in which case you can use
the OF_PROMPT flag with OF_DELETE so that Windows also gets the chance to
delete the file on the disk in drive A. OpenFile returns -1 if no file was
deleted. If the file has previously been opened with OpenFile and then
closed, you can use OF_DELETE in combination with OF_REOPEN.

The OF_PARSE flag does not open a file or even check for a file's existence.
This flag is used by itself to parse the filename and fill in the szPathName
field of the OFSTRUCT structure. OpenFile returns -1 if the filename is
invalid--say, if it uses illegal characters.


Two Methods of File I/O

The main rule of file I/O in Windows is this: Do not keep files open for
long periods of time. More specifically, that means you should not keep a
file open between messages. You should open or create a file, read it or
write to it in several large gulps, and then close it--all in the course of
processing a single message.

You have two options for using files that have been opened with the OpenFile
call:

  þ   Use normal C functions for file I/O. The file handle returned from
      OpenFile can be used directly with the "low-level" file I/O functions.
      The most important of these functions are open, read, lseek, close,
      create, write, and tell. We used the read and close functions in the
      HEAD program in Chapter 5.

      The problem with these functions is that you can't use far pointers
      with them unless your program is compact model or large model. As you
      know, compact model and large model are not recommended for Windows
      programs because their data segments must be fixed in memory. If you
      want to read part of a file into a global memory segment, you must
      first read the file into a local memory block and then transfer it to
      the global memory block.

      You can also use the normal C buffered file I/O functions such as
      fopen, fread, fwrite, and fclose. The MS-DOS file handle returned from
      OpenFile can be converted to a structure of type FILE using fdopen.
      The buffered file I/O functions are of less value in Windows than in
      other environments, because you need to read and write in large
      chunks, and buffering doesn't help unless you're reading small parts
      of a file.

  þ   Use file I/O functions included in Windows.  These go by the names of
      _lopen, _lclose, _lcreat, _llseek, _lread, and _lwrite.  The "l"
      prefix indicates that these functions accept far pointers for read and
      write buffers, thus allowing you to use them with global memory
      blocks. (These functions existed in Windows since version 1 but have
      only been documented beginning in Windows 3.)

This third method turns out to be the easiest when the file must be read
into or written from buffer areas accessible only with far pointers. (The
normal C low-level file I/O calls are preferable when you can use near
pointers.) Do not write your own assembly-language routines for interfacing
with the MS-DOS file I/O functions.

You'll probably use OpenFile to open and create files, but you can also use
_lopen and _lcreat. The syntax is:

hFile = _lopen (lpszPathName, iReadWrite) ;

The lpszPathName parameter is a filename with an optional drive and
subdirectory path. The iReadWrite parameter should be set to one of the
identifiers OF_READ, OF_WRITE, or OF_READWRITE. The hFile value returned
from _lopen is an MS-DOS file handle if the file is opened or -1 if the file
cannot be opened.

The _lcreat function looks similar to the _lopen call:

hFile = _lcreat (lpszPathName, iAttribute) ;

However, the second parameter is an MS-DOS file attribute. Use 0 for a
normal (nonhidden, nonsystem, read-write) file. If the file already exists,
the size is truncated to 0 and opened; if it doesn't exist, it is created
and opened. Like the _lopen call, _lcreat returns an MS-DOS file handle if
the function is successful or -1 if an error occurs.

After an _lopen or _lcreat call, the file pointer is set initially to the
beginning of the file. Normally, all reading and writing is sequential. The
file pointer is updated after each read or write. However, you can use
_llseek (MS-DOS Function 42H) to change the file pointer:

lPosition = _llseek (hFile, lPosition, iMethod) ;

The iMethod parameter should be set to one of the following values:

Value                             Purpose
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
0                                 Move the file pointer lPosition bytes
                                  from the beginning of the file

1                                 Move the file pointer lPosition bytes
                                  from the current position in the file

2                                 Move the file pointer lPosition bytes
                                  from the end of the file


The value of lPosition returned from _llseek is the new position of the file
pointer if the function is successful or -1L if an error occurs.

If you want to open an existing file and add data to it, you call:

_llseek (hFile, 0L, 2) ;

This moves the file pointer to the end of the file. You can also use _llseek
to determine the size of a file. You might want to define a function called
FileLength to do this:

long FileLength (int hFile)
     {
     long   lCurrentPos = _llseek (hFile, 0L, 1) ;
     long   lFileLength = _llseek (hFile, 0L, 2) ;

     _llseek (hFile, lCurrentPos, 0) ;

     return lFileLength ;
     }

FileLength saves the current position of the file pointer, moves the file
pointer to the end  of the file, and then restores the file pointer to its
original position.

To write to a file, use:

wBytesWritten = _lwrite (hFile, lpBuffer, wBytes) ;

The lpBuffer parameter is a far pointer to the data you want to write to the
file, and wBytes is the number of bytes to write. The file buffer cannot
extend past the end of a segment. The wBytesWritten value returned from the
function is the number of bytes that are actually written. This can be less
than wBytes if not enough disk space is available to write the entire
buffer. Normally, MS-DOS function calls allow you to write up to 65,535
bytes to a file, but _lwrite returns -1 to signal an error, so you'll want
to restrict yourself to 65,534 bytes or less.

The function to read from a file is similar:

wBytesRead = _lread (hFile, lpBuffer, wBytes) ;

The lpBuffer parameter is a far pointer to an area that receives the data
read from the file, and wBytes is the number of bytes to read. The
wBytesRead value returned can be less than wBytes if the end of the file is
encountered before wBytes are read. A return value of -1 signals an error.

Finally, to close a file, use:

_lclose (hFile) ;

When working with files and global memory blocks, you may also need to make
use of string functions that work with far pointers. Windows includes
functions called lstrlen, lstrcpy, lstrcat, and lstrcmp that are equivalent
to the normal C string functions strlen, strcpy, strcat, and strcmp, except
that they use far pointers. These functions are useful for moving data
between two global memory blocks or between a global memory block and a
local memory block. They are coded in assembly language and are thus much
faster than equivalent C code. The lstrcmp function is a "Windows version"
of the strcmp function: It is case sensitive for both the normal ASCII
character codes and the ANSI character codes, and it can accommodate strings
with multibyte character codes.


Dialog Boxes for Open and Save

With these preliminaries out of the way, we are now ready to create dialog
box templates and dialog box procedures to assist our Windows programs in
opening and saving files. The FILEDLG.C source code file, FILEDLG.H header
file, and FILEDLG.DLG dialog box template file, shown in Figure 10-7 on the
following pages, will be used in the POPPAD3 program in this chapter. You
can use these routines (or similar ones) in your own programs.

 FILEDLG.C

/*-----------------------------------------------
   FILEDLG.C -- Open and Close File Dialog Boxes
  -----------------------------------------------*/

#include 
#include "filedlg.h"

BOOL FAR PASCAL FileOpenDlgProc (HWND, WORD, WORD, LONG) ;
BOOL FAR PASCAL FileSaveDlgProc (HWND, WORD, WORD, LONG) ;

LPSTR lstrchr  (LPSTR str, char ch) ;
LPSTR lstrrchr (LPSTR str, char ch) ;

static char      szDefExt   [5] ;
static char      szFileName [96] ;
static char      szFileSpec [16] ;
static POFSTRUCT pof ;
static WORD      wFileAttr, wStatus ;

int DoFileOpenDlg (HANDLE hInst, HWND hwnd, char *szFileSpecIn,
                   char *szDefExtIn, WORD wFileAttrIn,
                   char *szFileNameOut, POFSTRUCT pofIn)
     {
     FARPROC lpfnFileOpenDlgProc ;
     int     iReturn ;

     lstrcpy (szFileSpec, szFileSpecIn) ;
     lstrcpy (szDefExt,   szDefExtIn) ;
     wFileAttr = wFileAttrIn ;
     pof = pofIn ;

     lpfnFileOpenDlgProc = MakeProcInstance (FileOpenDlgProc, hInst) ;

     iReturn = DialogBox (hInst, "FileOpen", hwnd, lpfnFileOpenDlgProc) ;

     FreeProcInstance (lpfnFileOpenDlgProc) ;

     lstrcpy (szFileNameOut, szFileName) ;
     return iReturn ;
     }

int DoFileSaveDlg (HANDLE hInst, HWND hwnd, char *szFileSpecIn,
                   char *szDefExtIn, WORD *pwStatusOut,
                   char *szFileNameOut, POFSTRUCT pofIn)
     {
     FARPROC lpfnFileSaveDlgProc ;
     int     iReturn ;



     lstrcpy (szFileSpec, szFileSpecIn) ;
     lstrcpy (szDefExt,   szDefExtIn) ;
     pof = pofIn ;

     lpfnFileSaveDlgProc = MakeProcInstance (FileSaveDlgProc, hInst) ;

     iReturn = DialogBox (hInst, "FileSave", hwnd, lpfnFileSaveDlgProc) ;

     FreeProcInstance (lpfnFileSaveDlgProc) ;

     lstrcpy (szFileNameOut, szFileName) ;
     *pwStatusOut = wStatus ;
     return iReturn ;
     }

BOOL FAR PASCAL FileOpenDlgProc (HWND hDlg, WORD message,
                                 WORD wParam, LONG lParam)
     {
     char  cLastChar ;
     short nEditLen ;

     switch (message)
        {
        case WM_INITDIALOG :
           SendDlgItemMessage (hDlg, IDD_FNAME, EM_LIMITTEXT, 80, 0L) ;
           DlgDirList (hDlg, szFileSpec, IDD_FLIST, IDD_FPATH, wFileAttr) ;
           SetDlgItemText (hDlg, IDD_FNAME, szFileSpec) ;
           return TRUE ;

        case WM_COMMAND :
           switch (wParam)
              {
              case IDD_FLIST :
                 switch (HIWORD (lParam))
                    {
                    case LBN_SELCHANGE :
                       if (DlgDirSelect (hDlg, szFileName, IDD_FLIST))
                          lstrcat (szFileName, szFileSpec) ;
                       SetDlgItemText (hDlg, IDD_FNAME, szFileName) ;
                       return TRUE ;

                    case LBN_DBLCLK :
                       if (DlgDirSelect (hDlg, szFileName, IDD_FLIST))
                          {
                          lstrcat (szFileName, szFileSpec) ;
                          DlgDirList (hDlg, szFileName, IDD_FLIST,
IDD_FPATH,
                                                              wFileAttr) ;
                          SetDlgItemText (hDlg, IDD_FNAME, szFileSpec) ;
                          }
                       else
                          {
                          SetDlgItemText (hDlg, IDD_FNAME, szFileName) ;
                          SendMessage (hDlg, WM_COMMAND, IDOK, 0L) ;
                          }
                       return TRUE ;
                    }
                 break ;

              case IDD_FNAME :
                 if (HIWORD (lParam) == EN_CHANGE)
                    EnableWindow (GetDlgItem (hDlg, IDOK),
                       (BOOL) SendMessage (LOWORD (lParam),
                                             WM_GETTEXTLENGTH, 0, 0L)) ;
                 return TRUE ;

              case IDOK :
                 GetDlgItemText (hDlg, IDD_FNAME, szFileName, 80) ;

                 nEditLen  = lstrlen (szFileName) ;
                 cLastChar = *AnsiPrev (szFileName, szFileName + nEditLen) ;

                 if (cLastChar == '\\' || cLastChar == ':')
                    lstrcat (szFileName, szFileSpec) ;

                 if (lstrchr (szFileName, '*') || lstrchr (szFileName, '?'))
                    {
                    if (DlgDirList (hDlg, szFileName, IDD_FLIST,
                                          IDD_FPATH, wFileAttr))
                       {
                       lstrcpy (szFileSpec, szFileName) ;
                       SetDlgItemText (hDlg, IDD_FNAME, szFileSpec) ;
                       }
                    else
                       MessageBeep (0) ;

                    return TRUE ;
                    }

                 lstrcat (lstrcat (szFileName, "\\"), szFileSpec) ;

                 if (DlgDirList (hDlg, szFileName, IDD_FLIST,
                                                   IDD_FPATH, wFileAttr))
                    {
                    lstrcpy (szFileSpec, szFileName) ;
                    SetDlgItemText (hDlg, IDD_FNAME, szFileSpec) ;
                    return TRUE ;
                    }

                 szFileName [nEditLen] = '\0' ;
                 if (-1 == OpenFile (szFileName, pof, OF_READ | OF_EXIST))
                    {
                    lstrcat (szFileName, szDefExt) ;
                    if (-1 == OpenFile (szFileName, pof, OF_READ |
OF_EXIST))
                       {
                       MessageBeep (0) ;
                       return TRUE ;
                       }
                    }
                 lstrcpy (szFileName,
                          AnsiNext (lstrrchr (pof->szPathName, '\\'))) ;

                 OemToAnsi (szFileName, szFileName) ;
                 EndDialog (hDlg, TRUE) ;
                 return TRUE ;

              case IDCANCEL :
                 EndDialog (hDlg, FALSE) ;
                 return TRUE ;
              }
        }
     return FALSE ;
     }

BOOL FAR PASCAL FileSaveDlgProc (HWND hDlg, WORD message,
                                 WORD wParam, LONG lParam)
     {
     switch (message)
        {
        case WM_INITDIALOG :
           SendDlgItemMessage (hDlg, IDD_FNAME, EM_LIMITTEXT, 80, 0L) ;
           DlgDirList (hDlg, szFileSpec, 0, IDD_FPATH, 0) ;
           SetDlgItemText (hDlg, IDD_FNAME, szFileSpec) ;
           return TRUE ;

        case WM_COMMAND :
           switch (wParam)
              {
              case IDD_FNAME :
                 if (HIWORD (lParam) == EN_CHANGE)
                    EnableWindow (GetDlgItem (hDlg, IDOK),
                       (BOOL) SendMessage (LOWORD (lParam),
                                             WM_GETTEXTLENGTH, 0, 0L)) ;
                 return TRUE ;
              case IDOK :
                 GetDlgItemText (hDlg, IDD_FNAME, szFileName, 80) ;
                 if (-1 == OpenFile (szFileName, pof, OF_PARSE))
                    {
                    MessageBeep (0) ;
                    return TRUE ;
                    }

                 if (!lstrchr (AnsiNext (lstrrchr (pof->szPathName, '\\')),
                               '.'))
                    lstrcat (szFileName, szDefExt) ;

                 if (-1 != OpenFile (szFileName, pof, OF_WRITE | OF_EXIST))
                    wStatus = 1 ;

                 else if (-1 != OpenFile (szFileName, pof,
                                                  OF_CREATE | OF_EXIST))
                    wStatus = 0 ;

                 else
                    {
                    MessageBeep (0) ;
                    return TRUE ;
                    }

                 lstrcpy (szFileName,
                          AnsiNext (lstrrchr (pof->szPathName, '\\'))) ;

                 OemToAnsi (szFileName, szFileName) ;
                 EndDialog (hDlg, TRUE) ;
                 return TRUE ;

              case IDCANCEL :
                 EndDialog (hDlg, FALSE) ;
                 return TRUE ;
              }
        }
     return FALSE ;
     }

LPSTR lstrchr (LPSTR str, char ch)
     {
     while (*str)
          {
          if (ch == *str)
               return str ;

          str = AnsiNext (str) ;
          }
     return NULL ;
     }
LPSTR lstrrchr (LPSTR str, char ch)
     {
     LPSTR strl = str + lstrlen (str) ;

     do
          {
          if (ch == *strl)
               return strl ;

          strl = AnsiPrev (str, strl) ;
          }
     while (strl > str) ;

     return NULL ;
     }

 FILEDLG.H

/*-----------------------
   FILEDLG.H header file
  -----------------------*/

#define IDD_FNAME   0x10
#define IDD_FPATH   0x11
#define IDD_FLIST   0x12

 FILEDLG.DLG

/*--------------------------------
   FILEDLG.DLG dialog definitions
  --------------------------------*/

FileOpen DIALOG 10, 10, 148, 116
     STYLE WS_POPUP | WS_DLGFRAME
     {
     LTEXT   "Open File &Name:", -1,        2,  4,  76, 10
     EDITTEXT                 IDD_FNAME,    2, 18, 100, 12, ES_AUTOHSCROLL
     LTEXT          "&Files in", -1,        2, 40,  38, 10
     LTEXT          "",       IDD_FPATH,   44, 40,  98, 12
     LISTBOX                  IDD_FLIST,    2, 54,  70, 58, WS_TABSTOP |
WS_VSCROLL
     DEFPUSHBUTTON  "&Open",  IDOK,        88, 62,  50, 14, WS_GROUP
     PUSHBUTTON     "Cancel", IDCANCEL,    88, 86,  50, 14, WS_GROUP
     }
FileSave DIALOG 10, 10, 180, 54
     STYLE WS_POPUP | WS_DLGFRAME
     {
     LTEXT "Save File &Name As:", -1,       6,  4,  84, 12
     LTEXT          "",       IDD_FPATH,   90,  4,  78, 12
     EDITTEXT                 IDD_FNAME,    6, 20, 104, 12, ES_AUTOHSCROLL
     DEFPUSHBUTTON  "OK",     IDOK,       124, 20,  50, 14, WS_GROUP
     PUSHBUTTON     "Cancel", IDCANCEL,   124, 36,  50, 14, WS_GROUP
     }

FILEDLG.DLG contains two dialog box templates named "FileOpen" and
"FileSave." When displayed, these look very much like the dialog boxes used
in the programs that come with Windows, so the operation of the two dialog
boxes will be familiar to Windows users. FileOpen contains Open and Cancel
push buttons, and FileSave contains OK and Cancel push buttons. The static
text field with an ID of IDD_FPATH is used to display the current disk drive
and directory path. The edit field with the ID of IDD_FNAME allows a user to
type in a filename. The FileOpen dialog box also contains a list box that
displays all the files matching a particular file specification (we'll use
"*.TXT" with POPPAD3), all valid disk drive letters, and all child
subdirectories of the current drive and directory.

The FILEDLG.C file contains two functions named DoFileOpenDlg and
DoFileSaveDlg that a program can call to invoke the dialog boxes. These
functions are responsible for copying input parameters to variables within
FILEDLG.C, calling DialogBox, and returning information obtained from the
dialog box procedure to the program that called the FILEDLG.C functions. The
parameters to the DoFileOpenDlg and DoFileSaveDlg functions include a
default file specification (szFileSpecIn), a default filename extension
(szDefExtIn), and a pointer to a structure of type OFSTRUCT (pofIn). The
DoFileOpenDlgProc also requires a file attribute (wFileAttrIn) to be used
when listing files in the list box.

The DoFileOpenDlg and DoFileSaveDlg functions return a 1 if the user ends
the dialog box with Open or OK, and a 0 if the user ends with Cancel. If the
user ends with Open or OK, the OFSTRUCT structure passed to the functions
will contain the fully qualified filename of the selected file in the
szPathName field. The filename only (without a drive or directory) is copied
to the szFileNameOut character array. For DoFileSaveDlg, the pwStatusOut
parameter points to a word that is set to 1 if the file does exist and to 0
if it does not.

The actual dialog box procedures are FileOpenDlgProc and FileSaveDlgProc.
These are relatively complex because they must deal with the interaction
between the list box and the edit control and with the checking that must be
performed on filenames and subdirectories entered by the user. To help with
these areas, the dialog box procedures extensively use the Windows functions
DlgDirList and DlgDirSelect.


The DlgDirList and DlgDirSelect Functions

You'll recall from working with list boxes in Chapter 6 that you can fill a
list box with a list of files, subdirectories, and disk drives by sending
the list box a message:

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

The wAttr parameter is an MS-DOS file attribute (with some additional bits
to indicate the listing of subdirectories and disk drives), and lpszFileSpec
is a far pointer to a filename specification such as "*.*".

Although this list box message is quite convenient, within a dialog box you
can use an even more sophisticated function:

iStatus = DlgDirList (hDlg, lpszFileSpec, nIDList, nIDStatic, wAttr) ;

The nIDList parameter is the ID of the list box, and nIDStatic is the ID of
a static text field. DlgDirList parses the string pointed to by lpszFileSpec
to extract any disk drive or subdirectory information from it. It then
changes the current disk drive or subdirectory using  MS-DOS function calls.

DlgDirList sends an LB_DIR message to the list box indicated by nIDList to
fill it with filenames meeting the file specification and the file attribute
word. The current disk drive and subdirectory path are then displayed in the
static text field whose ID is nIDStatic. When DlgDirList returns, the string
pointed to by lpszFileSpec contains only the file specification without any
drive or subdirectory. The function returns nonzero if it is successful and
0 otherwise. A 0 value usually indicates that the string pointed to by
lpszFileSpec does not contain a valid drive or subdirectory.

For example, suppose that drive C has a directory named WINDOWS and that
your dialog box procedure contains this code:

char szFileSpec [] = "C:\\WINDOWS\\*.TXT" ;
[other program lines]
DlgDirList (hDlg, szFileSpec, IDD_FLIST, IDD_FPATH, 0x4010) ;

When DlgDirList returns, the current disk drive and subdirectory for the
instance of the program will have been changed to C:\WINDOWS. The list box
whose ID is IDD_FLIST will list files with the extension .TXT, all
subdirectories of the directory WINDOWS, and all valid disk drives. The
static text field whose ID is IDD_FPATH will display the text C:\WINDOWS.
The szFileSpec string array will contain "*.TXT". If either the nIDList or
nIDStatic parameter is 0, however, DlgDirList will assume that the list box
or static text field does not exist.

Both FileOpenDlgProc and FileSaveDlgProc use DlgDirList while processing the
WM_INITDIALOG message. (In FileSaveDlgProc, DlgDirList is called with the
nIDList parameter set to 0 because the dialog box does not contain a list
box.)

SetDlgItemText is used to set the text of the edit control to the text file
specification:

SetDlgItemText (hDlg, IDD_FNAME, szFileSpec) ;

This function does the same thing as the SetWindowText function or the
WM_SETTEXT message. You can use the companion function GetDlgItemText to
obtain the contents of the edit control.

FileOpenDlgProc also uses DlgDirSelect extensively. This function returns
the currently selected string from a list box:

bDirectory = DlgDirSelect (hDlg, lpszString, nIDList) ;

The nIDList parameter is the ID of the list box. The currently selected
string is copied to the character array pointed to by lpszString. If the
return value is TRUE (nonzero), meaning that the string is either a disk
drive or subdirectory name, DlgDirSelect removes the hyphens and brackets
that appear when disk-drive letters and the subdirectory names are displayed
in a list box. The function appends a colon to disk-drive letters and a
backslash to subdirectory names when copying them to the lpszString array.

If DlgDirSelect returns a disk drive or directory, you can then append the
default file specification ("*.TXT", for instance) to the string and use
that as the lpszFileSpec parameter to DlgDirList. DlgDirList will then
change the drive or directory and update the static text field. The file
specification pointed to by lpszFileSpec on return from DlgDirList can then
be transferred to the edit control. If DlgDirSelect returns FALSE (0),
meaning that lpszString contains a filename from the list box, then this
filename can be transferred to the edit field directly.


Getting Valid Filenames

The dialog box to open a file would be much simpler if it did not contain an
edit control. The edit control forces the dialog box procedure into doing
some filename parsing. Much of this logic occurs when FileOpenDlgProc
processes the WM_COMMAND message with wParam equal to IDOK. (When the user
double-clicks a list box filename, the dialog procedure transfers the name
to the edit box and then generates a WM_COMMAND message with wParam equal to
IDOK. This avoids repeating the parsing logic for a list box double-click
message.)

The dialog box procedure uses GetDlgItemText to obtain the string in the
edit control. The parsing logic begins with a check to determine if the last
character in this string is a backslash or colon. If it is, the user is
requesting that the drive or directory be changed, so the current file
specification must be appended to the string the user entered. If the
resultant filename string contains a global character (* or ?), the dialog
box procedure calls DlgDirList with the new specification, and the dialog
box procedure exits to wait for the next message.

If the character string entered by the user neither terminates with a
backslash or colon nor contains a global character, it could be either a
directory name or a filename. FileOpenDlgProc appends a backslash and the
current file specification to it. If DlgDirList doesn't report an error,
processing of the message is over. Otherwise, the entered text string is
probably a filename, in which case FileOpenDlgProc strips off the previously
appended file specification and calls OpenFile. If OpenFile does not find
the file, then the default extension is added, and OpenFile tries again. If
either one of these OpenFile calls is successful in opening the file for
reading, then the szPathName field of the OFSTRUCT structure is used to
obtain the filename without any drive or subdirectory, and the dialog box is
terminated. Otherwise, the dialog procedure beeps to indicate an error in
the filename.

The FILEDLG.C file contains alternate strchr and strrchr functions that
search for characters when parsing filename strings. These alternate
functions use AnsiNext and AnsiPrev to allow multibyte characters in the
filename strings.


The New Version of POPPAD

The new version of POPPAD that uses these two dialog boxes (called POPPAD3)
is shown in Figure 10-8.

 POPPAD3.MAK

#-----------------------
# POPPAD3.MAK make file
#-----------------------

poppad3.exe : poppad.obj  poppadf.obj poppadp0.obj \
              filedlg.obj poppad3.def poppad.res
     link poppad poppadf poppadp0 filedlg, poppad3.exe /align:16, \
          NUL, /nod slibcew libw, poppad3
     rc poppad.res  poppad3.exe

poppad.obj : poppad.c poppad.h
     cl -c -Gsw -Ow -W2 -Zp poppad.c

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

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

filedlg.obj : filedlg.c filedlg.h
     cl -c -Gsw -Ow -W2 -Zp filedlg.c

poppad.res : poppad.rc poppad.h poppad.ico filedlg.dlg filedlg.h
     rc -r poppad.rc


 POPPAD.C

/*---------------------------------------
   POPPAD.C -- Popup Editor
               (c) Charles Petzold, 1990
  ---------------------------------------*/

#include 
#include "poppad.h"
#define  EDITID 1

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

BOOL ReadFile  (HANDLE, HWND, HWND, POFSTRUCT, char *, BOOL) ;
BOOL WriteFile (HANDLE, HWND, HWND, POFSTRUCT, char *, BOOL) ;
BOOL PrintFile (HANDLE, HWND, HWND, char *) ;

LPSTR lstrrchr (LPSTR, char) ;

char szAppName  [] = "PopPad" ;
char szFileSpec [] = "*.TXT"  ;
char szUntitled [] = "(untitled)" ;

int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance,
                    LPSTR lpszCmdLine, int nCmdShow)
     {
     MSG      msg ;
     HWND     hwnd ;
     HANDLE   hAccel ;
     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 (hInstance, szAppName) ;
          wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;
          wndclass.hbrBackground = GetStockObject (WHITE_BRUSH) ;
          wndclass.lpszMenuName  = szAppName ;
          wndclass.lpszClassName = szAppName ;

          RegisterClass (&wndclass) ;
          }

     hwnd = CreateWindow (szAppName, NULL,
                          WS_OVERLAPPEDWINDOW,
                          GetSystemMetrics (SM_CXSCREEN) / 4,
                          GetSystemMetrics (SM_CYSCREEN) / 4,
                          GetSystemMetrics (SM_CXSCREEN) / 2,
                          GetSystemMetrics (SM_CYSCREEN) / 2,
                          NULL, NULL, hInstance, lpszCmdLine) ;

     ShowWindow (hwnd, nCmdShow) ;

     UpdateWindow (hwnd) ;

     hAccel = LoadAccelerators (hInstance, szAppName) ;

     while (GetMessage (&msg, NULL, 0, 0))
          {
          if (!TranslateAccelerator (hwnd, hAccel, &msg))
               {
               TranslateMessage (&msg) ;
               DispatchMessage (&msg) ;
               }
          }
     return msg.wParam ;
     }

BOOL FAR PASCAL AboutDlgProc (HWND hDlg, WORD message, WORD wParam, LONG
lParam)
     {
     switch (message)
          {
          case WM_INITDIALOG :
               return TRUE ;

          case WM_COMMAND :
               switch (wParam)
                    {
                    case IDOK :
                         EndDialog (hDlg, 0) ;
                         return TRUE ;
                    }
               break ;
          }
     return FALSE ;
     }

void DoCaption (HWND hwnd, char *szFileName)
     {
     char szCaption [40] ;

     wsprintf (szCaption, "%s - %s", (LPSTR) szAppName,
               (LPSTR) (szFileName [0] ? szFileName : szUntitled)) ;

     SetWindowText (hwnd, szCaption) ;
     }
short AskAboutSave (HWND hwnd, char *szFileName)
     {
     char  szBuffer [40] ;
     short nReturn ;

     wsprintf (szBuffer, "Save current changes: %s",
               (LPSTR) (szFileName [0] ? szFileName : szUntitled)) ;

     if (IDYES == (nReturn = MessageBox (hwnd, szBuffer, szAppName,
                                    MB_YESNOCANCEL | MB_ICONQUESTION)))

          if (!SendMessage (hwnd, WM_COMMAND, IDM_SAVE, 0L))
               return IDCANCEL ;

     return nReturn ;
     }

long FAR PASCAL WndProc (HWND hwnd, WORD message, WORD wParam, LONG lParam)
     {
     static BOOL    bNeedSave = FALSE ;
     static char    szRealFileName [16] ;
     static FARPROC lpfnAboutDlgProc ;
     static HANDLE  hInst ;
     static HWND    hwndEdit ;
     char           szFileName [16] ;
     LONG           lSelect ;
     OFSTRUCT       of ;
     WORD           wEnable ;

     switch (message)
          {
          case WM_CREATE :
               hInst = ((LPCREATESTRUCT) lParam)->hInstance ;
               lpfnAboutDlgProc = MakeProcInstance (AboutDlgProc, hInst) ;

               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, EDITID, hInst, NULL) ;

               SendMessage (hwndEdit, EM_LIMITTEXT, 32000, 0L) ;

               if (lstrlen (((LPCREATESTRUCT) lParam)->lpCreateParams))
                    {
                    OpenFile (((LPCREATESTRUCT) lParam)->lpCreateParams,
                                        &of, OF_PARSE) ;
                    lstrcpy (szFileName,
                              AnsiNext (lstrrchr (of.szPathName, '\\'))) ;
                    if (ReadFile (hInst, hwnd, hwndEdit, &of,
                              szFileName, FALSE))
                         lstrcpy (szRealFileName, szFileName) ;
                    }
               DoCaption (hwnd, szRealFileName) ;
               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_INITMENUPOPUP :
               if (lParam == 1)
                    {
                    EnableMenuItem (wParam, IDM_UNDO,
                         SendMessage (hwndEdit, EM_CANUNDO, 0, 0L) ?
                              MF_ENABLED : MF_GRAYED) ;

                    EnableMenuItem (wParam, IDM_PASTE,
                         IsClipboardFormatAvailable (CF_TEXT) ?
                              MF_ENABLED : MF_GRAYED) ;

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

                    if (HIWORD (lSelect) == LOWORD (lSelect))
                         wEnable = MF_GRAYED ;
                    else
                         wEnable = MF_ENABLED ;

                    EnableMenuItem (wParam, IDM_CUT,   wEnable) ;
                    EnableMenuItem (wParam, IDM_COPY,  wEnable) ;
                    EnableMenuItem (wParam, IDM_CLEAR, wEnable) ;
                    }
               return 0 ;

          case WM_COMMAND :
               if (LOWORD (lParam) && wParam == EDITID)
                    {
                    switch (HIWORD (lParam))
                         {
                         case EN_UPDATE :
                              bNeedSave = TRUE ;
                              return 0 ;
                         case EN_ERRSPACE :
                              MessageBox (hwnd, "Edit control out of
space.",
                                        szAppName, MB_OK | MB_ICONSTOP) ;
                              return 0 ;
                         }
                    break ;
                    }

               switch (wParam)
                    {
                    case IDM_NEW :
                         if (bNeedSave && IDCANCEL ==
                                   AskAboutSave (hwnd, szRealFileName))
                              return 0 ;

                         SetWindowText (hwndEdit, "\0") ;
                         szRealFileName [0] = '\0' ;
                         DoCaption (hwnd, szRealFileName) ;
                         bNeedSave = FALSE ;
                         return 0 ;

                    case IDM_OPEN :
                         if (bNeedSave && IDCANCEL ==
                                   AskAboutSave (hwnd, szRealFileName))
                              return 0 ;

                         if (ReadFile (hInst, hwnd, hwndEdit, &of,
                                        szFileName, TRUE))
                              {
                              lstrcpy (szRealFileName, szFileName) ;
                              DoCaption (hwnd, szRealFileName) ;
                              bNeedSave = FALSE ;
                              }

                         return 0 ;

                    case IDM_SAVE :
                         if (szRealFileName [0])
                              {
                              if (WriteFile (hInst, hwnd, hwndEdit, &of,
                                             szRealFileName, FALSE))
                                   {
                                   bNeedSave = FALSE ;
                                   return 1 ;
                                   }
                              return 0 ;
                              }
                                                  // fall through
                    case IDM_SAVEAS :
                         if (WriteFile (hInst, hwnd, hwndEdit, &of,
                                        szFileName, TRUE))
                              {
                              lstrcpy (szRealFileName, szFileName) ;
                              DoCaption (hwnd, szFileName) ;
                              bNeedSave = FALSE ;
                              return 1 ;
                              }
                         return 0 ;

                    case IDM_PRINT :
                         PrintFile (hInst, hwnd, hwndEdit,
                              szRealFileName [0] ? szRealFileName :
                                                   szUntitled) ;
                         return 0 ;

                    case IDM_EXIT :
                         SendMessage (hwnd, WM_CLOSE, 0, 0L) ;
                         return 0 ;

                    case IDM_ABOUT :
                         DialogBox (hInst, "AboutBox", hwnd,
                                   lpfnAboutDlgProc) ;
                         return 0 ;

                    case IDM_UNDO :
                         SendMessage (hwndEdit, WM_UNDO, 0, 0L) ;
                         return 0 ;

                    case IDM_CUT :
                         SendMessage (hwndEdit, WM_CUT, 0, 0L) ;
                         return 0 ;

                    case IDM_COPY :
                         SendMessage (hwndEdit, WM_COPY, 0, 0L) ;
                         return 0 ;

                    case IDM_PASTE :
                         SendMessage (hwndEdit, WM_PASTE, 0, 0L) ;
                         return 0 ;

                    case IDM_CLEAR :
                         SendMessage (hwndEdit, WM_CLEAR, 0, 0L) ;
                         return 0 ;

                    case IDM_SELALL :
                         SendMessage (hwndEdit, EM_SETSEL, 0,
                                        MAKELONG (0, 32767)) ;
                         return 0 ;
                    }
               break ;
          case WM_CLOSE :
               if (!bNeedSave || IDCANCEL !=
                         AskAboutSave (hwnd, szRealFileName))
                    DestroyWindow (hwnd) ;

               return 0 ;

          case WM_QUERYENDSESSION :
               if (!bNeedSave || IDCANCEL !=
                         AskAboutSave (hwnd, szRealFileName))
                    return 1L ;

               return 0 ;

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

 POPPADF.C

/*-----------------------------------
   POPPADF -- Popup Notepad File I/O
  -----------------------------------*/

#include 
                                        // in FILEDLG.C

int DoFileOpenDlg (HANDLE, WORD, char *, char *, WORD,   char *, POFSTRUCT)
;
int DoFileSaveDlg (HANDLE, WORD, char *, char *, WORD *, char *, POFSTRUCT)
;

extern char szAppName  [] ;             // in POPPAD.C
extern char szFileSpec [] ;

long FileLength (HANDLE hFile)
     {
     long   lCurrentPos = _llseek (hFile, 0L, 1) ;
     long   lFileLength = _llseek (hFile, 0L, 2) ;

     _llseek (hFile, lCurrentPos, 0) ;

     return lFileLength ;
     }
void OkMessageBox (HWND hwnd, char *szString, char *szFileName)
     {
     char szBuffer [40] ;

     wsprintf (szBuffer, szString, (LPSTR) szFileName) ;

     MessageBox (hwnd, szBuffer, szAppName, MB_OK | MB_ICONEXCLAMATION) ;
     }

BOOL ReadFile (HANDLE hInstance, HWND hwnd, HWND hwndEdit, POFSTRUCT pof,
               char *szFileName, BOOL bAskName)
     {
     DWORD  dwLength ;
     HANDLE hFile, hTextBuffer ;
     LPSTR  lpTextBuffer ;

     if (bAskName)
          {
          if (!DoFileOpenDlg (hInstance, hwnd, szFileSpec, szFileSpec + 1,
                                        0x4010, szFileName, pof))
               return FALSE ;
          }

     if (-1 == (hFile = OpenFile (szFileName, pof, OF_READ | OF_REOPEN)))
          {
          OkMessageBox (hwnd, "Cannot open file %s", szFileName) ;
          return FALSE ;
          }

     if ((dwLength = FileLength (hFile)) >= 32000)
          {
          _lclose (hFile) ;
          OkMessageBox (hwnd, "File %s too large", szFileName) ;
          return FALSE ;
          }

     if (NULL == (hTextBuffer = GlobalAlloc (GHND, (DWORD) dwLength + 1)))
          {
          _lclose (hFile) ;
          OkMessageBox (hwnd, "Cannot allocate memory for %s", szFileName) ;
          return FALSE ;
          }

     lpTextBuffer = GlobalLock (hTextBuffer) ;
     _lread (hFile, lpTextBuffer, (WORD) dwLength) ;
     _lclose (hFile) ;
     lpTextBuffer [(WORD) dwLength] = '\0' ;

     SetWindowText (hwndEdit, lpTextBuffer) ;
     GlobalUnlock (hTextBuffer) ;
     GlobalFree (hTextBuffer) ;
     return TRUE ;
     }

BOOL WriteFile (HANDLE hInstance, HWND hwnd, HWND hwndEdit, POFSTRUCT pof,
                char *szFileName, BOOL bAskName)
     {
     char      szBuffer [40] ;
     HANDLE    hFile, hTextBuffer ;
     NPSTR     npTextBuffer ;
     WORD      wStatus, wLength ;

     if (bAskName)
          {
          if (!DoFileSaveDlg (hInstance, hwnd, szFileSpec, szFileSpec + 1,
                                   &wStatus, szFileName, pof))
               return FALSE ;

          if (wStatus == 1)
               {
               wsprintf (szBuffer, "Replace existing %s", (LPSTR)
szFileName) ;
               if (IDNO == MessageBox (hwnd, szBuffer, szAppName,
                                             MB_YESNO | MB_ICONQUESTION))
                    return FALSE ;
               }
          }
     else
          OpenFile (szFileName, pof, OF_PARSE) ;

     if (-1 == (hFile = OpenFile (szFileName, pof, OF_CREATE | OF_REOPEN)))
          {
          OkMessageBox (hwnd, "Cannot create file %s", szFileName) ;
          return FALSE ;
          }

     wLength = GetWindowTextLength (hwndEdit) ;
     hTextBuffer = (HANDLE) SendMessage (hwndEdit, EM_GETHANDLE, 0, 0L) ;
     npTextBuffer = LocalLock (hTextBuffer) ;

     if (wLength != _lwrite (hFile, npTextBuffer, wLength))
          {
          _lclose (hFile) ;
          OkMessageBox (hwnd, "Cannot write file %s to disk", szFileName) ;
          return FALSE ;
          }

     _lclose (hFile) ;
     LocalUnlock (hTextBuffer) ;

     return TRUE ;
     }

 POPPADP0.C

/*---------------------------------------------------------
   POPPADP0.C -- Popup Notepad Printing -- dummy functions
  ---------------------------------------------------------*/

#include 

extern char szAppName [] ;              // in POPPAD.C

BOOL FAR PASCAL PrintDlgProc (HWND hDlg, WORD message,
                              WORD wParam, LONG lParam)
     {
     return FALSE ;
     }

BOOL FAR PASCAL AbortProc (HDC hPrinterDC, short nCode)
     {
     return FALSE ;
     }

BOOL PrintFile (HANDLE hInstance, HWND hwnd, HWND hwndEdit, char
*szFileName)
     {
     MessageBox (hwnd, "Printing not yet implemented", szAppName,
                       MB_OK | MB_ICONEXCLAMATION) ;
     return FALSE ;
     }

 POPPAD.RC

/*---------------------------
   POPPAD.RC resource script
  ---------------------------*/

#include 
#include "poppad.h"
#include "filedlg.h"

PopPad ICON "poppad.ico"

PopPad MENU
     {
     POPUP "&File"
          {
          MENUITEM "&New",              IDM_NEW
          MENUITEM "&Open...",          IDM_OPEN
          MENUITEM "&Save",             IDM_SAVE
          MENUITEM "Save &As...",       IDM_SAVEAS
          MENUITEM SEPARATOR
          MENUITEM "&Print...",         IDM_PRINT
          MENUITEM SEPARATOR
          MENUITEM "E&xit",             IDM_EXIT
          }
     POPUP "&Edit"
          {
          MENUITEM "&Undo\tAlt+BkSp",   IDM_UNDO
          MENUITEM SEPARATOR
          MENUITEM "Cu&t\tShift+Del",   IDM_CUT
          MENUITEM "&Copy\tCtrl+Ins",   IDM_COPY
          MENUITEM "&Paste\tShift+Ins", IDM_PASTE
          MENUITEM "C&lear\tDel",       IDM_CLEAR
          MENUITEM SEPARATOR
          MENUITEM "&Select All",       IDM_SELALL
          }
     POPUP "&Help"
          {
          MENUITEM "&About PopPad...",  IDM_ABOUT
          }
     }

PopPad ACCELERATORS
     {
     VK_DELETE, IDM_CUT,   VIRTKEY, SHIFT
     VK_INSERT, IDM_COPY,  VIRTKEY, CONTROL
     VK_INSERT, IDM_PASTE, VIRTKEY, SHIFT
     VK_DELETE, IDM_CLEAR, VIRTKEY
     }

AboutBox DIALOG  20, 20, 160, 80
     STYLE WS_POPUP | WS_DLGFRAME
     {
     CTEXT "PopPad"                              -1,   0, 12, 160,  8
     ICON  "PopPad"                              -1,   8,  8,   0,  0
     CTEXT "Popup Editor for Microsoft Windows"  -1,   0, 36, 160,  8
     CTEXT "Copyright (c) Charles Petzold, 1990" -1,   0, 48, 160,  8
     DEFPUSHBUTTON "OK"                        IDOK,  64, 60,  32, 14,
WS_GROUP
     }

PrintDlgBox DIALOG 20, 20, 100, 76
     STYLE WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_VISIBLE
     CAPTION "PopPad"
     {
     CTEXT "Sending",                  -1,  0, 10, 100,  8
     CTEXT "",                  IDD_FNAME,  0, 20, 100,  8
     CTEXT "to print spooler.",        -1,  0, 30, 100,  8
     DEFPUSHBUTTON  "Cancel",    IDCANCEL, 34, 50,  32, 14, WS_GROUP
     }

rcinclude filedlg.dlg

 POPPAD.H

/*----------------------
   POPPAD.H header file
  ----------------------*/

#define IDM_NEW      1
#define IDM_OPEN     2
#define IDM_SAVE     3
#define IDM_SAVEAS   4

#define IDM_PRINT    5

#define IDM_EXIT     6
#define IDM_ABOUT    7

#define IDM_UNDO     8
#define IDM_CUT      9
#define IDM_COPY    10
#define IDM_PASTE   11
#define IDM_CLEAR   12
#define IDM_SELALL  13

 POPPAD.ICO  -- Please refer to the book.

FIG 1008.epb

 POPPAD3.DEF

;------------------------------------
; POPPAD3.DEF module definition file
;------------------------------------

NAME           POPPAD3

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

As you'll recall, the POPPAD series of programs uses a multiline edit
control to do all the editing. The POPPADF.C file serves as an intermediary
between the main POPPAD.C program and the functions in FILEDLG.C. The
ReadFile function in POPPADF.C calls DoFileOpenDlgProc and reads the file
into a global memory block. ReadFile is responsible for reporting if the
file is too large or if memory can't be allocated for the file. W