Win32 Series - Modal Dialog Boxes

来源:互联网 发布:淘宝代充微信红包 编辑:程序博客网 时间:2024/05/24 15:38

http://www-user.tu-chemnitz.de/~heha/petzold/

 

 

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, 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 can do 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 some other  information. (Perhaps a telephone number for technical support?) The first program we'll  look at does nothing except display an About dialog box. The ABOUT1 program is shown  in Figure 11-1.

Figure 11-1. The ABOUT1 program.

 

ABOUT1.C

/*------------------------------------------ ABOUT1.C -- About Box Demo Program No. 1 (c) Charles Petzold, 1998 ------------------------------------------*/#include <windows.h>#include "resource.h"LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;BOOL CALLBACK AboutDlgProc (HWND, UINT, WPARAM, LPARAM) ;int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szAppName[] = TEXT ("About1") ; MSG msg ; HWND hwnd ; WNDCLASS wndclass ; 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 = (HBRUSH) GetStockObject (WHITE_BRUSH) ; wndclass.lpszMenuName = szAppName ; wndclass.lpszClassName = szAppName ; if (!RegisterClass (&wndclass)) { MessageBox (NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR) ; return 0 ; } hwnd = CreateWindow (szAppName, TEXT ("About Box Demo Program"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ; ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } return msg.wParam ;}LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam){ static HINSTANCE hInstance ; switch (message) { case WM_CREATE : hInstance = ((LPCREATESTRUCT) lParam)->hInstance ; return 0 ; case WM_COMMAND : switch (LOWORD (wParam)) { case IDM_APP_ABOUT : DialogBox (hInstance, TEXT ("AboutBox"), hwnd, AboutDlgProc) ; break ; } return 0 ; case WM_DESTROY : PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ;}BOOL CALLBACK AboutDlgProc (HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam){ switch (message) { case WM_INITDIALOG : return TRUE ; case WM_COMMAND : switch (LOWORD (wParam)) { case IDOK : case IDCANCEL : EndDialog (hDlg, 0) ; return TRUE ; } break ; } return FALSE ;}

 

ABOUT1.RC (excerpts)

//Microsoft Developer Studio generated resource script.#include "resource.h"#include "afxres.h"/////////////////////////////////////////////////////////////////////////////// DialogABOUTBOX DIALOG DISCARDABLE  32, 32, 180, 100STYLE DS_MODALFRAME | WS_POPUPFONT 8, "MS Sans Serif"BEGIN    DEFPUSHBUTTON   "OK",IDOK,66,80,50,14    ICON            "ABOUT1",IDC_STATIC,7,7,21,20    CTEXT           "About1",IDC_STATIC,40,12,100,8    CTEXT           "About Box Demo Program",IDC_STATIC,7,40,166,8    CTEXT           "(c) Charles Petzold, 1998",IDC_STATIC,7,52,166,8END/////////////////////////////////////////////////////////////////////////////// MenuABOUT1 MENU DISCARDABLE BEGIN    POPUP "&Help"    BEGIN        MENUITEM "&About About1...",            IDM_APP_ABOUT    ENDEND/////////////////////////////////////////////////////////////////////////////// IconABOUT1                  ICON    DISCARDABLE     "About1.ico"

 

RESOURCE.H (excerpts)

// Microsoft Developer Studio generated include file.// Used by About1.rc#define IDM_APP_ABOUT                   40001#define IDC_STATIC                      -1

 

ABOUT1.ICO

You create the icon and the menu in this program the same way as described in  the last chapter. Both the icon and the menu have text ID names of "About1." The menu  has one option, which generates a WM_COMMAND message with an ID of  IDM_APP_ABOUT. This causes the program to display the dialog box shown in Figure 11-2.

Click to view at full size.

Figure 11-2. The ABOUT1 program's dialog box.

The Dialog Box and Its Template

To add a dialog box to an application in the Visual C++ Developer Studio, you begin  by selecting Resource from the Insert menu and choosing Dialog Box. You are then  presented with a dialog box with a title bar and caption ("Dialog") and OK and Cancel buttons.  A Controls toolbar allows you to insert various controls in the dialog box.

Developer Studio gives the dialog box a standard ID of IDD_DIALOG1. You can  right-click this name (or the dialog box itself) and select Properties from the menu. For  this program, change the ID to "AboutBox" (with quotation marks). To be consistent with  the dialog box I created, change the X Pos and Y Pos fields to 32. This is to indicate  where the dialog box is displayed relative to the upper left corner of the client area of the  program's window. (I'll discuss dialog box coordinates in more detail shortly.)

Now, still in the Properties dialog, select the Styles tab. Unclick the Title Bar  check box because this dialog box does not have a title bar. Click the close button on the  Properties dialog.

