Enhancing the Dialog Program

The EX06A program required little coding for a lot of functionality. Now we'll make a new version of this program that uses some hand-coding to add extra features. We'll eliminate EX06A's rude habit of dumping the user in response to a press of the Enter key, and we'll hook up the scroll bar controls.

Taking Control of the OnOK Exit

In the original EX06A program, the CDialog::OnOK virtual function handled the OK button, which triggered data exchange and the exit from the dialog. Pressing the Enter key happens to have the same effect, and that might or might not be what you want. If the user presses Enter while in the Name edit control, for example, the dialog closes immediately.

What's going on here? When the user presses Enter, Windows looks to see which pushbutton has the input focus, as indicated on the screen by a dotted rectangle. If no button has the focus, Windows looks for the default pushbutton that the program or the resource specifies. (The default pushbutton has a thicker border.) If the dialog has no default button, the virtual OnOK function is called, even if the dialog does not contain an OK button.

You can disable the Enter key by writing a do-nothing CEx06aDialog::OnOK function and adding the exit code to a new function that responds to clicking the OK button. Here are the steps:

  1. Use ClassWizard to "map" the IDOK button to the virtual OnOK function. In ClassWizard, choose IDOK from the CEx06aDialog Object IDs list, and then double-click on BN_CLICKED. This generates the prototype and skeleton for OnOK.

  2. Use the dialog editor to change the OK button ID. Select the OK button, change its ID from IDOK to IDC_OK, and then uncheck its Default Button property. Leave the OnOK function alone.

  3. Use ClassWizard to create a member function called OnClickedOk. This CEx06aDialog class member function is keyed to the BN_CLICKED message from the newly renamed control IDC_OK.

  4. Edit the body of the OnClickedOk function in ex06aDialog.cpp. This function calls the base class OnOK function, as did the original CEx06aDialog::OnOK function. Here is the code:

    void CEx06aDialog::OnClickedOk()
    {
        TRACE("CEx06aDialog::OnClickedOk\n");
        CDialog::OnOK(); 
    }
    

  5. Edit the original OnOK function in ex06aDialog.cpp. This function is a "leftover" handler for the old IDOK button. Edit the code as shown here:

    void CEx06aDialog::OnOK()
    {
        // dummy OnOK function -- do NOT call CDialog::OnOK()
        TRACE("CEx06aDialog::OnOK\n"); 
    }
    

  6. Build and test the application. Try pressing the Enter key now. Nothing should happen, but TRACE output should appear in the Debug window. Clicking the OK button should exit the dialog as before, however.

For Win32 Programmers

Dialog controls send WM_ COMMAND notification messages to their parent dialogs. For a single button click, for example, the bottom 16 bits of wParam contain the button ID, the top 16 bits of wParam contain the BN_CLICKED notification code, and lParam contains the button handle. Most window procedure functions process these notification messages with a nested switch statement. MFC "flattens out" the message processing logic by "promoting" control notification messages to the same level as other Windows messages.

For a Delete button (for example), ClassWizard generates notification message map entries similar to these:

ON_BN_CLICKED(IDC_DELETE, OnDeleteClicked)
ON_BN_DOUBLECLICKED(IDC_DELETE, OnDeleteDblClicked)

Button events are special because they generate command messages if your dialog class doesn't have notification handlers like the ones above. As Chapter 13 explains, the application framework "routes" these command messages to various objects in your application. You could also map the control notifications with a more generic ON_ COMMAND message-handling entry like this:

ON_COMMAND(IDC_DELETE, OnDelete)

In this case, the OnDelete function is unable to distinguish between a single click and a double click, but that's no problem because few Windows-based programs utilize double clicks for buttons.

OnCancel Processing

Just as pressing the Enter key triggers a call to OnOK, pressing the Esc key triggers a call to OnCancel, which results in an exit from the dialog with a DoModal return code of IDCANCEL. EX06A does no special processing for IDCANCEL; therefore, pressing the Esc key (or clicking the Close button) closes the dialog. You can circumvent this process by substituting a dummy OnCancel function, following approximately the same procedure you used for the OK button.

Hooking Up the Scroll Bar Controls

The dialog editor allows you to include scroll bar controls in your dialog, and ClassWizard lets you add integer data members. You must add code to make the Loyalty and Reliability scroll bars work.

Scroll bar controls have position and range values that can be read and written. If you set the range to (0, 100), for example, a corresponding data member with a value of 50 positions the scroll box at the center of the bar. (The function CScrollBar::SetScrollPos also sets the scroll box position.) The scroll bars send the WM_ HSCROLL and WM_ VSCROLL messages to the dialog when the user drags the scroll box or clicks the arrows. The dialog's message handlers must decode these messages and position the scroll box accordingly.

Each control you've seen so far has had its own individual message handler function. Scroll bar controls are different because all horizontal scroll bars in a dialog are tied to a single WM_HSCROLL message handler and all vertical scroll bars are tied to a single WM_VSCROLL handler. Because this monster dialog contains two horizontal scroll bars, the single WM_ HSCROLL message handler must figure out which scroll bar sent the scroll message.

Here are the steps for adding the scroll bar logic to EX06A:

  1. Add the class enum statements for the minimum and maximum scroll range. In ex06aDialog.h, add the following lines at the top of the class declaration:

    enum { nMin = 0 };
    enum { nMax = 100 }; 
    

  2. Edit the OnInitDialog function to initialize the scroll ranges. In the OnInitDialog function, we'll set the minimum and the maximum scroll values such that the CEx06aDialog data members represent percentage values. A value of 100 means "Set the scroll box to the extreme right"; a value of 0 means "Set the scroll box to the extreme left."

    Add the following code to the CEx06aDialog member function OnInitDialog in the file ex06aDialog.cpp:

    CScrollBar* pSB = (CScrollBar*) GetDlgItem(IDC_LOYAL);
    pSB->SetScrollRange(nMin, nMax);
    
    pSB = (CScrollBar*) GetDlgItem(IDC_RELY);
    pSB->SetScrollRange(nMin, nMax); 
    

  3. Use ClassWizard to add a scroll bar message handler to CEx06aDialog. Choose the WM_HSCROLL message, and then add the member function OnHScroll. Enter the following boldface code:

    void CEx06aDialog::OnHScroll(UINT nSBCode, UINT nPos,
                                 CScrollBar* pScrollBar)
    {
        int nTemp1, nTemp2;
    
        nTemp1 = pScrollBar->GetScrollPos();
        switch(nSBCode) {
        case SB_THUMBPOSITION:
            pScrollBar->SetScrollPos(nPos);
            break;
        case SB_LINELEFT: // left arrow button
            nTemp2 = (nMax - nMin) / 10;
            if ((nTemp1 - nTemp2) > nMin) {
                nTemp1 -= nTemp2;
            }
            else {
                nTemp1 = nMin;
            }
            pScrollBar->SetScrollPos(nTemp1);
            break;
        case SB_LINERIGHT: // right arrow button
            nTemp2 = (nMax - nMin) / 10;
            if ((nTemp1 + nTemp2) < nMax) {
                nTemp1 += nTemp2;
            }
            else {
                nTemp1 = nMax;
            }
            pScrollBar->SetScrollPos(nTemp1);
            break;
        } 
    }
    

The scroll bar functions use 16-bit integers for both range and position.

  1. Build and test the application. Build and run EX06A again. Do the scroll bars work this time? The scroll boxes should "stick" after you drag them with the mouse, and they should move when you click the scroll bars' arrows. (Notice that we haven't added logic to cover the user's click on the scroll bar itself.)