If it weren't for communication, we would all be living in a lonely world indeed. With no way to transfer our thoughts and ideas to other people, our minds would be sealed traps, forever locking up all the knowledge we managed to acquire over our lifetimes. Luckily, we have, over the years, managed to solve many of our communications problems. But these solutions were a long time coming. Way, way back, early humans communicated with body gestures. Then language came along, enabling the transference of more sophisticated ideas. When machines were invented, so were ways to communicate with them, including such devices as patch bays and keyboards.
What has this to do with MFC? When Windows and other graphically oriented user interfaces came along, programmers needed to find a new way to retrieve information from the computer usera communication problem for the modern age. In the days of DOS, the program could simply print a prompt on-screen and direct the user to enter whatever value the program needed. With Windows, however, getting data from the user is not so simple. Because a window's client area should be reserved for other purposes, a well designed Windows application gets most user input through dialog boxes.
MFC includes several types of dialog boxes. In this chapter, you learn to use MFC to display and handle your own custom dialog boxes, which you design with Developer Studio's dialog box editor.
As mentioned in the introduction to this chapter, dialog boxes are one way a Windows application can get information from the user. Chances are that your Windows application will have several dialog boxes, each designed to retrieve a different type of information from your user. However, before you can add these dialog boxes to your program, you must create them. To make this job simpler, Developer Studio includes an excellent resource editor. In Chapter 6, "Using Menus," you used the resource editor to create menus; you can use this handy visual tool to create all types of resources, including bitmaps, icons, string tables, and, of course, dialog boxes.
See Creating a Menu Resource, (ch. 6)
Because dialog boxes are used so extensively in Windows applications, MFC provides several classes that you can use to make dialog box manipulation easier and more convenient. Although you can use MFC's CDialog class directly, you'll most often derive your own dialog box class from CDialog in order to have more control over the way that your dialog box operates. However, MFC also provides classes for more specific types of dialog boxes, including CColorDialog, CFontDialog, and CFileDialog.
The minimum steps for adding a dialog box to your MFC application are as follows:
Although the preceding steps are all you need to display a simple dialog box, you'll usually need much more control over your dialog box than these steps provide. For example, this method of displaying a dialog box enables no way to retrieve data from the dialog box, which means the method is really only useful for dialog boxes that display information to the usersort of like a glorified message box. To create a dialog box that you can control, perform the following steps:
In the rest of this chapter, you'll see how to perform all of the steps listed previously, including how to write your dialog box class and how to provide this class with automatic data transfer and validation. In the next section, you learn to use Developer Studio's dialog box editor.
As you now know, the first step toward adding a dialog box to your MFC application is creating the dialog box resource, which acts as a sort of template for Windows. When Windows sees the dialog box resource in your program, it uses the commands in the resource to construct the dialog box for you. To learn how to create a dialog box resource, just perform the following steps:
Just like controls, a dialog box has a property sheet that you can customize.
You might remember that, when you created your menu resource in Chapter 6, you were able to choose menu IDs from predefined system IDs or create your own custom IDs. Because dialog boxes are often unique to an application (with the exception of the common dialog boxes), you will almost always create your own IDs for both the dialog box and the controls it contains. You can, if you like, accept the default IDs that the dialog box editor creates for you. However, these IDs are generic (i.e. IDD_DIALOG1, IDC_EDIT1, IDC_RADIO1, and so on.), and so you'll probably want to change them to something more specific.
In any case, as you can tell from the default IDs, a dialog box ID usually begins with the prefix IDD_, and control IDs usually begin with the prefix IDC_. You can, of course, use your own prefixes if you like, although sticking with conventions often makes your programs easier to read.
As you learned in Chapter 6, Using Menus, after creating a resource, Developer Studio creates or modifies at least two files that you need to add to your application. If you remember, the first file is called RESOURCE.H and contains the resource IDs that you've defined. You must include RESOURCE.H in any file that refers to these IDs.
See Dealing with Resource Files, (ch. 6)
The second file Developer Studio creates or modifies has the .RC extension and is the resource script that defines all your application's resources. In Chapter 6, you learned that the resource script is like a source code file written in a language that the resource compiler understands. The resource compiler takes the .RC file and compiles it into a .RES file, which is the binary representation of your application's resources. Listing 7.1 shows part of a resource script that defines a dialog box.
Listing 7.1 LST07_01.RCThe Resource Script that Defines a Dialog Box
IDD_DIALOG1 DIALOG DISCARDABLE 0, 0, 203, 151 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "Test Dialog" FONT 8, "MS Sans Serif" BEGIN DEFPUSHBUTTON "OK",IDOK,7,130,91,14 PUSHBUTTON "Cancel",IDCANCEL,105,130,91,14 LTEXT "Name:",IDC_STATIC,14,14,23,10 EDITTEXT IDC_NAME,39,13,143,12,ES_AUTOHSCROLL COMBOBOX IDC_DEPARTMENT,13,74,80,72,CBS_DROPDOWN | CBS_SORT | WS_VSCROLL | WS_TABSTOP LTEXT "Department:",IDC_STATIC,14,62,41,10 GROUPBOX "Availability",IDC_STATIC,99,33,85,87 CONTROL "Monday",IDC_MONDAY,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,119,47,60,10 CONTROL "Tuesday",IDC_TUESDAY,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,119,60,63,10 CONTROL "Wednesday",IDC_WEDNESDAY,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,119,73,56,11 CONTROL "Thursday",IDC_THURSDAY,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,119,87,49,10 CONTROL "Friday",IDC_FRIDAY,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,119,100,38,9 LTEXT "Age:",IDC_STATIC,15,41,17,9 EDITTEXT IDC_AGE,32,38,32,15,ES_AUTOHSCROLL END
If you examine Listing 7.1, even though you might not yet understand the script language, you can easily see hierarchical organization of the dialog box and its controls. Also, you can see that each control's definition includes a number of values including its caption, ID, style, position, and size. When you use Developer Studio's resource editors to create your resources, you don't usually need to modify the resultant resource script directly. However, there might be times when such direct editing will be convenient, so it couldn't hurt to learn more about how resource scripts work. Consult your favorite Windows programming manual for more information on resource scripts.
This chapter's sample program, called Dialog, demonstrates two ways to create and display dialog boxes in your Windows applications. You can find the program, along with all its source code, in the CHAP07\DIALOG folder of this book's CD-ROM (or on your hard drive if you installed the contents of the CD-ROM). To run the program, double-click the DIALOG.EXE file. When you do, you see the window shown in Figure 7.6.
The Dialog application's main window displays the data collected from one of its dialog boxes.
The data displayed in the window are the default values for information that can be collected by one of the program's dialog boxes. You'll take a look at that dialog
box in a second, but first select the Help, About command to display the application's About dialog box. As you can see in Figure 7.7, the About
dialog box only displays information to the user and does not enable the user to enter data. For this reason, the About dialog box can be created and displayed
very easily in an MFC program, as you'll soon see.
The About dialog box doesn't interact with the user (except through the OK button), so it's easy to create and display in the program.
To display the application's secondand much more complexdialog box, select the Dialog, Test command. When you do, a dialog box
containing a number of controls appears. You can use these controls to enter information about an imaginary employee (see Figure 7.8). When you finish entering
information, close the dialog box by clicking OK. The Dialog application's main window then displays the new information you entered into the dialog box's
controls (see Figure 7.9).
The Dialog, Test command reveals a dialog box containing several types of controls.
After transferring data from the dialog box, you can display the user's choices in the window.
This second dialog box is also capable of validating its data. For example, the Name text box will not let you enter more than thirty characters. In addition, the Age text box will reject any value that doesn't fall between 16 and 100. If you enter an invalid value into the Age text box, you will see the warning shown in Figure 7.10, and you will not be allowed to exit the dialog box with the OK button until the value is corrected. Notice also that, if you enter new data into the dialog box and then select the Cancel button, your changes are thrown away and do not appear in the application's window.
Dialog boxes in MFC programs can validate their own data.
Now that you've had a chance to use the Dialog application, it's time to see how it works. Listing 7.2 through Listing 7.5 are the most pertinent source code files for
the Dialog application. Listing 7.2 and Listing 7.3 constitute the application's CMainFrame class, which, of course, represents the application's main window.
Listing 7.4 and Listing 7.5 are the source code files for the CDlg1 class, which represents the dialog box that appears when you select the application's D
ialog, Test command. Due to its simplicity, the program doesn't need a special class for the About dialog box. Also, because there are few changes to the
application class as compared with other programs in this book, the DIALOG.H and DIALOG.CPP files, which are the source code for the CDialogApp class,
are not shown here.
Listing 7.2 MAINFRM.HThe Header File of the Main Window Class
/////////////////////////////////////////////////////////// // MAINFRM.H: Header file for the CMainFrame class, which // represents the application's main window. /////////////////////////////////////////////////////////// class CMainFrame : public CFrameWnd { // Protected data members. protected: CString m_name; UINT m_age; CString m_department; BOOL m_monday; BOOL m_tuesday; BOOL m_wednesday; BOOL m_thursday; BOOL m_friday; // Constructor and destructor. public: CMainFrame(); ~CMainFrame(); // Overrides. protected: virtual BOOL PreCreateWindow(CREATESTRUCT& cs); // Message map functions. protected: // System message handlers. afx_msg void OnPaint(); // Menu command message handlers. afx_msg void OnDialogTest(); afx_msg void OnHelpAbout(); // Update command UI handlers. // None. // Protected member functions. protected: DECLARE_MESSAGE_MAP() };
Listing 7.3 MAINFRM.CPPThe Implementation File of the Main Window Class
/////////////////////////////////////////////////////////// // MAINFRM.CPP: Implementation file for the CMainFrame // class, which represents the application's // main window. /////////////////////////////////////////////////////////// #include <afxwin.h> #include "mainfrm.h" #include "resource.h" #include "dlg1.h" BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd) // Message map entries for system messages. ON_WM_PAINT() // Message map entries for menu commands. ON_COMMAND(IDM_DIALOG_TEST, OnDialogTest) ON_COMMAND(IDM_HELP_ABOUT, OnHelpAbout) // Message map entries for update command UI handlers. // None. END_MESSAGE_MAP() /////////////////////////////////////////////////////////// // CMainFrame: Construction and destruction. /////////////////////////////////////////////////////////// CMainFrame::CMainFrame() { // Create the main frame window. Create(NULL, "Dialog App", WS_OVERLAPPEDWINDOW, rectDefault, NULL, MAKEINTRESOURCE(IDR_MENU1)); // Initialize data members. m_name = ""; m_age = 16; m_department = "Sales"; m_monday = TRUE; m_tuesday = TRUE; m_wednesday = TRUE; m_thursday = TRUE; m_friday = TRUE; } CMainFrame::~CMainFrame() { } /////////////////////////////////////////////////////////// // Overrides. /////////////////////////////////////////////////////////// BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs) { // Set size of the main window. cs.cx = 350; cs.cy = 250; // Call the base class's version. BOOL returnCode = CFrameWnd::PreCreateWindow(cs); return returnCode; } /////////////////////////////////////////////////////////// // Message map functions. /////////////////////////////////////////////////////////// void CMainFrame::OnPaint() { // Create a device context. CPaintDC paintDC(this); // Have Windows fill in a TEXTMETRIC structure. TEXTMETRIC textMetric; paintDC.GetTextMetrics(&textMetric); // Initialize the vertical position of a text line. UINT position = 10; // Display the current name. paintDC.TextOut(10, position, "NAME:"); paintDC.TextOut(130, position, m_name); // Convert and display the current age. char s[4]; wsprintf(s, "%d", m_age); position += textMetric.tmHeight; paintDC.TextOut(10, position, "AGE:"); paintDC.TextOut(130, position, s); // Display the currently selected department. position += textMetric.tmHeight; paintDC.TextOut(10, position, "DEPARTMENT:"); paintDC.TextOut(130, position, m_department); // Display the days the person is available. position += textMetric.tmHeight; paintDC.TextOut(10, position, "AVAILABILITY:"); if (m_monday) paintDC.TextOut(130, position, "Available Monday"); else paintDC.TextOut(130, position, "Not available Monday"); position += textMetric.tmHeight; if (m_tuesday) paintDC.TextOut(130, position, "Available Tuesday"); else paintDC.TextOut(130, position, "Not available Tuesday"); position += textMetric.tmHeight; if (m_wednesday) paintDC.TextOut(130, position, "Available Wednesday"); else paintDC.TextOut(130, position, "Not available Wednesday"); position += textMetric.tmHeight; if (m_thursday) paintDC.TextOut(130, position, "Available Thursday"); else paintDC.TextOut(130, position, "Not available Thursday"); position += textMetric.tmHeight; if (m_friday) paintDC.TextOut(130, position, "Available Friday"); else paintDC.TextOut(130, position, "Not available Friday"); } void CMainFrame::OnDialogTest() { // Create a new dialog-box object. CDlg1 dlg(this); // Copy the most current data into the dialog box. dlg.m_name = m_name; dlg.m_age = m_age; dlg.m_department = m_department; dlg.m_monday = m_monday; dlg.m_tuesday = m_tuesday; dlg.m_wednesday = m_wednesday; dlg.m_thursday = m_thursday; dlg.m_friday = m_friday; // Display the dialog box. int result = dlg.DoModal(); // If the user exited the dialog box with the OK button... if (result == IDOK) { // Copy the dialog's data into this class's data members. m_name = dlg.m_name; m_age = dlg.m_age; m_department = dlg.m_department; m_monday = dlg.m_monday; m_tuesday = dlg.m_tuesday; m_wednesday = dlg.m_wednesday; m_thursday = dlg.m_thursday; m_friday = dlg.m_friday; // Force the window to show the new data. Invalidate(); } } void CMainFrame::OnHelpAbout() { CDialog dlg(IDD_ABOUTDIALOG, this); dlg.DoModal(); }
Listing 7.4 DLG1.HThe Header File of the CDlg1 Class
/////////////////////////////////////////////////////////// // DLG1.H: Header file for the CDlg class. /////////////////////////////////////////////////////////// class CDlg1 : public CDialog { // Constructor. public: CDlg1(CWnd* pParent); // Data transfer variables. public: CString m_department; BOOL m_monday; BOOL m_tuesday; BOOL m_wednesday; BOOL m_thursday; BOOL m_friday; CString m_name; UINT m_age; // Overrides. protected: virtual void DoDataExchange(CDataExchange* pDX); };
Listing 7.5 DLG1.CPPThe Implementation File of the CDlg1 Class
/////////////////////////////////////////////////////////// // DLG1.CPP: Implementation file for the CDLG1 class. /////////////////////////////////////////////////////////// #include <afxwin.h> #include "dialog.h" #include "resource.h" #include "dlg1.h" /////////////////////////////////////////////////////////// // CONSTRUCTOR /////////////////////////////////////////////////////////// CDlg1::CDlg1(CWnd* pParent) : CDialog(IDD_TESTDIALOG, pParent) { // Initialize data transfer variables. m_department = "Sales"; m_monday = TRUE; m_tuesday = TRUE; m_wednesday = TRUE; m_thursday = TRUE; m_friday = TRUE; m_name = ""; m_age = 16; } /////////////////////////////////////////////////////////// // Overrides. /////////////////////////////////////////////////////////// void CDlg1::DoDataExchange(CDataExchange* pDX) { // Call the base class's version. CDialog::DoDataExchange(pDX); // Associate the data transfer variables with // the ID's of the controls. DDX_Text(pDX, IDC_NAME, m_name); DDV_MaxChars(pDX, m_name, 30); DDX_Text(pDX, IDC_AGE, m_age); DDV_MinMaxUInt(pDX, m_age, 16, 100); DDX_CBString(pDX, IDC_DEPARTMENT, m_department); DDX_Check(pDX, IDC_MONDAY, m_monday); DDX_Check(pDX, IDC_TUESDAY, m_tuesday); DDX_Check(pDX, IDC_WEDNESDAY, m_wednesday); DDX_Check(pDX, IDC_THURSDAY, m_thursday); DDX_Check(pDX, IDC_FRIDAY, m_friday); }
Before you get too deep into learning how to create sophisticated dialog boxes, it might be nice to see how easy it can be to display simple dialog boxes that require no complex interaction with the user. A good example is the Dialog application's About dialog box, which does nothing more than display program information to the user. The hardest part of getting the About dialog box up on the screen is creating the dialog box template with the resource editorand that task is just a matter of positioning a few static-text controls. If you look at the OnHelpAbout() function of the CMainFrame class, you see that displaying the dialog box requires only two lines of code, as follows:
CDialog dlg(IDD_ABOUTDIALOG, this); dlg.DoModal();
The first line creates a CDialog object that's associated with the About dialog box template that was defined in the resource editor. The constructor's two arguments are the dialog box's resource ID and a pointer to the dialog box's parent window, which is the CMainFrame window. After the dialog box object has been constructed and associated with the template, a call to the dialog box object's DoModal() member function displays the dialog box on the screen.
The DoModal() function handles all the user's interactions with the dialog box, which, in this case, amounts to little more than waiting for the user to click the OK button. When the user exits the dialog box, the DoModal() function returns, and your program can continue. Because the dialog box object is created locally on the stack, it is automatically deleted when it goes out of scope, which is when the OnHelpAbout() function exits. You could also create the dialog box dynamically on the heap, by using the new operator. In that case, you'd have to delete the object yourself. The following lines illustrate this technique:
CDialog* dlg = new CDialog(IDD_ABOUTDIALOG, this); dlg->DoModal(); delete dlg;
When creating your dialog box's template with the resource editor, make sure that you turn on the dialog box's Visible attribute, which is found in the Dialog's property sheet on the More Styles page. If you fail to set this style attribute, your dialog box will not appear on the screen properly, making the application seem to lock up.
Displaying a simple About dialog box is all well and good. Unfortunately though, most dialog boxes are much more complex, requiring that your application be able to transfer information back and forth between the program and the dialog box. Often, you'll want to initialize the dialog box's controls with default values before displaying the dialog box. After the user enters information into the dialog box, you then need to extract that information so that it can be used in your program. Accomplishing this means not only creating a dialog box template with the resource editor, but also associating that template with your own custom dialog box class derived from MFC's CDialog.
Figure 7.11 shows the Test Dialog from the Dialog application under construction in Developer Studio's dialog box editor. As you can see, the dialog box has ten controls that the user can manipulate. These controls are the Name text box, the Age text box, the Department combo box, the five check boxes for the days of the week, and the OK and Cancel buttons.
As with most dialog boxes, the Dialog application's dialog box was created using Developer Studio's resource editors.
You don't have to worry about the OK and Cancel buttons because MFC handles them for you. When you write your dialog box's class, however, you do need to provide data members for storing the information the user enters into the other controls. The CDlg1 class, which is derived from CDialog, provides the needed data members, as shown in Listing 7.6.
Listing 7.6 LST07_06.CPPDeclaring Data Members for the Dialog Box Controls
public: CString m_department; BOOL m_monday; BOOL m_tuesday; BOOL m_wednesday; BOOL m_thursday; BOOL m_friday; CString m_name; UINT m_age;
Notice that these variables are declared as public data members of the class. This is important because if they were declared as private or protected, your program would not be able to access them outside of the class. (Yes, I know, this is another case where MFC breaks the rules of strict object-oriented design, which dictates that a class's data member should never be accessed from outside of the class.)
If you want a slightly more elegant solution, you can declare the data members as protected, and then declare the window class that must access the variables as a friend class of the dialog box class. Another way to enable access to the data members, and still stick to OOP rules, would be to write public member functions that return or set the values of the protected data members.
The variables you declare for your dialog box's controls must, of course, hold the appropriate type of data for the controls with which they're associated. For example, text boxes that return strings must be associated with string variables, whereas controls that return integers must be associated with integer variables. To determine the correct data type for a particular control, please refer to your Visual C++ online documentation or Windows programming manual.
Besides the data members, your dialog box class must supply a constructor and must override the virtual DoDataExchange() function, which is where the data transfer occurs. The CDlg1 class's constructor is declared like this:
public: CDlg1(CWnd* pParent);
The constructor's single argument is a pointer to the dialog box's parent window. As you'll soon see, the constructor's implementation will pass this pointer, as well as the dialog box template's resource ID, on to the CDialog class's constructor.
The CDlg1 class overrides the DoDataExchange() function like this:
protected: virtual void DoDataExchange(CDataExchange* pDX);
As you can see, DoDataExchange()'s only argument is a pointer to a CDataExchange object, which is responsible for handling the actual transfer of the data between the dialog box's controls and the class's data members.
With the class's header file ready to go, you can start writing the class's implementation. As always, the first step is to include the header files required to compile the code, as shown in Listing 7.7.
Listing 7.7 LST07_07.CPPIncluding the Appropriate Header Files
#include <afxwin.h> #include "dialog.h" #include "resource.h" #include "dlg1.h"
As you might remember from other programs, the first line above includes the general header file for MFC programs. The second line brings in the application class's declaration; the third is your resource IDs; and the fourth is the CDlg1 class's declaration.
The CDlg1 class's constructor is responsible for initializing the dialog box object, as shown in Listing 7.8.
Listing 7.8 LST07_08.CPPConstructing a CDlg1 Object
CDlg1::CDlg1(CWnd* pParent) : CDialog(IDD_TESTDIALOG, pParent) { // Initialize data transfer variables. m_department = "Sales"; m_monday = TRUE; m_tuesday = TRUE; m_wednesday = TRUE; m_thursday = TRUE; m_friday = TRUE; m_name = ""; m_age = 16; }
The CDlg1 constructor receives a single parameter, which is a pointer to the dialog box's parent window. The constructor passes this pointer, along with the dialog box's resource ID, to the base class's constructor. It is the resource ID that links the correct dialog box template to the dialog box class. Because you've built the ID into the class's constructor, you don't need to worry about it when you create the dialog box object. You need only supply the pointer to the parent window.
Inside the constructor, the program initializes the data members that represent the contents of the dialog box's controls. The values stored in these variables will automatically be copied to the dialog box's controls when the dialog box is displayed. When the user dismisses the dialog box, MFC copies the data from the controls into these variables, where they can be accessed by other parts of your program.
The only other function in the dialog box class is the overridden DoDataExchange() function, which is shown in Listing 7.9.
Listing 7.9 LST07_09.CPPOverriding the DoDataExchange() Function
void CDlg1::DoDataExchange(CDataExchange* pDX) { // Call the base class's version. CDialog::DoDataExchange(pDX); // Associate the data transfer variables with // the ID's of the controls. DDX_Text(pDX, IDC_NAME, m_name); DDV_MaxChars(pDX, m_name, 30); DDX_Text(pDX, IDC_AGE, m_age); DDV_MinMaxUInt(pDX, m_age, 16, 100); DDX_CBString(pDX, IDC_DEPARTMENT, m_department); DDX_Check(pDX, IDC_MONDAY, m_monday); DDX_Check(pDX, IDC_TUESDAY, m_tuesday); DDX_Check(pDX, IDC_WEDNESDAY, m_wednesday); DDX_Check(pDX, IDC_THURSDAY, m_thursday); DDX_Check(pDX, IDC_FRIDAY, m_friday); }
It is the DoDataExchange() function that sets up MFC's capability to transfer data between the data members of the dialog box class and the controls of the dialog box. The function receives a single parameter, which is a pointer to a CDataExchange object. This pointer is supplied by MFC when it calls your overridden version of the function. You don't have to do anything with this pointer except pass it on to the base class's DoDataExchange() function, as well as to various DDX and DDV functions you'll call to set up the dialog box's data transfer. The first line of your DoDataExchange() function, as shown in Listing 7.9, should call the base class's version, passing along the pointer to the CDataExchange object.
What are DDX and DDV functions? An excellent question! MFC's DDX functions set up the link between a data member and a control. For example, in Listing 7.9, the call to DDX_Text() links the edit control whose ID is IDC_NAME to the data member m_name. This tells MFC to copy the contents of m_name to the control when the dialog box is displayed and to copy the data in the control back to m_name when the dialog box is dismissed. Notice that a DDX function's first argument is the CDataExchange pointer passed into the DoDataExchange() function. There is a DDX function for each type of control you might want to include in a data transfer. These functions are listed in Table 7.1. To learn about each function's parameters, look them up in your Visual C++ online documentation.
Table 7.1 DDX Functions
Function | Description |
DDX_CBIndex() | Links a combo box's index to an integer variable |
DDX_CBString() | Links a combo box's string to a string variable |
DDX_CBStringExact() | Links a combo box's selected string to a string variable |
DDX_Check() | Links a check box with an integer variable |
DDX_LBIndex() | Links a list box's index with an integer variable |
DDX_LBString() | Links a list box's string to a string variable |
DDX_LBStringExact() | Links a list box's selected string to a string variable |
DDX_Radio() | Links a radio button with an integer variable |
DDX_Scroll() | Links a scroll bar to an integer variable |
DDX_Text() | Links a text box to a string variable |
MFC's DDV functions perform data validation for controls. For example, in Listing 7.9, the call to DDV_MaxChars() tells MFC that a valid entry in the edit control linked to m_name should be no longer than thirty characters. After the text box control reaches thirty characters, it will accept no more input from the user. On the other hand, the call to DDV_MinMaxInt() tells MFC that the m_age variable, which is associated with the edit control whose ID is IDC_AGE, should not accept values outside the range of 16 to 100. If the user enters an invalid number in the IDC_AGE edit box, MFC displays a message, warning the user of his mistake. The user cannot exit via the OK button of the dialog box until the value is corrected. Table 7.2 shows the DDV functions you can call. For more information on their parameters, consult your Visual C++ online documentation.
Table 7.2 DDV Functions
Function | Description |
DDV_MaxChars() | Limits the length of a string |
DDV_MinMaxByte() | Limits a byte value to a specific range |
DDV_MinMaxDouble() | Limits a double value to a specific range |
DDV_MinMaxDWord() | Limits a DWORD value to a specific range |
DDV_MinMaxFloat() | Limits a floating-point value to a specific range |
DDV_MinMaxInt() | Limits an integer value to a specific range |
DDV_MinMaxLong() | Limits a long integer value to a specific range |
DDV_MinMaxUInt() | Limits an unsigned integer value to a specific range |
It's important that you call DDV functions immediately after the DDX function that sets up the data exchange for a control. When you do this, MFC can set the input focus to the control that contains invalid datanot only showing the user exactly where the problem is, but also enabling him to enter a new value as conveniently as possible.
Now that you have your dialog box class written, you can create objects of that class within your program and display the associated dialog box element. The first step in using your new class is to include the header file of the dialog box class in any class that'll access the class. Failure to do this will cause the compiler to complain that it doesn't recognize the dialog box class. Also, when you develop the window class that'll display the dialog box, you'll usually want to create data members that mirror the data members of the dialog box class. This gives you a place to store information that you transfer from the dialog box. The CMainFrame class declares this set of member variables, as shown in Listing 7.10.
Listing 7.10 LST07_10.CPPDeclaring Storage for Dialog Box Data
protected: CString m_name; UINT m_age; CString m_department; BOOL m_monday; BOOL m_tuesday; BOOL m_wednesday; BOOL m_thursday; BOOL m_friday;
Notice that the data members declared in Listing 7.10 have the same names as the equivalent data members in the CDlg1 class. This convention makes it easier to keep track of what the variables do. Notice also that, unlike the equivalent variables in the CDlg1 class, the variables of the CMainFrame class are declared as protected, rather than as public. This is because no other class requires access to these variables in the same way that other classes require access to the variables of the dialog box class.
Just as the dialog box class initializes its data members in its constructor, so too does the CMainFrame class, as shown in Listing 7.11.
Listing 7.11 LST07_11.CPPInitializing the Frame Window's Data Members
m_name = ""; m_age = 16; m_department = "Sales"; m_monday = TRUE; m_tuesday = TRUE; m_wednesday = TRUE; m_thursday = TRUE; m_friday = TRUE;
The constructor initializes these data members to the same values as their counterparts in the CDlg1 class because, as you'll soon see, the contents of these variables will be copied into the dialog box variables before the dialog box is displayed. Why bother to initialize the dialog box class variables then? You do this to be sure that an object of the class is always created in a fully initialized state. After all, there's no guarantee that someone who uses the class will copy values into the dialog box class before displaying the dialog box.
In the CMainFrame class, the program displays the dialog box in the OnDialogTest() function, which MFC calls when the user chooses the Dialog, T
est command from the application's menu bar. The OnDialogTest() function first creates a dialog object of your new class, like this:
CDlg1 dlg(this);
With the dialog box object created, the program can now access the data members of the dialog box and initialize them to the current contents of the values stored in the CMainFrame class, as shown in Listing 7.12.
Listing 7.12 LST07_12.CPPTransferring Data to the Dialog Box Object
dlg.m_name = m_name; dlg.m_age = m_age; dlg.m_department = m_department; dlg.m_monday = m_monday; dlg.m_tuesday = m_tuesday; dlg.m_wednesday = m_wednesday; dlg.m_thursday = m_thursday; dlg.m_friday = m_friday;
You might wonder why the main frame window class bothers to initialize the dialog box's data members before displaying the dialog box. In this program, the dialog box always appears with the last entered data in the dialog box because the dialog box's data disappears along with the dialog box when the dialog box is deleted (either by the object going out of scope or by being explicitly deleted with the delete operator). The only place the current dialog box data is saved is in the CMainFrame class. If you wanted the dialog box to always appear with its original default values, you wouldn't bother to copy the stored values into the data members of the dialog box, but instead would stick with the values supplied by the dialog box's own constructor.
After the program initializes the dialog box's contents, the program calls the DoModal() function of the dialog box object to display the dialog box to the user, like this:
int result = dlg.DoModal();
At this point, the user has control until he dismisses the dialog box. If the user exits by pressing the OK button, DoModal() returns the value IDOK. (The Cancel button causes DoModal() to return IDCANCEL.) In the case of IDOK, the program must copy the new data from the dialog box to the CMainFrame class's variables, as shown in Listing 7.13.
Listing 7.13 LST07_13.CPPRetrieving Data from the Dialog Box
if (result == IDOK) { // Copy the dialog's data into this class's data members. m_name = dlg.m_name; m_age = dlg.m_age; m_department = dlg.m_department; m_monday = dlg.m_monday; m_tuesday = dlg.m_tuesday; m_wednesday = dlg.m_wednesday; m_thursday = dlg.m_thursday; m_friday = dlg.m_friday; // Force the window to show the new data. Invalidate(); }
The call to Invalidate() in Listing 7.13 forces the window's contents to be redrawn, this time using the new values retrieved from the dialog box. When the OnDialogTest() function ends, the dialog box object goes out of scope and disappears along with the data that the user entered into it. It's a good thing you saved that data in the CMainFrame class!
When a dialog box is displayed or closed, MFC's data-transfer mechanism handles moving data to and from the dialog box's controls. However, there might be times in your dialog box class when you want to force the data transfer to occur. You can do this by calling the UpdateData(BOOL bSaveAndValidate) function. This function's single argument is a Boolean value indicating whether the dialog box should have data transferred to (bSaveAndValidate = FALSE) or from (bSaveAndValidate = TRUE) its controls.
As you've learned, handling dialog boxes in an MFC program is much easier than handling them in a conventional Windows program because MFC provides a powerful class, CDialog, from which you can derive custom dialog box classes. These custom classes, not only perform automatic data exchange, but also validate the contents of a dialog box's controls before the user is allowed to dismiss the dialog box.