Visual C++ 6 Unleashed

Visual C++ 6 Unleashed

By Mickey Williams and David Bennett

Handling Dialog Boxes in MFC

Dialog boxes in MFC usually are based on dialog box templates. These dialog box templates are binary resources held in the structure defined by the Win32 DLGTEMPLATE, DLGITEMTEMPLATE, DLGTEMPLATEEX, and DLGITEMTEMPLATEEX. You can fill these structures with positioning information, size, ID, and style flags required to describe every control on a dialog box.

If these structures were created tediously by hand for every dialog box, user interface software would progress at a snail's pace. Fortunately, the Visual Studio dialog box Template Editor is a powerful tool dedicated to this task.

After you create a dialog box template resource, you can create a CDialog-derived class that uses the template to create the dialog box and then manages the exchange between window controls and your dialog box's member variables.

Creating Dialog Box Template Resources

You can create dialog boxes and add controls by using the Resource Editor, as Figure 5.1 shows.

05fig01.gif

Figure 5.1 Editing a dialog box with Visual Studio's Dialog Box Editor.

After you create the dialog box, Visual Studio saves the details in a text format in your project's .rc file. These details provide the source code required for the Resource Compiler to generate the required DLGTEMPLATE and DLGITEMTEMPLATE binary structures.

You'll rarely need to edit the .rc file directly, because the Resource Editor should solely maintain it. It is interesting to note the code produced after editing a dialog box, however, such as this entry produced for the dialog box shown in Figure 5.1:

IDD_CUSTOM_DIALOG DIALOG DISCARDABLE  0, 0, 232, 135
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Custom Dialog"
FONT 8, "MS Sans Serif"
BEGIN
    DEFPUSHBUTTON   "OK",IDOK,175,7,50,14
    PUSHBUTTON      "Cancel",IDCANCEL,175,24,50,14
    LTEXT           "Static Text",IDC_STATIC,7,23,44,8
    EDITTEXT        IDC_EDIT1,53,19,97,14,ES_AUTOHSCROLL
    LISTBOX         IDC_LIST1,7,64,147,64,LBS_SORT | LBS_NOINTEGRALHEIGHT |
                    WS_VSCROLL | WS_TABSTOP
    CONTROL         "DateTimePicker1",IDC_DATETIMEPICKER1,"SysDateTimePick32",
                    DTS_RIGHTALIGN | WS_TABSTOP,7,42,146,15
    CONTROL         "Tree1",IDC_TREE1,"SysTreeView32",WS_BORDER | WS_TABSTOP,
                    161,42,64,86
END

When you compile the resources, the compiler produces a file with a .res extension that holds the binary format of these dialog box templates (and the other resources). This .res file then is linked with the program code (.obj files) to produce your target .exe, .dll, or .lib file.

Creating a CDialog-Derived Class

You can use ClassWizard to generate a CDialog-derived class by invoking the wizard while the new dialog box is displayed in the Resource Editor. You could create the derived class by hand, but the wizard makes this process much easier.

ClassWizard detects that there is no class currently held in the .clw file that uses the ID of the new dialog box template resource. It then displays a dialog box to let you select an existing handler class or create a new one (see Figure 5.2).

05fig02.gif

Figure 5.2 ClassWizard prompts you to create or select a dialog box handler class.

Normally, you would create a new dialog box handler class, but you sometimes may want to select an existing implementation.

If you choose to create a new dialog box handler class, the wizard displays the New Class dialog box, as Figure 5.3 shows.

05fig03.gif

Figure 5.3 The New Class dialog box.

The New Class dialog box lets you change the name of the class. It then generates filenames based on the name you have specified. You can change these autogenerated filenames if they aren't appropriate.

The CDialog base class and your dialog box template ID are selected automatically. If your application is object linking and embedding (OLE)-enabled, you also can specify any OLE automation specific details for the new dialog box.

After you click OK in the New Class dialog box, you see the new dialog box handler class in the ClassView pane. If you examine the new header, you see a minefield of //{{AFX_... comments created by ClassWizard. When you subsequently add member variables and functions with ClassWizard, it adds the new code inside the appropriate comment sections.

You'll also notice that the dialog box ID is enumerated as the IDD member with a value based on your dialog box template's resource ID, such as this:

enum {IDD = IDD_CUSTOM_DIALOG };

If you examine the dialog constructor function, you'll see that it passes this ID down to the CDialog base class, along with an optional parent-window pointer:

CCustomDlg::CCustomDlg(CWnd* pParent /*=NULL*/)
    : CDialog(CCustomDlg::IDD, pParent)
{
  //{{AFX_DATA_INIT(CCustomDlg)
    // NOTE: the ClassWizard will add member initialization here
  //}}AFX_DATA_INIT
}

The //{{AFX_DATA_INIT section in the constructor implementation is used by ClassWizard to initialize any member variables that you insert using the wizard.

The dialog box class definition also includes a declaration for the DoDataExchange() virtual function (covered in more detail later in the section, "The Data Exchange and Validation Mechanism"), and a message map declaration:

DECLARE_MESSAGE_MAP()

All classes that implement the DECLARE_MESSAGE_MAP macro must be derived from the CCmdTarget class. The dialog box class is derived from CCmdTarget through the CWnd class. This is because the dialog box is a window, and your CDialog-derived class must process messages from its own window and the various dialog box controls (which are child windows).

Displaying the Dialog Box

At this point, you can create an instance of the dialog box handler class and display the dialog box. Dialog boxes normally are displayed in a modal sense. (Modeless dialog boxes are discussed later in this chapter in "Modeless Dialog Boxes.") When a modal dialog box is displayed, users are unable to interact with any other part of the application until they click OK or Cancel to exit the dialog box.

The CDialog class's DoModal() function initiates the modal process and ends when the EndDialog() member function is called. The default implementation of CDialog calls EndDialog() in response to the user clicking the OK or Cancel button. You can use the integer value returned from DoModal() to find out which button was pressed. This integer actually is returned from the parameter passed to EndDialog(), which by default is IDOK or IDCANCEL in response to the OK or Cancel button.

The following lines show how an object of the CCustomDlg class is constructed and displayed, as well as how the return code determines which button was clicked to close the dialog box:

CCustomDlg dlgCustom;
if (dlgCustom.DoModal()==IDOK)
    AfxMessageBox("User Hit OK");
else
    AfxMessageBox("User Hit Cancel");

If you do want to use a dialog box template constructed in memory, you should construct a CDialog-derived class calling the constructor with no parameters. Then you can call the InitModalIndirect() function passing a pointer to a DLGTEMPLATE structure in memory, or an HGLOBAL handle to a global memory segment. If the initialization suc ceeds, InitModalIndirect() returns a TRUE value. You then can call DoModal() as normal to display the modal dialog box.

Dialog Box Coordinates

When you edit a dialog box in the Resource Editor, you'll notice that the coordinates don't correspond to the current screen resolution. Dialog boxes use their own coordinate system based on the size of the font being used in the dialog box. This method helps with the problem of matching the sizes of controls and group boxes to the various font sizes.

When the dialog box is displayed, the control positions and sizes are converted from dialog box units to real-screen units, depending on the size of the font used. Most dialog boxes use the default system font, but you can set different fonts when editing the dialog box, from within your code by calling the CWnd base class's SetFont() function, or by overriding the CDialog class's OnSetFont() function and supplying a pointer to your required dialog box font.

These real-screen units are calculated from the average height and width of the current dialog box font. The real-screen units are one-eighth of the height and one-fourth of the width of the dialog box units.

You can convert between these dialog box units and the real-screen coordinates by passing a RECT structure holding the dialog box coordinates to the MapDialogRect()function. MapDialogRect() then translates these dialog box coordinates into screen coordinates.

Changing the Input Focus and Default Buttons

When you edit a dialog box template in the Resource Editor, you can set the tab order by pressing Ctrl+D. This action displays sequence numbers above the controls and lets you change the order by clicking on various controls in sequence.

Whenever the user presses the Tab button while the dialog box is displayed, the input focus moves to the next control in this tab order.

A set of member CDialog functions lets you change the current input focus from within your code based on this tab order.

You can set the focus to a specific control after the dialog box is displayed by calling GotoDlgCtrl() and passing a pointer to the control's window. You can find this pointer by ID by calling GetDlgItem(). To set the input focus to the OK button, for example, you could call the following from within your dialog box class (after activation):

GotoDlgCtrl(GetDlgItem(IDOK));

The NextDlgCtrl() and PrevDlgCtrl() functions let you move forward and backward through the tab order, setting the input focus to the next or preceding control from the current control with input focus.

Normally the OK button is the default button, so when a user presses the Enter key, the OK button is clicked. You can change this behavior by passing a different button ID to SetDefID(). The alternative button then becomes the default clicked when the user presses Enter. The corresponding GetDefID() returns the ID of the current default button.

If you are using context-sensitive help, you can set the context help ID for a dialog box by calling SetHelpID() and passing the ID that corresponds to the documentation for that dialog box.

+ Share This