The Dialog That Ate Cincinnati—The EX06A Example

Let's not mess around with wimpy little dialogs. We'll build a monster dialog that contains almost every kind of control. The job will be easy because Visual C++'s dialog editor is there to help us. The finished product is shown in Figure 6-1.

Click to view at full size.

Figure 6-1. The finished dialog in action.

As you can see, the dialog supports a human resources application. These kinds of business programs are fairly boring, so the challenge is to produce something that could not have been done with 80-column punched cards. The program is brightened a little by the use of scroll bar controls for "Loyalty" and "Reliability." Here is a classic example of direct action and visual representation of data! ActiveX controls could add more interest, but you'll have to wait until Chapter 8 for details on ActiveX.

Building the Dialog Resource

Here are the steps for building the dialog resource:

  1. Run AppWizard to generate a project called EX06A. Choose New from Visual C++'s File menu, and then click the Projects tab and select MFC AppWizard (exe). Accept all the defaults but two: select Single Document and deselect Printing And Print Preview. The options and the default class names are shown here.

    As usual, AppWizard sets the new project as the current project.

  2. Create a new dialog resource with ID IDD_DIALOG1. Choose Resource from Visual C++'s Insert menu. The Insert Resource dialog appears. Click on Dialog, and then click New. Visual C++ creates a new dialog resource, as shown here.

    The dialog editor assigns the resource ID IDD_DIALOG1 to the new dialog. Notice that the dialog editor inserts OK and Cancel buttons for the new dialog.

  3. Size the dialog and assign a caption. Enlarge the dialog box to about 5-by-7 inches.

    When you right-click on the new dialog and choose Properties from the pop-up menu, the Dialog Properties dialog appears. Type in the caption for the new dialog as shown in the screen below. The state of the pushpin button in the upper-left corner determines whether the Dialog Properties dialog stays on top of other windows. (When the pushpin is "pushed," the dialog stays on top of other windows.) Click the Toggle Grid button (on the Dialog toolbar) to reveal the grid and to help align controls.

    Click to view at full size.

  4. Set the dialog style. Click on the Styles tab at the top of the Dialog Properties dialog, and then set the style properties as shown in the following illustration.

  5. Set additional dialog styles. Click on the More Styles tab at the top of the Dialog Properties dialog, and then set the style properties as shown here.

  6. Add the dialog's controls. Use the control palette to add each control. (If the control palette is not visible, right-click any toolbar and choose Controls from the list.) Drag controls from the control palette to the new dialog, and then position and size the controls, as shown in Figure 6-1. Here are the control palette's controls.

    The dialog editor displays the position and size of each control in the status bar. The position units are special "dialog units," or DLUs, not device units. A horizontal DLU is the average width of the dialog font divided by 4. A vertical DLU is the average height of the font divided by 8. The dialog font is normally 8-point MS Sans Serif.

    Here's a brief description of the dialog's controls:

  1. Check the dialog's tabbing order. Choose Tab Order from the dialog editor's Layout menu. Use the mouse to set the tabbing order shown below. Click on each control in the order shown, and then press Enter.

    Click to view at full size.

    If you mess up the tab sequence partway through, you can recover with a Ctrl-left mouse click on the last correctly sequenced control. Subsequent mouse clicks will start with the next sequence number.

    A static text control (such as Name or Skill) has an ampersand (&) embedded in the text for its caption. At runtime, the ampersand will appear as an underscore under the character that follows. (See Figure 6-1.) This enables the user to jump to selected controls by holding down the Alt key and pressing the key corresponding to the underlined character. (The related control must immediately follow the static text in the tabbing order.) Thus, Alt-N jumps to the Name edit control and Alt-K jumps to the Skill combo box. Needless to say, designated jump characters should be unique within the dialog. The Skill control uses Alt-K because the SS Nbr control uses Alt-S.

  2. Save the resource file on disk. For safety, choose Save from the File menu or click the Save button on the toolbar to save ex06a.rc. Keep the dialog editor running, and keep the newly built dialog on the screen.

ClassWizard and the Dialog Class

