The Windows Common Dialogs

Windows provides a group of standard user interface dialogs, and these are supported by the MFC library classes. You are probably familiar with all or most of these dialogs because so many Windows-based applications, including Visual C++, already use them. All the common dialog classes are derived from a common base class, CCommonDialog. A list of the COMDLG32 classes is shown in the following table.

ClassPurpose
CColorDialogAllows the user to select or create a color
CFileDialogAllows the user to open or save a file
CFindReplaceDialogAllows the user to substitute one string for another
CPageSetupDialogAllows the user to input page measurement parameters
CFontDialogAllows the user to select a font from a list of available fonts
CPrintDialogAllows the user to set up the printer and print a document

Here's one characteristic that all common dialogs share: they gather information from the user, but they don't do anything with it. The file dialog can help the user select a file to open, but it really just provides your program with the pathname—your program must make the call that opens the file. Similarly, a font dialog fills in a structure that describes a font, but it doesn't create the font.

Using the CFileDialog Class Directly

Using the CFileDialog class to open a file is easy. The following code opens a file that the user has selected through the dialog:

CFileDialog dlg(TRUE, "bmp", "*.bmp");
if (dlg.DoModal() == IDOK) {
    CFile file;
    VERIFY(file.Open(dlg.GetPathName(), CFile::modeRead));
}

The first constructor parameter (TRUE) specifies that this object is a "File Open" dialog instead of a "File Save" dialog. The default file extension is bmp, and *.bmp appears first in the filename edit box. The CFileDialog::GetPathName function returns a CString object that contains the full pathname of the selected file.

Deriving from the Common Dialog Classes

Most of the time, you can use the common dialog classes directly. If you derive your own classes, you can add functionality without duplicating code. Each COMDLG32 dialog works a little differently, however. The next example is specific to the file dialog, but it should give you some ideas for customizing the other common dialogs.

In the early editions of this book, the EX07B example dynamically created controls inside the standard file dialog. That technique doesn't work in Win32, but the nested dialog method described here has the same effect.

Nested Dialogs

Win32 provides a way to "nest" one dialog inside another so that multiple dialogs appear as one seamless whole. You must first create a dialog resource template with a "hole" in it—typically a group box control—with the specific child window ID stc32 (=0x045f). Your program sets some parameters that tell COMDLG32 to use your template. In addition, your program must hook into the COMDLG32 message loop so that it gets first crack at selected notifications. When you're done with all of this, you'll notice that you have created a dialog window that is a child of the COMDLG32 dialog window, even though your template wraps COMDLG32's template.

This sounds difficult, and it is unless you use MFC. With MFC, you build the dialog resource template as described above, derive a class from one of the common dialog base classes, add the class-specific connection code in OnInitDialog, and then happily use ClassWizard to map the messages that originate from your template's new controls.

Windows NT 3.51 uses an earlier version of the common dialogs DLL that does not support the new Windows namespace feature. The nested dialog technique illustrated in the EX07B example won't work with the Windows NT 3.51 version of the file dialog.

A CFileDialog Example—EX07B

In this example, you will derive a class CEx07bDialog that adds a working Delete All Matching Files button to the standard file dialog. It also changes the dialog's title and changes the Open button's caption to Delete (to delete a single file). The example illustrates how you can use nested dialogs to add new controls to standard common dialogs. The new file dialog is activated as in the previous examples—by pressing the left mouse button when the mouse cursor is in the view window. Because you should be gaining skill with Visual C++, the following steps won't be as detailed as those for the earlier examples. Figure 7-1 shows what the dialog looks like.

Click to view at full size.

Figure 7-1. The Delete File dialog in action.

