- Table of Contents
- Copyright
- About the Authors
- About the Contributors
- Acknowledgments
- Tell Us What You Think!
- Introduction
- How to Use This Book
- What You Need to Use This Book
- What's New in Visual C++ 6.0
- Contacting the Main Author
- Part I: Introduction
- Chapter 1. The Visual C++ 6.0 Environment
- Part II: MFC Programming
- Chapter 2. MFC Class Library Overview
- Chapter 3. MFC Message Handling Mechanism
- Chapter 4. The Document View Architecture
- Chapter 5. Creating and Using Dialog Boxes
- Handling Dialog Boxes in MFC
- Dialog Box Data Exchange and Validation
- Derived Control Classes in Dialog Boxes
- Modeless Dialog Boxes
- Summary
- Chapter 6. Working with Device Contexts and GDI Objects
- Chapter 7. Creating and Using Property Sheets
- Chapter 8. Working with the File System
- Chapter 9. Using Serialization with File and Archive Objects
- Part III: Internet Programming with MFC
- Chapter 10. MFC and the Internet Server API (ISAPI)
- Chapter 11. The WinInet API
- Chapter 12. MFC HTML Support
- Part IV: Advanced Programming Topics
- Chapter 13. Using the Standard C++ Library
- Chapter 14. Error Detection and Exception Handling Techniques
- Chapter 15. Debugging and Profiling Strategies
- Chapter 16. Multithreading
- Chapter 17. Using Scripting and Other Tools to Automate the Visual C++ IDE
- Part V: Database Programming
- Chapter 18. Creating Custom AppWizards
- Chapter 19. Database Overview
- Chapter 20. ODBC Programming
- Chapter 21. MFC Database Classes
- Chapter 22. Using OLE DB
- Chapter 23. Programming with ADO
- Part VI: MFC Support for COM and ActiveX
- Chapter 24. Overview of COM and Active Technologies
- Chapter 25. Active Documents
- Chapter 26. Active Containers
- Chapter 27. Active Servers
- Chapter 28. ActiveX Controls
- Part VII: Using the Active Template Library
- Chapter 29. ATL Architecture
- Chapter 30. Creating COM Objects Using ATL
- Chapter 31. Creating ActiveX Controls Using ATL
- Chapter 32. Using ATL to Create MTS and COM+ Components
- Part VIII: Finishing Touches
- Chapter 33. Adding Windows Help
- Part IX: Appendix
Dialog Box Data Exchange and Validation
The controls in a dialog box are specialized windows that store their own copy of the data that the user enters or changes. In the lifetime of a dialog box, you'll normally want to initialize these controls with data from your program, and then save that data back into those variables.
You'll probably also want to validate the values stored in the controls to ensure that they are within acceptable ranges when the user attempts to click OK to exit the dialog box.
Obviously, the first task in this process is to add new member variables to the dialog box handler class that correspond to the controls.
Mapping Member Variables to Controls
You can use ClassWizard to add member variables and provide the mapping for most of the dialog box controls via ClassWizard's Member Variables tab (see Figure 5.4).
Figure 5.4 The ClassWizard Member Variables tab.
The Member Variables tab lists the control IDs of the various controls. You can select an ID and click the Add Variable button to display the Add Member Variable dialog box (see Figure 5.5).
Figure 5.5 The Add Member Variable dialog box.
You'll notice that the Add Member Variable dialog box lets you set a category that can be a value or a control, as well as a variable type. The variable types available change depending on your selection in the Category combo box.
If you set the Category combo box to indicate value mapping, the Variable Type combo box lists member variable types that can be used with the type of control being mapped. These values are great for quick and easy value-oriented transfer, but often you'll need to map a control class to the control so that you can manipulate the control's more advanced features.
You can map a number of variables to the same control so that you can perform easy value transfer and allow control handler class mapping concurrently.
After you add the member variable map, you'll notice that the new member variable is inserted into your dialog box handler class definition between the ClassWizard AFX_DATA-generated comments.
The new variable also initializes your dialog box class's constructor function like this:
//{{AFX_DATA_INIT(CCustomDlg)
m_strCustomEdit = _T("");
//}}AFX_DATA_INIT
You'll see a new entry placed in the DoDataExchange() function like this:
void CCustomDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CCustomDlg)
DDX_Text(pDX, IDC_CUSTOM_EDIT, m_strCustomEdit);
//}}AFX_DATA_MAP
}
The new DDX_Text macro entry automates data transfer between the control identified by IDC_CUSTOM_EDIT and your m_strCustomEdit member variable.
If you were to add a control map for the edit control, you'd see a CEdit member variable entry added to the class definition, and a corresponding DDX_Control macro to map it to the control ID. Consider this example:
DDX_Control(pDX, IDC_CUSTOM_EDIT, m_editCustom);
If you insert other variable types, you'll see different DDX_ macros used to map the various types of controls to various data types. Table 5.1 lists some of these controls.
Table 5.1. Some Common Controls and Their Mapping Classes/Variables
| Control | Mapping Class | Allowable Mapped Data Types |
| Static | CStatic | CString |
| Edit | CEdit | CString, DWORD, UINT, int, long |
| double, float, BOOL, short | ||
| COleDateTime & ColeCurrency | ||
| Button | CButton | None |
| CheckBox | CButton | BOOL |
| 3-State CheckBox | CButton | int |
| Radio | CButton | int |
| ListBox | CListBox | CString, int |
| ComboBox | CComboBox | CString, int |
| Extended Combo | CComboBoxEx | CString, int |
| ScrollBar | CScrollBar | int |
| Spin | CSpinButtonCtrl | None |
| Progress Bar | CProgressCtrl | None |
| Slider Control | CSliderCtrl | int |
| List Control | CListCtrl | None |
| Tree Control | CTreeCtrl | None |
| Date Time Picker | CDateTimeCtrl | CTime, COleDateTime |
| Month Calendar | CMonthCalCtrl | CTime, COleDateTime |
You can add some simple validation maps to certain controls, such as Edit controls. Depending on the variable type mapped, the lower section of the Member Variables tab displays a section that lets you specify validation information.
If you map a CString to an Edit control, for example, you can set the validation rules to limit the maximum number of characters allowed in the Edit control. If you map an integer to the Edit control, you can set upper and lower ranges for the entered value.
If you set any of these validation rules, ClassWizard adds DDV_ routines to the DoDataExchange() function to perform validation, like this:
DDV_MaxChars(pDX, m_strCustomEdit, 10);
Several different DDV_ routines exist for the various types of validation rules, member variables, and control types.
The Data Exchange and Validation Mechanism
The DoDataExchange() function is called several times during the lifetime of a dialog box and performs a variety of tasks. When the dialog box is initialized, this function subclasses any mapped controls through the DDX_Control routine (discussed in more detail later in the section, "Initializing the Dialog Box Controls"). Then it transfers the data held in the member variables to the controls using the DDX_ routines. Finally, after the user clicks OK, the data from the controls is validated using DDV_ routines and then transferred back into the member variables using the DDX_ routines again.
You'll notice that the DoDataExchange() function is passed a pointer to a CDataExchange object. This object holds the details that let the DDX routines know whether they should be transferring data to or from the controls. The DDX_ routines then implement the Windows message required to set or retrieve data from the control associated with the given control ID.
When the m_bSaveAndValidate member is set to TRUE, the data exchange should transfer data from the controls to the member variables and perform validation. It is set to FALSE when data from the member variables should be loaded into the controls. You can add your own custom code to DoDataExchange() to transfer data to or from the controls and check the m_bSaveAndValidate member of the CDataExchange object to see whether you should be transferring the data to or from the control.
The DDV_ routines check m_bSaveAndValidate and perform the validation if it is set to TRUE. If validation should be performed, the DDV_ macro calls the CDataExchange class's PrepareCtrl() function (or PrepareEditCtrl() for Edit controls), passing the ID of the control to be validated. PrepareCtrl() and PrepareEditCtrl() find the HWND handle of the control associated with the passed ID and store it.
If the validation fails, this window handle is used to reset the focus to the control that caused the validation failure. When failing, the validation macro also calls the Fail() member variable, which throws a CUserException to escape the validation function and display a message box to inform the user as to why the validation failed.
The DDX_ and DDV_ routines can use the m_pDlgWnd member of the CDataExchange object to find the control associated with the specified ID by calling GetDlgItem(). GetDlgItem() is a CWnd function that returns a pointer to a child window of the parent window object (in this case, the dialog box) when passed a control ID. The ID for each of the controls is set in the GWL_ID window value of each control when it is created. You can use the GetWindowLong() and SetWindowLong() functions to get and change this control ID value for any of the dialog box controls.
You can add your own custom DDX_ and DDV_ routines to handle custom data types. For most circumstances, though, it is easier just to add validation code directly to the DoDataExchange() function to perform the same transfer and validation functions on your custom data type.
Initializing the Dialog Box Controls
Obviously, the DoDataExchange() function must be called initially to load the default values from the mapped member variables into the controls.
This initialization can't be performed until the dialog box is open, because the controls don't exist yet. After the DoModal() function is entered and the dialog box is created, it enters a message loop to wait for interaction from the user. But the first message it receives before the box actually is displayed is a WM_INITDIALOG. This message tells the dialog box handler class that the dialog box and controls have been created but require initialization.
The base class implementation of CDialog handles this message and calls the UpdateData() function passing a FALSE value. UpdateData() is called to set up the starting conditions for a DoDataExchange() call; DoDataExchange() itself is never called directly (except from UpdateData() itself), because UpdateData() initializes the CDataExchange object and also handles the CUserException thrown by a validation rule violation. The FALSE parameter passed from the CDialog class's OnInitDialog() implementation ends up as the m_bSaveAndValidate member passed in the CDataExchange object to DoDataExchange().
In this way, the default OnInitDialog() implementation initializes the controls to their initial settings from the member variables. You can supply your own handler for OnInitDialog() in your derived class to extend to the default functionality.
When you add a handler for the WM_INITDIALOG message through the Add New Windows Message/Event Handler dialog box, your CDialog-derived class gains an OnInitDialog() function that looks like this:
BOOL CCustomDlg::OnInitDialog()
{
CDialog::OnInitDialog();
// TODO: Add extra initialization here
return TRUE; // return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE
}
You'll notice that OnInitDialog() first calls the base class implementation to initialize the controls. You then can add lines to perform your own specialized initialization. You may want to disable some of the dialog box controls, for example, if you are using the same dialog box in several circumstances. You can do this by passing FALSE to the EnableWindow() function of any of the control mapping classes (those mapped with DDX_Control) like this:
m_editCustom.EnableWindow(FALSE);
All these mapping classes are derived from CWnd, so you can perform any of the CWnd operations or any of the more specialized control-specific operations. In this example, m_editCustom is a CEdit object.
Alternatively, you may want to initialize more sophisticated controls, such as list or tree controls like this list control initialization (where m_listCustom is a CListBox object):
m_listCustom.AddString("Goodbye");
m_listCustom.AddString("Cruel");
m_listCustom.AddString("World");
For the examples shown above, ClassWizard generates code to map the m_listCustom and m_editCustom variables to the edit and list box controls in the dialog box. ClassWizard creates DDX_Control maps to initialize the control mapping variables, like this:
DDX_Control(pDX, IDC_CUSTOM_LIST, m_listCustom); DDX_Control(pDX, IDC_CUSTOM_EDIT, m_editCustom);
If you don't call the base class implementation of OnInitDialog(), you should at least call UpdateData(FALSE) before using any of the mapped control classes in DoDataExchange(). This is because the first pass of the DDX_Control routines also is responsible for initializing the HWND member variables of the control mapping classes. If you try to use these mapping classes without setting the HWND member, they won't work and will cause an assertion.
You'll also notice that OnInitDialog() must return a Boolean value. If you return TRUE, Windows sets the input focus to the first control in the dialog box (as defined by the tab order). If you return FALSE, you must set the input focus to a specific control yourself from within OnInitDialog().
Retrieving Data from the Controls
You can call UpdateData() passing TRUE to retrieve and validate the current data from the controls. Once again, DoDataExchange() is called from UpdateData()—this time, passing a TRUE value for the m_bSaveAndValidate flag.
UpdateData() returns a TRUE value if the validation succeeded or FALSE if there was a problem validating the data. You can call UpdateData() at any time during the life of the dialog box while the dialog box and controls are active.
UpdateData() often is useful when you are responding to messages from one control (such as a combo box) but want to use the result from that control to update another. By the same token, you may find that UpdateData() is too sweeping and performs validation before the user has completed the whole dialog box or overwrites values in controls or member variables.
For this reason, you occasionally will find that it is best to reserve the use of UpdateData() for initializing and finally closing the dialog box. You can perform more precise data transfers by using the control mapping classes (such as CEdit, CComboBox, CListBox, and so on). The following lines use a CEdit control mapped object (m_editCustom), for example, to retrieve the current contents of an Edit control:
CString strText; m_editCustom.GetWindowText(strText);
You also can set the text by using the SetWindowText() function. Each of the control mapping classes has a large range of member functions to exchange and manipulate spe cific aspects of the control.
Responding to Control Notifications
Often you'll want to receive notifications from the controls. These messages are sent from the control to the parent dialog box after the user performs a specific action, such as changing the selection in a list box.
You can use the Message Maps tab in ClassWizard to insert the message map entries for these notifications. You can select the ID associated with the required control to display the notification messages available from that control. You then can add handler functions for these notifications by clicking Add Function (see Figure 5.6).
Figure 5.6 The Message Maps tab.
When you add the new message handler, a message handler function is generated in your dialog box class along with a message map macro specific to the type of message selected.
For example, a list box selection changed notification results in the following message map entry:
BEGIN_MESSAGE_MAP(CCustomDlg, CDialog)
//{{AFX_MSG_MAP(CCustomDlg)
ON_LBN_SELCHANGE(IDC_CUSTOM_LIST, OnSelchangeCustomList)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
When you click on a valid list box line, the control posts the LBN_SELCHANGE notification to the parent dialog box. This message map entry then calls your OnSelchangeCustomList() implementation.
Your implementation then can directly manipulate other controls through their control mapping objects. For example, you may want to disable the Edit control if the first item in a list box is selected, but enable it otherwise with a notification handler function like this:
void CCustomDlg::OnSelchangeCustomList()
{
BOOL bEnable = (m_listCustom.GetCurSel()==0);
m_editCustom.EnableWindow(bEnable);
}
Your dialog box's message map entries can include all of the messages sent directly to the dialog box itself, such as WM_SIZE messages if the dialog box can be resized, or WM_PAINT if you want to implement some specialized rendering of your dialog box.
If you don't add specific handlers for these messages, they will be passed down to the CDialog base class (and then the CWnd class) to be handled by the dialog box's default implementations.
Dynamically Mapping Controls
Instead of using ClassWizard to generate member variable control mapping objects and their associated DDX_ routine entries, you may want to just create a local temporary map to a specific control. You can do this by using the GetDlgItem() function and casting the CWnd pointer returned to the specific control mapping class.
To change the text and disable the OK button using a dynamic map, for example, you might add the following code in response to a control notification message:
CButton* pOK = (CButton*)GetDlgItem(IDOK);
if (pOK)
{
pOK->SetWindowText("Not OK");
pOK->EnableWindow(FALSE);
}
If the control ID can't be found, GetDlgItem() returns a NULL value, which you should check; otherwise, you risk a crash caused by calling through a NULL pointer.
You can map any of the control mapping classes to their corresponding controls in this way, and then call any of the control map member functions to send messages that manipulate the control. The corresponding GetDlgCtrlID() function returns the ID associated with any particular control.
Responding to OK and Cancel
When you click OK or Cancel, the framework normally calls CDialog's OnOK() and OnCancel() virtual functions.
The default implementation for OnCancel() is fairly simple and just calls EndDialog() passing IDCANCEL. This closes the dialog box and returns IDCANCEL from the DoModal() function.
The default implementation for OnOk() calls UpdateData(TRUE) to save and validate the information from the dialog box controls. If the validation fails, the dialog box remains open. Otherwise, EndDialog() is called and passed IDOK to close the dialog box.
You can add your own BN_CLICKED handler function for the OK and Cancel buttons to perform specialized validation. If you do this, you normally will want to call UpdateData(TRUE) yourself before your specialized validation to do the standard DoDataExchange() transfer and validation. Then you might perform your own special OnOK() code before calling EndDialog() to close the dialog box. If you were to call the base class implementation from your overridden handler function, you might find that you've lost control over when the UpdateData() and EndDialog() functions are called.
You can call EndDialog() at any time during a modal dialog box's lifetime to close the dialog box.
Derived Control Classes in Dialog Boxes | Next Section

Account Sign In
View your cart