Now it's time to actually design the dialog box. We won't be needing the  Cancel button, so click that button and press the Delete key on your keyboard. Click the OK  button, and move it to the bottom of the dialog. At the bottom of the Developer Studio  window will be a small bitmap on a toolbar that lets you center the control horizontally in  the window. Press that button.

We want the program's icon to appear in the dialog box. To do so, press the  Pictures button on the floating Controls toolbar. Move the mouse to the surface of the dialog  box, press the left button, and drag a square. This is where the icon will appear. Press the  right mouse button on this square, and select Properties from the menu. Leave the ID  as IDC_STATIC. This identifier will be defined in RESOURCE.H as  -1, which is used for all IDs that the C program does not refer to. Change the Type to Icon. You should be able to  type the name of the program's icon in the Image field, or, if you've already created the  icon, you can select the name ("About1") from the combo box.

For the three static text strings in the dialog box, select Static Text from the  Controls toolbar and position the text in the dialog window. Right-click the control, and  select Properties from the menu. You'll type the text you want to appear in the Caption field  of the Properties box. Select the Styles tab to select Center from the Align Text field.

As you add these text strings, you may want to make the dialog box larger. Select  it and drag the outline. You can also select and size controls. It's often easier to use  the keyboard cursor movement keys for this. The arrow keys by themselves move the  controls; the arrow keys with Shift depressed let you change the controls' sizes. The  coordinates and sizes of the selected control are shown in the lower right corner of the  Developer Studio window.

If you build the application and later look at the ABOUT1.RC resource script file,  you'll see the dialog box template that Developer Studio generated. The dialog box that I  designed has a template that looks like this:

ABOUTBOX DIALOG DISCARDABLE  32, 32, 180, 100STYLE DS_MODALFRAME | WS_POPUPFONT 8, "MS Sans Serif"BEGIN    DEFPUSHBUTTON   "OK",IDOK,66,80,50,14    ICON            "ABOUT1",IDC_STATIC,7,7,21,20    CTEXT           "About1",IDC_STATIC,40,12,100,8    CTEXT           "About Box Demo Program",IDC_STATIC,7,40,166,8    CTEXT           "(c) Charles Petzold, 1998",IDC_STATIC,7,52,166,8END

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  keywords DIALOG and DISCARDABLE, 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 the font used for the dialog box (in this case, an 8-point MS Sans Serif font):  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 the character height. Thus, for  this particular dialog box, the upper left corner of the dialog box is 5 characters from the  left edge of the main window's client area and 2-1/2 characters from the top edge. The  dialog itself 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 and the font you've selected. Because font characters are often approximately  twice as high as they are wide, the dimensions on both the  x-axis and they-axis are nearly the same.

The STYLE statement in the template is similar to the style field of a  CreateWindow call. WS_POPUP and DS_MODALFRAME are normally used for modal dialog boxes,  but we'll explore some alternatives later on.

