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:
- Place a Common Dialog control, CommonDialog1, on the form.
- Place a button, Command1, on the form. In the click event add the following code:
Private Sub Command1_Click() CommonDialog1.ShowOpen 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: " & _ CommonDialog1.FileTitle 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 CommonDialog1.ShowOpen If Err.Number = cdlCancel Then MsgBox "You clicked cancel!" Err.Clear Else 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:
"description1|filter1|description2|filter2..."
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 CmDlg.ShowOpen If Err.Number = cdlAPICancel Then MsgBox "You clicked cancel!" Err.Clear Else 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!