Using the Common Dialog Open/Save Boxes

by John Percival

Using the Common Dialog Open/Save Boxes

In almost every application you write you are going to need to allow the user to select a file name. The standard Windows Open/Save dialog box is found in the comdlg32.dll library and can be accessed by two methods. The first involves using an ActiveX control, MS Common Dialog Control, which in turn accesses the DLL, and the second accesses the DLL directly via the API. Both have their advantages and disadvantages. The OCX method requires you to have a form on which to place the control. It also incurs a performance penalty, as the OCX libraries must be loaded on top of the DLL. It does not allow for as much flexibility as the API, but is much easier to use. Almost all of the properties can be set at design-time, meaning that you must only invoke one method to display the dialog. The API requires you to set up a UDT, including string buffers, before you can show the dialog. The advantages of the API easily outweigh this though. There is no need to deploy a hefty OCX (140kb) with your application, as the comdlg32.dll is distributed with Windows. You also have several more options and increased flexibility with the API method.

I will show you how to use each method, and you can decide how easy each method is, and whether the extra flexibility of the DLL is worth the extra code.

This control contains the interface for the open/save dialogs, as well as several others, namely colour and font pickers, printer setup and an interface for Winhelp. Using the control is as simple as placing the control on a form and calling a method. Let's do that now:

  1. Place a Common Dialog control, CommonDialog1, on the form.
  2. Place a button, Command1, on the form. In the click event add the following code:
Private Sub Command1_Click()
MsgBox "You chose: " & CommonDialog1.filename
End Sub

When you run this, you will see that the CommonDialog control is not visible. However, it is sitting in the background, and when you click the button, the open dialog is shown. If you had wanted to show the save dialog, you would have called CommonDialog1.ShowSave, rather than .ShowOpen. When the dialog is closed, the filename that you chose is placed in the filename property of the control. You can also retrieve the filename and path separately very easily. The filename on its own is stored in the filetitle property. With a little juggling of strings, you can find out the path. Add the following code to Command1_Click:

MsgBox "Filename: " & _
MsgBox "Path: " & Left$(CommonDialog1.filename, _
  Len(CommonDialog1.filename) - Len(CommonDialog1.FileTitle))

But what if the user does not select a file and clicks on cancel? Here we can use a property called 'CancelError' to tell the control to raise a trappable error when the user selects cancel. You can then trap this error, and take due action. Modify the code in Command1_Click to read:

On Error Resume Next ' Set up error handler
CommonDialog1.CancelError = True
If Err.Number = cdlCancel Then
MsgBox "You clicked cancel!"
MsgBox "You chose: " & CommonDialog1.filename
MsgBox "Filename: " & CommonDialog1.FileTitle
MsgBox "Path: " & Left$(CommonDialog1.filename, _
Len(CommonDialog1.filename) - Len(CommonDialog1.FileTitle))
End If
On Error GoTo 0 ' Turn off error handler

Now when you click cancel, error 32755(cdlCancel) is raised. The error handler catches it and resumes the next statement. The next statement checks the error object to see if the error has occurred; if it has, it lets you know, otherwise, it operates as normal. In your applications, you might consider asking the user to confirm that they wanted to click cancel and to give them the option to try again.

You can set which directory the dialog starts in, using the InitDir property. To demonstrate this, draw a DirListBox on the form, called Dir1. Add the following line before the call to CommonDialog1.ShowOpen:

CommonDialog1.InitDir = Dir1.Path

In your applications, you could allow the user to specify a default location to open in. You may also only want the user to be able to specify certain types of files. You can do this by using a filter. A filter consists of a list of description and filenames to use as filters. It is in the form:


You can have as many filters as you like, and if you want to specify more than one filename, separate it with a semicolon. Each description and filter is separated by a pipe (ASCII 124). For example, to allow the user to select certain programs, you might use the following filter:

"Batch Files (*.bat)|*.bat|Files beginning with a (a*.*)|a*.*|Batch files beginning with a (a*.bat)|a*.bat|Executables (*.exe;*.com)|*.exe;*.com|All files (*.*)|*.*"

By setting FilterIndex, you can choose which filter is selected as default. The first filter is 1, the second is 2, etc. When the dialog box is closed, filterindex contains the filter that the user had chosen.

The Flags property can be set, from a selection of the constants below, combined with the Or operator, to allow control over the details of the box. In the demo, there are more. Most of these are supported only by the API calls, so I will leave the description of the important API-only ones until later. Here are the values and a description of their effects, from the VB help file:

cdlOFNAllowMultiselect(&H200)    Specifies that the File Name list box allows multiple selections. The user can select more than one file at run time by pressing the SHIFT key and using the UP ARROW and DOWN ARROW keys to select the desired files. When this is done, the FileName property returns a string containing the names of all selected files. The names in the string are delimited by spaces, because of this, no long filenames can be returned.

cdlOFNCreatePrompt(&H2000)    Specifies that the dialog box prompts the user to create a file that doesn't currently exist. This flag automatically sets the cdlOFNPathMustExist and cdlOFNFileMustExist flags.

