Windows Common Dialog Boxes
Windows provides a group of standard user interface dialog boxes (in COMDLG32.DLL), and these are supported by the MFC library classes. You're probably familiar with all or most of these dialog boxes because so many Windows-based applications, including Visual C++ applications, already use them. All the common dialog classes are derived from a common base class, CCommonDialog. Table 7-1 shows lists the CCommonDialog classes.
Class | Purpose |
---|---|
CColorDialog | Allows the user to select or create a color |
CFileDialog | Allows the user to open or save a file |
CFindReplaceDialog | Allows the user to substitute one string for another |
CFontDialog | Allows the user to select a font from a list of available fonts |
COleDialog | Useful for inserting OLE objects |
CPageSetupDialog | Allows the user to input page measurement parameters |
CPrintDialog | Allows the user to set up the printer and print a document |
CPrintDialogEx | Printing and Print Preview for Windows 2000 |
One characteristic that all common dialog boxes share is that they gather information from the user but don't do anything with it. The file dialog box 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 box 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 using the dialog box:
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 box instead of a File Save dialog box. The default file extension is BMP, and *.bmp appears first in the filename edit box. The CFileDialog::GetPath-Name function returns a CString object that contains the full pathname of the selected file.
Deriving from the Common Dialog Classes
You can usually 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 box, but it should give you some ideas for customizing the other common dialog boxes.
Nested Dialog Boxes
Win32 provides a way to "nest" one dialog box inside another so that multiple dialog boxes 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've created a dialog box that is a child of the COMDLG32 dialog box, 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 with a "hole" in it 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 the Properties window for a class to map the messages that originate from your template's new controls.
The Ex07b Example: CFileDialog
In this example, we'll derive a class CEx07bDialog that adds a working Delete All Matching Files button to the standard file dialog box. It will also change the dialog box's title and change the Open button's caption to Delete (to delete a single file). The example illustrates how you can use nested dialog boxes to add new controls to standard common dialog boxes. The new file dialog box 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-2 shows what the dialog box looks like.
Figure 7-2: The Delete File dialog box in action.
Follow these steps to build the Ex07b application:
Create a new MFC Application project named Ex07b. In the MFC Application Wizard, accept all the defaults but two: On the Application Type page, select Single Document, and on the Advanced Features page, deselect Printing And Print Preview.
Create a new dialog resource and set its properties. On the Project menu, choose Add Resource and add a new dialog box. Make the dialog box about 3 by 5 inches. Using the Properties window for the dialog box, change the ID property to IDD_filesPECIAL, set the Style property to Child, set the Border property to None, and set the Clip Siblings and Visible properties to True.
Specify controls for the dialog box. Delete the existing OK and Cancel buttons on the dialog box. Add a button at the bottom of the dialog box and set the ID to IDC_DELETE and set the Caption to Delete All Matching Files. Add a group box, set the ID to stc32=0x045f, and set the Visible property to False, as shown here.
Check your work by right-clicking on the IDD_filesPECIAL dialog resource in Resource View and choosing Resource Symbols. You should see a symbol list like the one shown here.
Use the MFC Class Wizard to create the CSpecialFileDialog class.In Class View, right-click on the Ex07b project, choose Add, Add Class, select the MFC Class template, and click Open to start the MFC Class Wizard. Fill in the wizard, as shown below. Be sure to change the filenames to SpecFileDlg.h and SpecFileDlg.cpp . Unfortunately, we cannot use the Base Class drop-down list to change the base class to CFileDialog—that would decouple our class from the IDD_filesPECIAL template. We have to change the base class by hand. When you're finished, click the Finish button.
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);
Replace CDialog with CFileDialog in SpecFileDlg.cpp . Choose Find And Replace, Replace from the Edit menu, and replace this name globally.
Edit the CSpecialFileDialog constructor in SpecFileDlg.cpp . The derived class constructor 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 dialog 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)
{
m_ofn.Flags |= OFN_ENABLETEMPLATE;
m_ofn.lpTemplateName = MAKEINTRESOURCE(IDD_filesPECIAL);
m_ofn.lpstrTitle = "Delete File";
m_bDeleteAll = FALSE;
}
Override the OnInitDialog function in the CSpecialDialog class. Select the CSpecialFileDialog class in Class View, click the Overrides button in the Properties window, and add the OnInitDialog function. The OnInitDialog member function needs to change the common dialog box's Open button caption to Delete. The child window ID is IDOK. Edit the code as follows.
BOOL CSpecialFileDialog::OnInitDialog()
BOOL bRet = CFileDialog::OnInitDialog();
if (bRet == TRUE) {
GetParent()->GetDlgItem(IDOK)->SetWindowText("Delete");
}
return bRet;
}
Add a BN_CLICKED message handler for the new IDC_DELETE button (Delete All Matching Files) in the CSpecialDialog class. Select the CSpecialFileDialog class in Class View, click the Events button in the Properties window, and add the OnBnClickedDelete message handler. The OnBnClickedDelete member function sets the m_bDeleteAll flag and then forces the main dialog box 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. Edit the code as follows:
void CSpecialFileDialog::OnBnClickedDelete()
{
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);
}
Add code to the virtual OnDraw function in file Ex07bView.cpp . The CEx07bView OnDraw function (whose skeleton was generated by MFC Application Wizard) should be coded as follows to prompt the user to press the mouse button:
void CEx07bView::OnDraw(CDC* pDC)
{
CEx07bDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
pDC->TextOut(0, 0, "Press the left mouse button here.");
}
Add the WM_LBUTTONDOWN message handler to the CEx07bView class. Select the CEx07bView class in Class View, click the Messages button in the Properties window, and add the OnLButtonDown message handler. 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 dialog boxes only gather data. Because the view is the client of the dialog box, 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 Visual Studio documentation describes all of the message box variations and options.
Include SpecFileDlg.h in Ex07bView.cpp . Of course, you'll need to include the statement
#include "SpecFileDlg.h"
after the line
#include "Ex07bView.h"
Build and test the application. Build and run Ex07b. Pressing the left mouse button should bring up the Delete File dialog box, and you should be able to use it to navigate through the disk directory and delete files. Be careful not to delete your important source files!
Other Customizations for CFileDialog
In the Ex07b example, you added a button to the dialog box. It's easy to add other controls, too. Just put them in the dialog resource template, and if they're standard Windows controls such as edit controls or list boxes, you can use the Add Member Variable Wizard 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.
Note | Even if you don't use nested dialog boxes, 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 box. 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(IDI_MYICON); |