Follow these steps to build the EX07B application:

  1. Run AppWizard to produce \vcpp32\ex07b\ex07b. Accept all the defaults but two: select Single Document and deselect Printing And Print Preview. The options and the default class names are shown in the next graphic.

  1. Use the dialog editor to create a dialog resource. Make the dialog box about 3-by-5 inches, and use the ID IDD_FILESPECIAL. Set the dialog's Style property to Child, its Border property to None, and select its Clip Siblings and Visible properties. Create the template with a button with ID IDC_DELETE and a group box with ID stc32=0x045f, as shown here.

Click to view at full size.

    Check your work by choosing Resource Symbols from the Visual C++ View menu. You should see a symbol list like the one shown in the graphic below.

  1. Use ClassWizard to create the CSpecialFileDialog class. Fill in the New Class dialog, as shown here, and then click the Change button.

    Change the names to SpecFileDlg.h and SpecFileDlg.cpp. Unfortunately, we cannot use the Base Class drop-down list to change the base class to CFileDialog, as that would decouple our class from the IDD_FILESPECIAL template. We have to change the base class by hand.

  1. Edit the file SpecFileDlg.h. Change the line

    class CSpecialFileDialog : public CDialog

    to

    class CSpecialFileDialog : public CFileDialog

    Add the following two public data members:

    CString m_strFilename;
    BOOL m_bDeleteAll;

    Finally, edit the constructor declaration:

    CSpecialFileDialog(BOOL bOpenFileDialog, 
        LPCTSTR lpszDefExt = NULL,
        LPCTSTR lpszFileName = NULL,
        DWORD dwFlags = OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
        LPCTSTR lpszFilter = NULL,
        CWnd* pParentWnd = NULL
    );

  2. Replace CDialog with CFileDialog in SpecFileDlg.h. Choose Replace from Visual C++'s Edit menu, and replace this name globally.

  3. Edit the CSpecialFileDialog constructor in SpecFileDlg.cpp. The derived class destructor must invoke the base class constructor and initialize the m_bDeleteAll data member. In addition, it must set some members of the CFileDialog base class data member m_ofn, which is an instance of the Win32 OPENFILENAME structure. The Flags and lpTemplateName members control the coupling to your IDD_FILESPECIAL template, and the lpstrTitle member changes the main dialog box title. Edit the constructor as follows:

    CSpecialFileDialog::CSpecialFileDialog(BOOL bOpenFileDialog,
            LPCTSTR lpszDefExt, LPCTSTR lpszFileName, DWORD dwFlags,
            LPCTSTR lpszFilter, CWnd* pParentWnd)
        : CFileDialog(bOpenFileDialog, lpszDefExt, lpszFileName,
            dwFlags, lpszFilter, pParentWnd)
    {
        //{{AFX_DATA_INIT(CSpecialFileDialog)
            // NOTE: the ClassWizard will add member initialization here
        //}}AFX_DATA_INIT
        m_ofn.Flags |= OFN_ENABLETEMPLATE;
        m_ofn.lpTemplateName = MAKEINTRESOURCE(IDD_FILESPECIAL);
        m_ofn.lpstrTitle = "Delete File";
        m_bDeleteAll = FALSE;
    }
    

  4. Map the WM_INITDIALOG message in the CSpecialDialog class. The OnInitDialog member function needs to change the common dialog's Open button caption to Delete. The child window ID is IDOK.

    BOOL bRet = CFileDialog::OnInitDialog();
    if (bRet == TRUE) {
        GetParent()->GetDlgItem(IDOK)->SetWindowText("Delete");
    }
    return bRet;
    

  5. Map the new IDC_DELETE button (Delete All Matching Files) in the CSpecialDialog class. The OnDelete member function sets the m_bDeleteAll flag and then forces the main dialog to exit as if the Cancel button had been clicked. The client program (in this case, the view) gets the IDCANCEL return from DoModal and reads the flag to see whether it should delete all files. Here is the function:

     void CSpecialFileDialog::OnDelete() 
    {
        m_bDeleteAll = TRUE;
        // 0x480 is the child window ID of the File Name edit control
        //  (as determined by SPYXX)
        GetParent()->GetDlgItem(0x480)->GetWindowText(m_strFilename);
        GetParent()->SendMessage(WM_COMMAND, IDCANCEL);
    }
    

  6. Add code to the virtual OnDraw function in file ex07bView.cpp. The CEx07bView OnDraw function (whose skeleton was generated by AppWizard) should be coded as follows to prompt the user to press the mouse button:

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

  7. Add the OnLButtonDown message handler to the CEx07bView class. Use ClassWizard to create the message handler for WM_LBUTTON-DOWN, and then edit the code as follows:

    void CEx07bView::OnLButtonDown(UINT nFlags, CPoint point) 
    {
        CSpecialFileDialog dlgFile(TRUE, NULL, "*.obj");
        CString strMessage;
        int nModal = dlgFile.DoModal();
        if ((nModal == IDCANCEL) && (dlgFile.m_bDeleteAll)) {
            strMessage.Format(
                "Are you sure you want to delete all %s files?",
                dlgFile.m_strFilename);
            if (AfxMessageBox(strMessage, MB_YESNO) == IDYES) {
                HANDLE h;
                WIN32_FIND_DATA fData;
                while((h = ::FindFirstFile(dlgFile.m_strFilename, &fData))
                        != (HANDLE) 0xFFFFFFFF) { // no MFC equivalent
                    if (::DeleteFile(fData.cFileName) == FALSE) {
                        strMessage.Format("Unable to delete file %s\n",
                            fData.cFileName);
                        AfxMessageBox(strMessage);
                        break;
                    }
                }
            }
        }
        else if (nModal == IDOK) {
            CString strSingleFilename = dlgFile.GetPathName();
            strMessage.Format(
                "Are you sure you want to delete %s?", strSingleFilename);
            if (AfxMessageBox(strMessage, MB_YESNO) == IDYES) {
                CFile::Remove(strSingleFilename);
            }
        }
    }

    Remember that common dialogs just gather data. Since the view is the client of the dialog, the view must call DoModal for the file dialog object and then figure out what to do with the information returned. In this case, the view has the return value from DoModal (either IDOK or IDCANCEL) and the value of the public m_bDeleteAll data member, and it can call various CFileDialog member functions such as GetPathName. If DoModal returns IDCANCEL and the flag is TRUE, the function makes the Win32 file system calls necessary to delete all the matching files. If DoModal returns IDOK, the function can use the MFC CFile functions to delete an individual file.

    Using the global AfxMessageBox function is a convenient way to pop up a simple dialog that displays some text and then queries the user for a Yes/No answer. The Microsoft Foundation Classes And Templates section in the online help describes all of the message box variations and options.

    Of course, you'll need to include the statement

    #include "SpecFileDlg.h"

    after the line

    #include "ex07bView.h"

  8. Build and test the application.Build and run EX07B. Pressing the left mouse button should bring up the Delete File dialog, and you should be able to use it to navigate through the disk directory and to delete files. Be careful not to delete your important source files!

Other Customization for CFileDialog

In the EX07B example, you added a pushbutton to the dialog. It's easy to add other controls too. Just put them in the resource template, and if they are standard Windows controls such as edit controls or list boxes, you can use ClassWizard to add data members and DDX/DDV code to your derived class. The client program can set the data members before calling DoModal, and it can retrieve the updated values after DoModal returns.

Even if you don't use nested dialogs, two windows are still associated with a CFileDialog object. Suppose you have overridden OnInitDialog in a derived class and you want to assign an icon to the file dialog. You must call CWnd::GetParent to get the top-level window, just as you did in the EX07B example. Here's the code:

HICON hIcon = AfxGetApp()->LoadIcon(ID_MYICON);
GetParent()->SetIcon(hIcon, TRUE);        // Set big icon
GetParent()->SetIcon(hIcon, FALSE);       // Set small icon