cdlOFNExplorer(&H80000)    Use the Explorer-like Open A File dialog box template. Works with Windows 95 and Windows NT 4.0. Do not use with cdlOFNAllowMultiselect.

cdlOFNExtensionDifferent(&H400)    Indicates that the extension of the returned filename is different from the extension specified by the DefaultExt property. This flag isn't set if the DefaultExt property is Null, if the extensions match, or if the file has no extension. This flag value can be checked upon closing the dialog box.

cdlOFNFileMustExist(&H1000)    Specifies that the user can enter only names of existing files in the File Name text box. If this flag is set and the user enters an invalid filename, a warning is displayed. This flag automatically sets the cdlOFNPathMustExist flag.

cdlOFNHelpButton(&H10)    Causes the dialog box to display the Help button. To use this button, set the HelpFile and HelpContext properties of the CommonDialog control prior to showing the dialog.

cdlOFNHideReadOnly(&H4)    Hides the Read Only check box.

cdlOFNLongNames(&H200000)    Use long filenames.

cdlOFNNoChangeDir(&H8)    Forces the dialog box to set the current directory to what it was when the dialog box was opened.

cdlOFNNoDereferenceLinks(&H100000)    Do not dereference shell links (also known as shortcuts). By default, choosing a shell link causes it to be dereferenced by the shell.

cdlOFNNoLongNames(&H40000)    No long file names.

cdlOFNNoReadOnlyReturn(&H8000)    Specifies that the returned file won't have the Read Only attribute set and won't be in a write-protected directory.

cdlOFNNoValidate(&H100)    Specifies that the common dialog box allows invalid characters in the returned filename.

cdlOFNOverwritePrompt(&H2)    Causes the Save As dialog box to generate a message box if the selected file already exists. The user must confirm whether to overwrite the file.

cdlOFNPathMustExist(&H800)    Specifies that the user can enter only valid paths. If this flag is set and the user enters an invalid path, a warning message is displayed.

cdlOFNReadOnly(&H1)    Causes the Read Only check box to be initially checked when the dialog box is created. This flag also indicates the state of the Read Only check box when the dialog box is closed.

cdlOFNShareAware(&H4000)    Specifies that sharing violation errors will be ignored.

Lastly, if you are not happy with the standard "Open" title, you can set a new one with the DialogTitle property.

That is it for the OCX method of displaying the dialogs. Remember that almost all of the properties can be set at design-time, so displaying the dialog is as easy as calling one method.

The API method accesses the comdlg32.dll library directly, rather than via an ActiveX control. Normally it requires quite complicated code, but I have written a class which encompasses all of the features. The properties and methods are identical to the OCX, except that you don't need a form loaded to view the dialogs.

Using this class is as simple as creating the object in code, then replacing all the references to the Common Dialog Control with this Common Dialog Class:

Dim CmDlg As New clsOpenSave

On Error Resume Next ' Set up error handler
CmDlg.CancelError = True
If Err.Number = cdlAPICancel Then
MsgBox "You clicked cancel!"
MsgBox "You chose: " & CmDlg.filename
MsgBox "Filename: " & CmDlg.FileTitle
MsgBox "Path: " & Left$(CmDlg.filename, _
Len(CmDlg.filename) - Len(CmDlg.FileTitle))
End If
On Error GoTo 0 ' Turn off error handler

The API method using this class is the same as the control. However, you cannot use the OFN_SHOWHELP flag. To use this requires subclassing of the dialog, which can be achieved using the lpfnHook member of the OEPNFILENAME structure. If you pass the address of one of your procedures in a standard module, you can receive the windows messages. The parameters for the window function are the usual ones:

Public Function customwndproc(ByVal hwnd As Long, _
ByVal msg As Long, ByVal wparam As Long, ByVal _
lparam As Long) As Long

End Function

If you decide to use a custom message handler, remember to save before testing, because if the are any unhandled error, VB is sure to crash. Using a message handler, you can hide the default controls on the dialog and create your own new ones. However, this requires very complicated methods, which are too much detail for this article.

Another member of the OPENFILENAME structure not covered in the class is the nFileOffset member. When the dialog is closed, this returns the length of the path in the filename string. Therefore to retrieve just the path, you would use:

strPath = Left$(Trim$(OFN.lpstrfile), OFN.nFileOffset)

Lastly, when setting the filter for the API method, you should separate each part with ASCII character 0 (vbNullchar) and terminate it with two nulls. This is done automatically by the class, so you can just pass the filter using the same way as the OCX, separating each item with a pipe ("|").

In this article, we have seen that the two methods for using the Common Dialogs are not dissimilar. Using the class the only change that you will have to make is changing the object of the properties and ShowOpen method. You can use this class in any project, though it will be particularly useful in a project without any forms. You will also have no need to distribute the OCX with your applications. You now have no need to ever use the Microsoft Common Dialog control for open and save dialogs again!

This article was originally published on Wednesday Nov 20th 2002
Mobile Site | Full Site