You have now built a dialog resource, but you can't use it without a corresponding dialog class. (The section titled "Understanding the EX06A Application" explains the relationship between the dialog window and the underlying classes.) ClassWizard works in conjunction with the dialog editor to create that class as follows:

  1. Choose ClassWizard from Visual C++'s View menu (or press Ctrl-W). Be sure that you still have the newly built dialog, IDD_DIALOG1, selected in the dialog editor and that EX06A is the current Visual C++ project.

  2. Add the CEx06aDialog class. ClassWizard detects the fact that you've just created a dialog resource without an associated C++ class. It politely asks whether you want to create a class, as shown below.

    Accept the default selection of Create A New Class, and click OK. Fill in the top field of the New Class dialog, as shown here.

  3. Add the CEx06aDialog variables. After ClassWizard creates the CEx06aDialog class, the MFC ClassWizard dialog appears. Click on the Member Variables tab, and the Member Variables page appears, as shown here.

    Click to view at full size.

    You need to associate data members with each of the dialog's controls. To do this, click on a control ID and then click the Add Variable button. The Add Member Variable dialog appears, as shown in the following illustration.

    Type in the member variable name, and choose the variable type according to the following table. Be sure to type in the member variable name exactly as shown; the case of each letter is important. When you're done, click OK to return to the MFC ClassWizard dialog. Repeat this process for each of the listed controls.
    Control IDData MemberType
    IDC_BIOm_strBioCString
    IDC_CATm_nCatint
    IDC_DEPTm_strDeptCString
    IDC_DISm_bInsDisBOOL
    IDC_EDUCm_strEducCString
    IDC_LANGm_nLangCString
    IDC_LIFEm_bInsLifeBOOL
    IDC_LOYALm_nLoyalint
    IDC_MEDm_bInsMedBOOL
    IDC_NAMEm_strNameCString
    IDC_RELYm_nRelyint
    IDC_SKILLm_strSkillCString
    IDC_SSNm_nSsnint

    As you select controls in the MFC ClassWizard dialog, various edit boxes appear at the bottom of the dialog. If you select a CString variable, you can set its maximum number of characters; if you select a numeric variable, you can set its high and low limits. Set the minimum value for IDC_SSN to 0 and the maximum value to 999999999.

    Most relationships between control types and variable types are obvious. The way in which radio buttons correspond to variables is not so intuitive, however. The CDialog class associates an integer variable with each radio button group, with the first button corresponding to value 0, the second to 1, and so forth.

  4. Add the message-handling function for the Special button. CEx06aDialog doesn't need many message-handling functions because the CDialog base class, with the help of Windows, does most of the dialog management. When you specify the ID IDOK for the OK button (ClassWizard's default), for example, the virtual CDialog function OnOK gets called when the user clicks the button. For other buttons, however, you need message handlers.

    Click on the Message Maps tab. The ClassWizard dialog should contain an entry for IDC_SPECIAL in the Object IDs list box. Click on this entry, and double-click on the BN_CLICKED message that appears in the Messages list box. ClassWizard invents a member function name, OnSpecial, and opens the Add Member Function dialog, as shown here.

    You could type in your own function name here, but this time accept the default and click OK. Click the Edit Code button in the MFC ClassWizard dialog. This opens the file ex06aDialog.cpp and moves to the OnSpecial function. Insert a TRACE statement in the OnSpecial function by typing in the boldface code, shown below, which replaces the existing code:

    void CEx06aDialog::OnSpecial()
    {
        TRACE("CEx06aDialog::OnSpecial\n");
    }
    

  5. Use ClassWizard to add an OnInitDialog message-handling function. As you'll see in a moment, ClassWizard generates code that initializes a dialog's controls. This DDX (Dialog Data Exchange) code won't initialize the list-box choices, however, so you must override the CDialog::OnInit-Dialog function. Although OnInitDialog is a virtual member function, ClassWizard generates the prototype and skeleton if you map the WM_INITDIALOG message in the derived dialog class. To do so, click on CEx06aDialog in the Object IDs list box and then double-click on the WM_INITDIALOG message in the Messages list box. Click the Edit Code button in the MFC ClassWizard dialog to edit the OnInitDialog function. Type in the boldface code, which replaces the existing code:

    BOOL CEx06aDialog::OnInitDialog()
    {
        // Be careful to call CDialog::OnInitDialog
        //  only once in this function
        CListBox* pLB = (CListBox*) GetDlgItem(IDC_DEPT);
        pLB->InsertString(-1, "Documentation");
        pLB->InsertString(-1, "Accounting");
        pLB->InsertString(-1, "Human Relations");
        pLB->InsertString(-1, "Security");
    
        // Call after initialization
        return CDialog::OnInitDialog();
    }
    

    You could also use the same initialization technique for the combo boxes, in place of the initialization in the resource.

Connecting the Dialog to the View