Within the BEGIN and END statements (or left and right brackets, if you'd prefer,  when designing dialog box templates by hand), you define the child window controls that  will appear in the dialog box. This dialog box uses three types of child window  controls: DEFPUSHBUTTON (a default push button), ICON (an icon), and CTEXT (centered  text). The format of these statements is

control-type "text" id, xPos, yPos, xWidth, yHeight, iStyle

The iStyle value at the end is optional; it specifies additional window styles using  identifiers defined in the Windows header files.

These DEFPUSHBUTTON, ICON, and CTEXT identifiers are used in dialog boxes  only. 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 inChapter 9.

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 argument in a CreateWindow call when you create a child window control in a program.

The id 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 ID values  are equivalent to the child window IDs used in the CreateWindow function when we created child windows in Chapter 9. Because the text and icon controls do not send messages  back to the parent window, these values are set to IDC_STATIC, which is defined in  RESOURCE.H as -1. The ID value for the push button is IDOK, which is defined in WINUSER.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 of the average width and 1/8 of the height of a 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 CALLBACK AboutDlgProc (HWND hDlg, UINT message,                             WPARAM wParam, LPARAM lParam){     switch (message)     {     case WM_INITDIALOG :          return TRUE ;               case WM_COMMAND :          switch (LOWORD (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; as with a window procedure, the dialog box procedure must be defined as a  CALLBACK function. Although I've usedhDlg 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 an LRESULT; a dialog box procedure returns  a BOOL, which is defined in the Windows header files 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  receives. This message is sent only to dialog box procedures. If the dialog box procedure  returns TRUE, 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 the 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  the low word ofwParam. 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.

Invoking the Dialog Box

During the processing of WM_CREATE in  WndProc, ABOUT1 obtains the program's instance handle and stores it in a static variable:

hInstance = ((LPCREATESTRUCT) lParam)->hInstance ;

ABOUT1 checks for WM_COMMAND messages where the low word of  wParam is equal to IDM_APP_ABOUT. When it gets one, the program calls DialogBox:

DialogBox (hInstance, TEXT ("AboutBox"), hwnd, AboutDlgProc) ;

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 dialog procedure. If you use a numeric  identifier 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 11-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 the low word ofwParam equal to the ID of the default push button when Enter or  the Spacebar is pressed. That ID is IDOK. You can also end the dialog box by pressing  Escape. In that case Windows sends a WM_COMMAND message with an ID equal to IDCANCEL.

The DialogBox function you call to display the dialog box will not return control  toWndProc 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 butis used in ABOUT2.) WndProc can then return control to Windows.

Even when the dialog box is displayed, however,  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  theSendMessage call in AboutDlgProc would start off like this:

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

Variations on a Theme

Although the dialog editor and other resource editors in the Visual C++ Developer  Studio seemingly make it unnecessary to even look at resource scripts, it is still helpful to  learn resource script syntax. Particularly for dialog templates, knowing the syntax allows you  to have a better feel for the scope and limitations of dialog boxes. You may even want to  create a dialog box template manually if there's something you need to do that can't be done  otherwise (such as in the HEXCALC program later in this chapter). The resource compiler  and resource script syntax is documented in /Platform  SDK/Windows Programming Guidelines/Platform SDK  Tools/Compiling/Using the Resource Compiler.

The window style of the dialog box is specified in the Properties dialog in the  Developer Studio, which is translated into 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 ¦ DS_MODALFRAME

However, you can also experiment with other styles. Some dialog boxes have a  caption bar that identifies the dialog's purpose and lets the user move the dialog box around  the display using the mouse. This is the style WS_CAPTION. 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 they-coordinate.

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

CAPTION "Dialog Box Caption"

Or while processing the WM_INITDIALOG message in the dialog procedure, you can use

SetWindowText (hDlg, TEXT ("Dialog Box Caption")) ;

If you use the WS_CAPTION style, you can also add a system menu box with the  WS_SYSMENU style. This style allows the user to select Move or Close from the system menu.

Selecting Resizing from the Border list box of the Properties dialog (equivalent to  the style WS_THICKFRAME) allows the user to resize the dialog box, although this is  unusual. If you don't mind being even more unusual, you can also try adding a maximize box  to the dialog box style.

You can even add a menu to a dialog box. The dialog box template will include  the statement

MENU menu-name

The argument is either the name or the 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, or if they're not, that  they duplicate the same commands.

The FONT statement lets you set something other than the system font for use  with dialog box text. This was once uncommon in dialog boxes but is now quite normal.  Indeed, Developer Studio selects the 8-point MS Sans Serif font by default in any dialog  box you create. A Windows program can achieve a unique look by shipping a special font  with a program that is used solely by the program for dialog boxes and other text output.

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,  specify a window class name in the dialog box template:

CLASS "class-name"

There are some other considerations involved, but I'll demonstrate this approach 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 arguments 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 address of 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.

You may want the benefit of using the Windows dialog manager, but you may  not want to (or be able to) define the dialog template in a resource script. Perhaps you  want the program to create a dialog box dynamically as it's running. The function to look at  is DialogBoxIndirect, which uses data structures to define the template. 

In the dialog box template in ABOUT1.RC, the shorthand notation CTEXT, ICON,  and DEFPUSHBUTTON is used to define the three types of child window controls we want  in the dialog box. There are several others that 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 some common control types:

 

Control TypeWindow ClassWindow StylePUSHBUTTONbuttonBS_PUSHBUTTON ¦ WS_TABSTOPDEFPUSHBUTTONbuttonBS_DEFPUSHBUTTON ¦ WS_TABSTOPCHECKBOXbuttonBS_CHECKBOX ¦ WS_TABSTOPRADIOBUTTONbuttonBS_RADIOBUTTON ¦ WS_TABSTOPGROUPBOXbuttonBS_GROUPBOX ¦ WS_TABSTOPLTEXTstaticSS_LEFT ¦ WS_GROUPCTEXTstaticSS_CENTER ¦ WS_GROUPRTEXTstaticSS_RIGHT ¦ WS_GROUPICONstaticSS_ICONEDITTEXTeditES_LEFT ¦ WS_BORDER ¦ WS_TABSTOPSCROLLBARscrollbarSBS_HORZLISTBOXlistboxLBS_NOTIFY ¦ WS_BORDER ¦ WS_VSCROLLCOMBOBOXcomboboxCBS_SIMPLE ¦ WS_TABSTOP

 

The 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", id, xPos, yPos, xWidth, yHeight, iStyle

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

control-type id, xPos, yPos, xWidth, yHeight, iStyle

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

In Chapter 9, 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 of  the average character width and 1/8 of 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", id, xPos, yPos, xWidth, yHeight, BS_LEFTTEXT

Notice that the control type EDITTEXT automatically has a border. If you want  to create a child window edit control without a border, you can use

EDITTEXT id, xPos, yPos, xWidth, yHeight, NOT WS_BORDER

The resource compiler also recognizes a generalized control statement that looks like

CONTROL "text", id, "class", iStyle, 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. In Developer Studio, you create a statement like this using  the Custom Control option from the Controls toolbar. In the ABOUT3 program, shown in  Figure 11-5, I show how you can use this to create a control  whose window class is defined in your program.

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 theDialogBox 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

hCtrl = CreateWindow (TEXT ("button"), TEXT ("OK"),                      WS_CHILD ¦ WS_VISIBLE ¦ WS_TABSTOP ¦ BS_PUSHBUTTON,                      10 * cxChar / 4, 20 * cyChar / 8,                      32 * cxChar / 4, 14 * cyChar / 8,                      hDlg, IDOK, hInstance, NULL) ;

where cxChar and cyChar are the width and height of the dialog box font character  in pixels. ThehDlg 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 11-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.

Figure 11-3. The ABOUT2 program.

 

ABOUT2.C

/*------------------------------------------   ABOUT2.C -- About Box Demo Program No. 2               (c) Charles Petzold, 1998  ------------------------------------------*/#include <windows.h>#include "resource.h"LRESULT CALLBACK WndProc      (HWND, UINT, WPARAM, LPARAM) ;BOOL    CALLBACK AboutDlgProc (HWND, UINT, WPARAM, LPARAM) ;     int iCurrentColor  = IDC_BLACK,    iCurrentFigure = IDC_RECT ;int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,                    PSTR szCmdLine, int iCmdShow){     static TCHAR szAppName[] = TEXT ("About2") ;     MSG          msg ;     HWND         hwnd ;     WNDCLASS     wndclass ;          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 = (HBRUSH) GetStockObject (WHITE_BRUSH) ;     wndclass.lpszMenuName  = szAppName ;     wndclass.lpszClassName = szAppName ;          if (!RegisterClass (&wndclass))     {          MessageBox (NULL, TEXT ("This program requires Windows NT!"),                      szAppName, MB_ICONERROR) ;          return 0 ;     }          hwnd = CreateWindow (szAppName, TEXT ("About Box Demo Program"),                          WS_OVERLAPPEDWINDOW,                          CW_USEDEFAULT, CW_USEDEFAULT,                          CW_USEDEFAULT, CW_USEDEFAULT,                          NULL, NULL, hInstance, NULL) ;          ShowWindow (hwnd, iCmdShow) ;     UpdateWindow (hwnd) ;           while (GetMessage (&msg, NULL, 0, 0))     {          TranslateMessage (&msg) ;          DispatchMessage (&msg) ;     }     return msg.wParam ;}void PaintWindow (HWND hwnd, int iColor, int iFigure){     static COLORREF crColor[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 (crColor[iColor - IDC_BLACK]) ;     hBrush = (HBRUSH) SelectObject (hdc, hBrush) ;          if (iFigure == IDC_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, int iColor, int iFigure){     InvalidateRect (hCtrl, NULL, TRUE) ;     UpdateWindow (hCtrl) ;     PaintWindow (hCtrl, iColor, iFigure) ;}LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam){     static HINSTANCE hInstance ;     PAINTSTRUCT      ps ;          switch (message)     {     case WM_CREATE:          hInstance = ((LPCREATESTRUCT) lParam)->hInstance ;          return 0 ;               case WM_COMMAND:          switch (LOWORD (wParam))          {          case IDM_APP_ABOUT:               if (DialogBox (hInstance, TEXT ("AboutBox"), hwnd, AboutDlgProc))                    InvalidateRect (hwnd, NULL, TRUE) ;               return 0 ;          }          break ;               case WM_PAINT:          BeginPaint (hwnd, &ps) ;          EndPaint (hwnd, &ps) ;                         PaintWindow (hwnd, iCurrentColor, iCurrentFigure) ;          return 0 ;                    case WM_DESTROY:          PostQuitMessage (0) ;          return 0 ;     }     return DefWindowProc (hwnd, message, wParam, lParam) ;}BOOL CALLBACK AboutDlgProc (HWND hDlg, UINT message,                             WPARAM wParam, LPARAM lParam){     static HWND hCtrlBlock ;     static int  iColor, iFigure ;          switch (message)     {     case WM_INITDIALOG:          iColor  = iCurrentColor ;          iFigure = iCurrentFigure ;                    CheckRadioButton (hDlg, IDC_BLACK, IDC_WHITE,   iColor) ;          CheckRadioButton (hDlg, IDC_RECT,  IDC_ELLIPSE, iFigure) ;                    hCtrlBlock = GetDlgItem (hDlg, IDC_PAINT) ;                    SetFocus (GetDlgItem (hDlg, iColor)) ;          return FALSE ;               case WM_COMMAND:          switch (LOWORD (wParam))          {          case IDOK:               iCurrentColor  = iColor ;               iCurrentFigure = iFigure ;               EndDialog (hDlg, TRUE) ;               return TRUE ;                         case IDCANCEL:               EndDialog (hDlg, FALSE) ;               return TRUE ;                         case IDC_BLACK:          case IDC_RED:          case IDC_GREEN:          case IDC_YELLOW:          case IDC_BLUE:          case IDC_MAGENTA:          case IDC_CYAN:          case IDC_WHITE:               iColor = LOWORD (wParam) ;               CheckRadioButton (hDlg, IDC_BLACK, IDC_WHITE, LOWORD (wParam)) ;               PaintTheBlock (hCtrlBlock, iColor, iFigure) ;               return TRUE ;                         case IDC_RECT:          case IDC_ELLIPSE:               iFigure = LOWORD (wParam) ;               CheckRadioButton (hDlg, IDC_RECT, IDC_ELLIPSE, LOWORD (wParam)) ;               PaintTheBlock (hCtrlBlock, iColor, iFigure) ;               return TRUE ;          }          break ;               case WM_PAINT:          PaintTheBlock (hCtrlBlock, iColor, iFigure) ;          break ;     }     return FALSE ;}

 

ABOUT2.RC (excerpts)

//Microsoft Developer Studio generated resource script.#include "resource.h"#include "afxres.h"/////////////////////////////////////////////////////////////////////////////// DialogABOUTBOX DIALOG DISCARDABLE  32, 32, 200, 234STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTIONFONT 8, "MS Sans Serif"BEGIN    ICON            "ABOUT2",IDC_STATIC,7,7,20,20    CTEXT           "About2",IDC_STATIC,57,12,86,8    CTEXT           "About Box Demo Program",IDC_STATIC,7,40,186,8    LTEXT           "",IDC_PAINT,114,67,74,72    GROUPBOX        "&Color",IDC_STATIC,7,60,84,143    RADIOBUTTON     "&Black",IDC_BLACK,16,76,64,8,WS_GROUP | WS_TABSTOP    RADIOBUTTON     "B&lue",IDC_BLUE,16,92,64,8    RADIOBUTTON     "&Green",IDC_GREEN,16,108,64,8    RADIOBUTTON     "Cya&n",IDC_CYAN,16,124,64,8    RADIOBUTTON     "&Red",IDC_RED,16,140,64,8    RADIOBUTTON     "&Magenta",IDC_MAGENTA,16,156,64,8    RADIOBUTTON     "&Yellow",IDC_YELLOW,16,172,64,8    RADIOBUTTON     "&White",IDC_WHITE,16,188,64,8    GROUPBOX        "&Figure",IDC_STATIC,109,156,84,46,WS_GROUP    RADIOBUTTON     "Rec&tangle",IDC_RECT,116,172,65,8,WS_GROUP | WS_TABSTOP    RADIOBUTTON     "&Ellipse",IDC_ELLIPSE,116,188,64,8    DEFPUSHBUTTON   "OK",IDOK,35,212,50,14,WS_GROUP    PUSHBUTTON      "Cancel",IDCANCEL,113,212,50,14,WS_GROUPEND/////////////////////////////////////////////////////////////////////////////// IconABOUT2                  ICON    DISCARDABLE     "About2.ico"/////////////////////////////////////////////////////////////////////////////// MenuABOUT2 MENU DISCARDABLE BEGIN    POPUP "&Help"    BEGIN        MENUITEM "&About",                      IDM_APP_ABOUT    ENDEND

 

RESOURCE.H (excerpts)

// Microsoft Developer Studio generated include file.// Used by About2.rc#define IDC_BLACK                       1000#define IDC_BLUE                        1001#define IDC_GREEN                       1002#define IDC_CYAN                        1003#define IDC_RED                         1004#define IDC_MAGENTA                     1005#define IDC_YELLOW                      1006#define IDC_WHITE                       1007#define IDC_RECT                        1008#define IDC_ELLIPSE                     1009#define IDC_PAINT                       1010#define IDM_APP_ABOUT                   40001#define IDC_STATIC                      -1ABOUT2.ICO

 

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 in 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  11-4. 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 IDC ("ID for a control"). These identifiers are defined in RESOURCE.H.

Click to view at full size.

Figure 11-4. The ABOUT2 program's dialog box.

When you create the radio buttons in the ABOUT2 dialog box, create them in  the order shown. This ensures that Developer Studio defines sequentially valued  identifiers, which is assumed by the program. Also, uncheck the Auto option for each radio  button. The Auto Radio Button requires less code but is initially more mysterious. Give them  the identifiers shown above in ABOUT2.RC.

Check the Group option in the Properties dialog for the OK and Cancel buttons,  and for the Figure group box, and for the first radio buttons (Black and Rectangle) in each  group. Check the Tab Stop check box for these two radio buttons.

When you have all the controls in the dialog box approximately positioned and  sized, choose the Tab Order option from the Layout menu. Click each control in the order  shown in the ABOUT2.RC resource script.

Working with Dialog Box Controls

In Chapter 9, 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 11-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 the low word of wParam set to the ID of the control. The high word of  wParam is a notification code, and lParam is the window handle of the control. For a radio button, this notification code is always BN_CLICKED, which equals  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 might recall from Chapter 9 that checking and unchecking a button  requires that you send the child window control a BM_CHECK message. To turn on a button  check mark, you use

SendMessage (hwndCtrl, BM_SETCHECK, 1, 0) ;

To turn off the check mark, you use

SendMessage (hwndCtrl, BM_SETCHECK, 0, 0) ;

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 the radio buttons. You know only the one from  which you're getting the message. 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, id) ;

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

id = GetWindowLong (hwndCtrl, GWL_ID) ;

but this is rarely necessary.)

You'll notice in the ABOUT2.H header file shown in Figure 11-3 that the ID  values for the eight colors are sequential from IDC_BLACK to IDC_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 int iColor ;[other program lines]case WM_COMMAND:     switch (LOWORD (wParam))     {     [other program lines]     case IDC_BLACK:     case IDC_RED:     case IDC_GREEN:     case IDC_YELLOW:     case IDC_BLUE:     case IDC_MAGENTA:     case IDC_CYAN:     case IDC_WHITE:          iColor = LOWORD (wParam) ;           for (i = IDC_BLACK, i <= IDC_WHITE, i++)               SendMessage (GetDlgItem (hDlg, i),                            BM_SETCHECK, i == LOWORD (wParam), 0) ;          return TRUE ;     [other program lines]

This approach works satisfactorily. You've saved the new color value in  iColor, 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, id, iMsg, wParam, lParam) ;

It is equivalent to

SendMessage (GetDlgItem (hDlg, id), id, wParam, lParam) ;

Now the loop would look like this:

for (i = IDC_BLACK, i <= IDC_WHITE, i++)     SendDlgItemMessage (hDlg, i, BM_SETCHECK, i == LWORD (wParam), 0) ;

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

CheckRadioButton (hDlg, idFirst, idLast, idCheck) ;

This function turns off the check marks for all radio button controls with IDs from idFirst toidLast except for the radio button with an ID of  idCheck, which is checked. The IDs must be sequential. Now we can get rid of the loop entirely and use:

CheckRadioButton (hDlg, IDC_BLACK, IDC_WHITE, LOWORD (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, idCheckbox, iCheck) ;

If iCheck 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 by using

iCheck = IsDlgButtonChecked (hDlg, idCheckbox) ;

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

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

If you define a BS_AUTOCHECKBOX control, you don't need to process  the WM_COMMAND message at all. You can simply obtain the current status of the button  by usingIsDlgButtonChecked before terminating the dialog box. However, if you use  the BS_AUTORADIOBUTTON style, IsDlgButtonChecked is not quite satisfactory because  you'd need to call it for each radio button until the function returned TRUE. Instead, you'd  still trap WM_COMMAND messages to keep track of which button gets pressed.

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 WINUSER.H as 1) and the  Cancel button has an ID of IDCANCEL (defined as 2). The OK button is the default:

    DEFPUSHBUTTON   "OK",IDOK,35,212,50,14    PUSHBUTTON      "Cancel",IDCANCEL,113,212,50,14

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 LOWORD 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, the LOWORD ofwParam is set to the ID of the push button with the  input focus. If no push button in the dialog box is the default push button, Windows sends  the dialog box procedure a WM_COMMAND message with the LOWORD of 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 the LOWORD of 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  callingEndDialog:

switch (LWORD (wParam)){case IDOK:     iCurrentColor  = iColor ;     iCurrentFigure = iFigure ;     EndDialog (hDlg, TRUE) ;     return TRUE ;case IDCANCEL :     EndDialog (hDlg, FALSE) ;     return TRUE ;

ABOUT2's window procedure uses the global variables  iCurrentColor andiCurrentFigure when drawing the rectangle or ellipse in the program's client area. AboutDlgProc uses the static local variables iColor and iFigure when drawing the figure within the dialog box.

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

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

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

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 argument toEndDialog 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.

Avoiding Global Variables

The use of global variables in ABOUT2 may or may not be disturbing to you. Some  programmers (myself included) prefer to keep the use of global variables to a bare  minimum. TheiCurrentColor and iCurrentFigure variables in ABOUT2 certainly seem to qualify  as legitimate candidates for global definitions because they must be used in both the  window procedure and the dialog procedure. However, a program that has many dialog  boxes, each of which can alter the values of several variables, could easily have a confusing  proliferation of global variables.

You might prefer to conceive of each dialog box within a program as being  associated with a data structure containing all the variables that can be altered by the dialog  box. You would define these structures in typedef statements. For example, in ABOUT2 you  might define a structure associated with the About box like so:

typedef struct{     int iColor, iFigure ;}ABOUTBOX_DATA ;

In WndProc, you define and initialize a static variable based on this structure:

static ABOUTBOX_DATA ad = { IDC_BLACK, IDC_RECT } ;

Also in WndProc, replace all occurrences of  iCurrentColor andiCurrentFigure withad.iColor and ad.iFigure. When you invoke the dialog box, use DialogBoxParam rather thanDialogBox. This function has a fifth argument that can be any 32-bit value you'd  like. Generally, it is set to a pointer to a structure, in this case the ABOUTBOX_DATA  structure inWndProc:

case IDM_ABOUT:     if (DialogBoxParam (hInstance, TEXT ("AboutBox"),                          hwnd, AboutDlgProc, &ad))          InvalidateRect (hwnd, NULL, TRUE) ;     return 0 ;

Here's the key: the last argument to  DialogBoxParam is passed to the dialog  procedure aslParam in the WM_INITDIALOG message.

The dialog procedure would have two static variables (a structure and a pointer  to a structure) based on the ABOUTBOX_DATA structure:

static ABOUTBOX_DATA ad, * pad ;

In AboutDlgProc this definition replaces the definitions of  iColor andiFigure. At the outset of the WM_INITDIALOG message, the dialog procedure sets the values of these  two variables fromlParam:

pad = (ABOUTBOX_DATA *) lParam ;ad = * pad ;

In the first statement, pad is set to the  lParam pointer. That is,pad actually points to  the ABOUTBOX_DATA structure defined in WndProc. The second statement performs a  field-by-field structure copy from the structure in WndProc to the local structure in DlgProc.

Now, throughout AboutDlgProc, replace  iFigure and iColor with ad.iColor andad.iFigure except in the code for when the user presses the OK button. In that case,  copy the contents of the local structure back to the structure in WndProc:

case IDOK:     * pad = ad ;     EndDialog (hDlg, TRUE) ;     return TRUE ;

Tab Stops and Groups

In Chapter 9, 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, 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 the 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.

Look at the dialog box template in ABOUT2.RC. 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 (Black) in the  Color group box and the Figure group box 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 (Rectangle) in the  Figure group box 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 the WS_GROUP style to prevent the cursor movement  keys from doing anything when the push buttons have the input focus.

When using ABOUT2, 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 (&) in the text field 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:

LTEXT  ""  IDC_PAINT, 114, 67, 72, 72

This area is 18 characters wide and 9 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, iColor, iFigure) ;

In AboutDlgProc, the window handle  hCtrlBlock had been set during the processing of  the WM_INITDIALOG message:

hCtrlBlock = GetDlgItem (hDlg, IDD_PAINT) ;

Here's the PaintTheBlock function:

void PaintTheBlock (HWND hCtrl, int iColor, int iFigure){     InvalidateRect (hCtrl, NULL, TRUE) ;     UpdateWindow (hCtrl) ;     PaintWindow (hCtrl, iColor, iFigure) ;}

This invalidates the child window control, generates a WM_PAINT message to the  control window procedure, 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 force the user to 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 when bEnable is TRUE (nonzero) and disables it when 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 9) 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 Developer Studio in the  Properties dialog box associated with a custom control. This translates into a CONTROL statement  in the dialog box template. The ABOUT3 program, shown in Figure 11-5, does exactly that.

Figure 11-5. The ABOUT3 program.

 

ABOUT3.C

/*------------------------------------------   ABOUT3.C -- About Box Demo Program No. 3               (c) Charles Petzold, 1998  ------------------------------------------*/#include <windows.h>#include "resource.h"LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;BOOL    CALLBACK AboutDlgProc (HWND, UINT, WPARAM, LPARAM) ;LRESULT CALLBACK EllipPushWndProc (HWND, UINT, WPARAM, LPARAM) ;int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,                    PSTR szCmdLine, int iCmdShow){     static TCHAR szAppName[] = TEXT ("About3") ;     MSG          msg ;     HWND         hwnd ;     WNDCLASS     wndclass ;     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 = (HBRUSH) GetStockObject (WHITE_BRUSH) ;     wndclass.lpszMenuName  = szAppName ;     wndclass.lpszClassName = szAppName ;          if (!RegisterClass (&wndclass))     {          MessageBox (NULL, TEXT ("This program requires Windows NT!"),                      szAppName, MB_ICONERROR) ;          return 0 ;     }          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 = (HBRUSH) (COLOR_BTNFACE + 1) ;     wndclass.lpszMenuName  = NULL ;     wndclass.lpszClassName = TEXT ("EllipPush") ;     RegisterClass (&wndclass) ;     hwnd = CreateWindow (szAppName, TEXT ("About Box Demo Program"),                          WS_OVERLAPPEDWINDOW,                          CW_USEDEFAULT, CW_USEDEFAULT,                          CW_USEDEFAULT, CW_USEDEFAULT,                          NULL, NULL, hInstance, NULL) ;          ShowWindow (hwnd, iCmdShow) ;     UpdateWindow (hwnd) ;           while (GetMessage (&msg, NULL, 0, 0))     {          TranslateMessage (&msg) ;          DispatchMessage (&msg) ;     }     return msg.wParam ;}LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam){     static HINSTANCE hInstance ;          switch (message)     {     case WM_CREATE :          hInstance = ((LPCREATESTRUCT) lParam)->hInstance ;          return 0 ;               case WM_COMMAND :          switch (LOWORD (wParam))          {          case IDM_APP_ABOUT :               DialogBox (hInstance, TEXT ("AboutBox"), hwnd, AboutDlgProc) ;               return 0 ;          }          break ;               case WM_DESTROY :          PostQuitMessage (0) ;          return 0 ;     }     return DefWindowProc (hwnd, message, wParam, lParam) ;}BOOL CALLBACK AboutDlgProc (HWND hDlg, UINT message,                             WPARAM wParam, LPARAM lParam){     switch (message)     {     case WM_INITDIALOG :          return TRUE ;               case WM_COMMAND :          switch (LOWORD (wParam))          {          case IDOK :               EndDialog (hDlg, 0) ;               return TRUE ;          }          break ;     }     return FALSE ;}LRESULT CALLBACK EllipPushWndProc (HWND hwnd, UINT message,                                    WPARAM wParam, LPARAM lParam){     TCHAR       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 = (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,               GetWindowLong (hwnd, GWL_ID), (LPARAM) hwnd) ;          return 0 ;     }     return DefWindowProc (hwnd, message, wParam, lParam) ;}

 

ABOUT3.RC (excerpts)

//Microsoft Developer Studio generated resource script.#include "resource.h"#include "afxres.h"/////////////////////////////////////////////////////////////////////////////// DialogABOUTBOX DIALOG DISCARDABLE  32, 32, 180, 100STYLE DS_MODALFRAME | WS_POPUPFONT 8, "MS Sans Serif"BEGIN    CONTROL         "OK",IDOK,"EllipPush",WS_GROUP | WS_TABSTOP,73,79,32,14    ICON            "ABOUT3",IDC_STATIC,7,7,20,20    CTEXT           "About3",IDC_STATIC,40,12,100,8    CTEXT           "About Box Demo Program",IDC_STATIC,7,40,166,8    CTEXT           "(c) Charles Petzold, 1998",IDC_STATIC,7,52,166,8END/////////////////////////////////////////////////////////////////////////////// MenuABOUT3 MENU DISCARDABLE BEGIN    POPUP "&Help"    BEGIN        MENUITEM "&About About3...",            IDM_APP_ABOUT    ENDEND/////////////////////////////////////////////////////////////////////////////// IconABOUT3                  ICON    DISCARDABLE     "icon1.ico"

 

RESOURCE.H (excerpts)

// Microsoft Developer Studio generated include file.// Used by About3.rc#define IDM_APP_ABOUT                   40001#define IDC_STATIC                      -1

ABOUT3.ICO

The window class we'll be registering is called EllipPush ("elliptical push  button"). In the dialog editor in Developer Studio, delete both the Cancel and OK buttons. To  add a control based on this window class, select Custom Control from the Controls toolbar.  In the Properties dialog for this control, type EllipPush in the Class field. Rather than a  DEFPUSHBUTTON statement appearing in the dialog box template, you'll see 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 = (HBRUSH) (COLOR_WINDOW + 1) ;wndclass.lpszMenuName  = NULL ;wndclass.lpszClassName = TEXT ("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 fromGetClientRect and obtains the text that appears in the push button  fromGetWindowText. It uses the Windows functions  Ellipse andDrawText to draw the  ellipse and the text.

The processing of the WM_KEYUP and WM_LBUTTONUP messages is simple:

case WM_KEYUP :     if (wParam != VK_SPACE)          break ;                              // fall throughcase WM_LBUTTONUP :     SendMessage (GetParent (hwnd), WM_COMMAND,          GetWindowLong (hwnd, GWL_ID), (LPARAM) hwnd) ;     return 0 ;

The window procedure obtains the handle of its parent window (the dialog box)  usingGetParent and sends a WM_COMMAND message with wParam equal to the control's ID. The ID is obtained using GetWindowLong. 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 11-6. You can use this same  method to create other customized controls for dialog boxes.

Click to view at full size.

Figure 11-6. A customized push button created by ABOUT3.

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 (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 by 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, 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 usingSetWindowLong and  GetWindowLong.

 

原创粉丝点击