Now we've got the resource and the code for a dialog, but it's not connected to the view. In most applications, you would probably use a menu choice to activate a dialog, but we haven't studied menus yet. Here we'll use the familiar mouse-click message WM_LBUTTONDOWN to start the dialog. The steps are as follows:

  1. In ClassWizard, select the CEx06aView class. At this point, be sure that EX06A is Visual C++'s current project.

  2. Use ClassWizard to add the OnLButtonDown member function. You've done this in the examples in earlier chapters. Simply select the CEx06aView class name, click on the CEx06aView object ID, and then double-click on WM_LBUTTONDOWN.

  3. Write the code for OnLButtonDown in file ex06aView.cpp. Add the boldface code below. Most of the code consists of TRACE statements to print the dialog data members after the user exits the dialog. The CEx06aDialog constructor call and the DoModal call are the critical statements, however:

    void CEx06aView::OnLButtonDown(UINT nFlags, CPoint point)
    {
        CEx06aDialog dlg;
        dlg.m_strName  = "Shakespeare, Will";
        dlg.m_nSsn     = 307806636;
        dlg.m_nCat     = 1;  // 0 = hourly, 1 = salary
        dlg.m_strBio   = "This person is not a well-motivated tech writer";
        dlg.m_bInsLife = TRUE;
        dlg.m_bInsDis  = FALSE;
        dlg.m_bInsMed  = TRUE;
        dlg.m_strDept  = "Documentation";
        dlg.m_strSkill = "Writer";
        dlg.m_nLang    = 0;
        dlg.m_strEduc  = "College";
        dlg.m_nLoyal   = dlg.m_nRely = 50;
        int ret = dlg.DoModal();
        TRACE("DoModal return = %d\n", ret);
        TRACE("name = %s, ssn = %d, cat = %d\n",
              dlg.m_strName, dlg.m_nSsn, dlg.m_nCat);
        TRACE("dept = %s, skill = %s, lang = %d, educ = %s\n",
              dlg.m_strDept, dlg.m_strSkill, dlg.m_nLang, dlg.m_strEduc);
        TRACE("life = %d, dis = %d, med = %d, bio = %s\n",
              dlg.m_bInsLife, dlg.m_bInsDis, dlg.m_bInsMed, dlg.m_strBio);
        TRACE("loyalty = %d, reliability = %d\n",
              dlg.m_nLoyal, dlg.m_nRely);
    }
    

  4. Add code to the virtual OnDraw function in file ex06aView.cpp. To prompt the user to press the left mouse button, code the CEx06aView::OnDraw function. (The skeleton was generated by AppWizard.) The following boldface code (which you type in) replaces the existing code:

    void CEx06aView::OnDraw(CDC* pDC)
    {
        pDC->TextOut(0, 0, "Press the left mouse button here.");
    }
    

  5. To ex06aView.cpp, add the dialog class include statement. The OnLButtonDown function above depends on the declaration of class CEx06aDialog. You must insert the include statement

    #include "ex06aDialog.h"

    at the top of the CEx06aView class source code file (ex06aView.cpp), after the statement

    #include "ex06aView.h"

  6. Build and test the application. If you have done everything correctly, you should be able to build and run the EX06A application through Visual C++. Try entering data in each control, and then click the OK button and observe the TRACE results in the Debug window. Notice that the scroll bar controls don't do much yet; we'll attend to them later. Notice what happens when you press Enter while typing in text data in a control: the dialog closes immediately.

Understanding the EX06A Application

When your program calls DoModal, control is returned to your program only when the user closes the dialog. If you understand that, you understand modal dialogs. When you start creating modeless dialogs, you'll begin to appreciate the programming simplicity of modal dialogs. A lot happens "out of sight" as a result of that DoModal call, however. Here's a "what calls what" summary:

CDialog::DoModal
    CEx06aDialog::OnInitDialog
        …additional initialization…
        CDialog::OnInitDialog
            CWnd::UpdateData(FALSE)
                CEx06aDialog::DoDataExchange
    user enters data…
    user clicks the OK button
    CEx06aDialog::OnOK
        …additional validation…
        CDialog::OnOK
            CWnd::UpdateData(TRUE)
                CEx06aDialog::DoDataExchange
            CDialog::EndDialog(IDOK)

OnInitDialog and DoDataExchange are virtual functions overridden in the CEx06aDialog class. Windows calls OnInitDialog as part of the dialog initialization process, and that results in a call to DoDataExchange, a CWnd virtual function that was overridden by ClassWizard. Here is a listing of that function:

void CEx06aDialog::DoDataExchange(CDataExchange* pDX)
{
    CDialog::DoDataExchange(pDX);
    //{{AFX_DATA_MAP(CEx06aDialog)
    DDX_Text(pDX, IDC_BIO, m_strBio);
    DDX_Radio(pDX, IDC_CAT, m_nCat);
    DDX_LBString(pDX, IDC_DEPT, m_strDept);
    DDX_Check(pDX, IDC_DIS, m_bInsDis);
    DDX_CBString(pDX, IDC_EDUC, m_strEduc);
    DDX_CBIndex(pDX, IDC_LANG, m_nLang);
    DDX_Check(pDX, IDC_LIFE, m_bInsLife);
    DDX_Scroll(pDX, IDC_LOYAL, m_nLoyal);
    DDX_Check(pDX, IDC_MED, m_bInsMed);
    DDX_Text(pDX, IDC_NAME, m_strName);
    DDX_Scroll(pDX, IDC_RELY, m_nRely);
    DDX_CBString(pDX, IDC_SKILL, m_strSkill);
    DDX_Text(pDX, IDC_SSN, m_nSsn);
    DDV_MinMaxInt(pDX, m_nSsn, 0, 999999999);
    //}}AFX_DATA_MAP 
}

The DoDataExchange function and the DDX_ (exchange) and DDV_ (validation) functions are "bidirectional." If UpdateData is called with a FALSE parameter, the functions transfer data from the data members to the dialog controls. If the parameter is TRUE, the functions transfer data from the dialog controls to the data members. DDX_Text is overloaded to accommodate a variety of data types.

The EndDialog function is critical to the dialog exit procedure. DoModal returns the parameter passed to EndDialog. IDOK accepts the dialog's data, and IDCANCEL cancels the dialog.

You can write your own "custom" DDX function and wire it into Visual C++. This feature is useful if you're using a unique data type throughout your application. See MFC Technical Note #26 in the online documentation.