Enhance
Your Apps with Common Dialogs: Part I
This article may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. To maintain the flow of the article, we've left these URLs in the text, but disabled the links.
VBA
Hacker
Mastering
the Basics of the Windows Open and Save As Dialog Boxes
By Romke
Soldaat
In this
two-part series, we'll explore some of Microsoft Windows' great assets - its
common dialog boxes - and put the emphasis on how to use them in your Office
2000 applications. (Sorry, the VBA code used in these articles and their
associated source files contains many functions and features that aren't
available in previous Office versions.)
Common
dialog boxes were introduced in Windows 3.1 to help developers create
applications with a consistent user interface. The most "common" common dialog
box is the one that lets you open and save files.
For some
strange reason, one of the software companies that often refuses to use these
dialog boxes is Microsoft. Office 95, 97, and 2000 have consistently been
inconsistent with the Windows interface guidelines (see FIGURE 1). As a matter
of fact, it looks like it's the Office developers, and not the Windows team,
that sets the trend. The Windows 2000 common dialog box (see FIGURE 1, bottom)
has clearly "borrowed" the Places Bar, introduced with Office 2000, and the new
Windows 2000 system font has become Tahoma, pioneered by the makers of Office
95. (Wasn't it Bill Gates who once claimed that there was a brick wall between
the system and applications groups?)
FIGURE 1a: Evolution of the
common dialog box: Windows 3.1.
FIGURE 1b: Evolution of the
common dialog box: Windows 95.
FIGURE 1c: Evolution of the
common dialog box: Windows 2000.
What Are
Your Options?
If you
write VBA applications that require a user interface to open or save a file,
you have several options. The most obvious one is to use the Open or Save As
dialog box of the host application. In Word VBA, for example, you can access
the Dialogs collection and retrieve
a file name with the following code:
Sub GetFileFromWord()
With Dialogs(wdDialogFileOpen)
If .Display Then MsgBox .Name
End With
End Sub
It's a
simple and straightforward technique. The Display
method (unlike the Show method)
allows you to display the dialog box and obtain a file name without actually
opening the file. Unfortunately, it's unreliable and therefore unusable. Just
try and run the previous code and select more than one file in the dialog box.
What you get is error 5174 (see FIGURE 2). Clearly, Word's Open dialog box
can't cope with multiple file selections - and never could. This bug has been
around since Word 95 because nobody in Redmond ever bothered fixing it.
FIGURE 2: Don't try to select
more than one file in Word!
You're
much better off in Excel 2000. You can't use the Dialog object (which only has a Show method, so it doesn't let you display the dialog box without
actually opening a file), but you can use the GetOpenFilename or GetSaveFilename
method instead. GetOpenFilename
copes well with multiple file selections:
Sub GetFilesFromExcel()
Filename = Application.GetOpenFilename(FileFilter:= _
"All Files (*.*),*.*,Text Files (*.txt), *.txt", _
FilterIndex:=1, Title:="Open a File", _
MultiSelect:= True)
For i = LBound(Filename) To UBound(Filename)
MsgBox Filename(i)
Next
End Sub
Alas,
the creators of Word and PowerPoint seem to have decided that you don't want
this functionality, so you can't use the GetOpenFilename
method across Office applications. A missed opportunity!
An
alternative technique is to create a reference in your project to the Common Dialog ActiveX Control (COMDLG32.OCX).
Unfortunately, this file isn't automatically installed with all versions of
Windows or Office. You need to get Visual Basic or Visual FoxPro (or a
distribution license from Microsoft) if you want to use it. However, if you do
have the file, and it's properly registered, it's easy to use. The Visual Basic
documentation claims that you must add the CommonDialog control to a
form before you can set its properties, but that's not true. The code in FIGURE
3 doesn't need a form at all and works just fine. (As a matter of fact, this
code doesn't even require a project reference to COMDLG32.OCX because it
creates its own MSComDlg.CommonDialog object.)
Sub GetFileFromOCX()
Dim CDLG As Object
Set CDLG = CreateObject("MSComDlg.CommonDialog")
With CDLG
.DialogTitle = "Get me a File!"
.Filter = _
"Documents|*.doc|Templates|*.dot|Text Files|*.txt"
.ShowOpen
MsgBox .FileName
End With
Set CDLG = Nothing
End Sub
FIGURE 3:
Adding the CommonDialog control to a form before setting its properties
isn't necessary with this code.
Finally,
there's a fourth technique: using the Windows API to create your own common
dialog box objects. This isn't only a convenient way to avoid licensing
problems and have a solution that works for all Office applications, but it
also opens the door to some exciting customizations that aren't available with
any of the other options. We'll discover that in Part II; in this article,
we'll keep it simple.
What's
Involved
Manipulating
and displaying Windows common dialog boxes requires just two API functions: GetOpenFileName and GetSaveFileName, both of which are
exposed by the COMDLG32.DLL file, installed in everybody's Windows\System
folder. In a VBA module, you declare these functions as follows:
Private Declare Function GetOpenFileName _
Lib "COMDLG32.DLL" Alias "GetOpenFileNameA" _
(pOpenfilename As OPENFILENAME) As Long
Private Declare Function GetSaveFileName _
Lib "COMDLG32.DLL" Alias "GetSaveFileNameA" _
(pOpenfilename As OPENFILENAME) As Long
Both
functions require a single parameter pointing to an OPENFILENAME structure. In
VBA, you format Windows structures as a user-defined type (UDT). FIGURE 4 shows
how to create this data type in VBA.
Private Type OPENFILENAME
lStructSize As Long
hwndOwner As Long
hInstance As Long
lpstrFilter As String
lpstrCustomFilter As String
nMaxCustFilter As Long
nFilterIndex As Long
lpstrFile As String
nMaxFile As Long
lpstrFileTitle As String
nMaxFileTitle As Long
lpstrInitialDir As String
lpstrTitle As String
Flags As Long
nFileOffset As Integer
nFileExtension As Integer
lpstrDefExt As String
lCustData As Long
lpfnHook As Long
lpTemplateName As String
End Type
FIGURE 4:
The OPENFILENAME structure disguised as a user-defined type (UDT).
The
OPENFILENAME structure contains information that the operating system uses to
initialize the Open or Save As common dialog box. After the user closes the
dialog box, the system returns information about the selected file(s) in this
structure. FIGURE 5 gives a brief explanation of the values you can provide and
expect in return.
|
Member
|
Comment
|
|
lStructSize
|
Size
of the structure.
|
|
hwndOwner
|
Handle
to the window that owns the dialog box. Can be zero if the dialog box doesn't
have an owner.
|
|
hInstance
|
Handle
to a module that contains a dialog box template. Ignored in this project;
will be used in Part II.
|
|
lpstrFilter
|
Buffer
containing pairs of null-terminated filter strings.
|
|
lpstrCustomFilter
|
Ignored
in this project.
|
|
nMaxCustFilter
|
Ignored
in this project.
|
|
nFilterIndex
|
First
filter to show (1-based).
|
|
lpstrFile
|
File
name to initialize file name edit control; returns full path.
|
|
nMaxFile
|
Size
of the buffer pointed to by lpstrFile. Must be at least 256.
|
|
lpstrFileTitle
|
Receives
the file name and extension (without path information) of the selected file.
|
|
nMaxFileTitle
|
Size
of the buffer pointed to by lpstrFileTitle.
|
|
lpstrInitialDir
|
String
that can specify the initial Directory.
|
|
lpstrTitle
|
String
to be placed in the title bar of the dialog box.
|
|
Flags
|
Bit
flags that determine dialog box behavior (see FIGURE 7).
|
|
nFileOffset
|
Zero-based
offset from the beginning of the path to the file name in the string pointed
to by lpstrFile.
|
|
nFileExtension
|
Zero-based
offset from the beginning of the path to the file name extension in the
string pointed to by lpstrFile.
|
|
lpstrDefExt
|
Buffer
that contains the default extension.
|
|
lCustData
|
Ignored
in this project.
|
|
lpfnHook
|
Ignored
in this project; will be used in Part II.
|
|
lpTemplateName
|
Ignored
in this project; will be used in Part II.
|
FIGURE 5:
The 20 members of the OPENFILENAME structure.
Although
most OPENFILENAME members are optional, a few (lStructSize, nMaxFile,
and lpstrFile) are required. The minimal listing in FIGURE 6
demonstrates how they're used.
Sub GetFileFromAPI ()
Dim OFN As OPENFILENAME
With OFN
.lStructSize = Len(OFN) ' Size of structure.
.nMaxFile = 260 ' Size of buffer.
' Create buffer.
.lpstrFile = String(.nMaxFile - 1, 0)
Ret = GetOpenFileName(OFN) ' Call function.
If Ret <> 0 Then ' Non-zero is success.
' Find first null char.
n = InStr(.lpstrFile, VbNullChar)
' Return what's before it.
MsgBox Left(.lpstrFile, n - 1)
End If
End With
End Sub
FIGURE 6:
Code showing how OPENFILENAME structures are used.
This
simple macro displays the most basic common dialog box. Since no value is
specified for lpstrTitle, the dialog box defaults to the string "Open,"
and the lack of a value for lpstrFilter results in an empty "Files of
type" list.
There
are many ways to personalize common dialog boxes. One of the most powerful
customization features is the Flags member of the OPENFILENAME
structure. By specifying one or more of the flag values shown in FIGURE 7, you
can alter the way the dialog box behaves.
|
Constant
name
|
Hex
value
|
Comment
|
|
OFN_ALLOWMULTISELECT
|
&H200
|
Allows
multiple file selections; use only with Open. OFN_EXPLORER must also be set.
|
|
OFN_CREATEPROMPT
|
&H2000
|
Asks
for permission to create a file that doesn't exist. Also sets
OFN_PATHMUSTEXIST. Works only with Open.
|
|
OFN_ENABLEHOOK
|
&H20
|
Enables
the Hook function specified in the lpfnHook member. Will be
used in Part II.
|
|
OFN_ENABLETEMPLATE
|
&H40
|
Indicates
that the lpTemplateName member is a pointer to the name of a dialog
box template resource in the module identified by the hInstance
member. Will be used in Part II.
|
|
OFN_EXPLORER
|
&H80000
|
Uses
Explorer-style dialog boxes. Is automatically set, but must specifically be
set if OFN_ALLOWMULTISELECT is used.
|
|
OFN_EXTENSIONDIFFERENT
|
&H400
|
On
return, specifies that the user typed a file name extension that differs from
the extension specified by lpstrDefExt. Not used if lpstrDefExt
is unspecified.
|
|
OFN_FILEMUSTEXIST
|
&H1000
|
Accepts
only existing file. Works only with Open.
|
|
OFN_HIDEREADONLY
|
&H4
|
Hides
the read-only box. Should be set for Save As.
|
|
OFN_LONGNAMES
|
&H200000
|
Explorer-style
dialog boxes ignore this flag and always display long file names.
|
|
OFN_NOCHANGEDIR
|
&H8
|
Restores
the current directory to its original value if the user changed the directory
while searching for files.
|
|
OFN_NODEREFERENCELINKS
|
&H100000
|
Returns
the path and file name of the selected shortcut (.lnk) file. If not
specified, the dialog box returns the path and file name of the file
referenced by the shortcut.
|
|
OFN_OVERWRITEPROMPT
|
&H2
|
In the
Save As dialog box, display a message box if the selected file already
exists. The user must confirm whether to overwrite the file.
|
|
OFN_PATHMUSTEXIST
|
&H800
|
Accepts
only existing path.
|
|
OFN_READONLY
|
&H1
|
Selects
the read-only check box when the dialog box is created. Returns the state of
the read-only check box when the dialog box is closed. Only useful with Open.
|
|
OFN_SHOWHELP
|
&H10
|
Displays the Help button. Will be used Part II.
|
FIGURE 7:
OFN Flags. This list isn't comprehensive, but it contains all flags used in
this project.
When you
specify flags, you can simply add the constant names or use the Or
operand. For example, to display the Save As dialog box, you probably don't
want to show the read-only checkbox, but you do want to ensure the user
specifies a valid path, and you certainly want to display a warning if the user
selects the name of an existing file. The following instructions do just that:
OFN.Flags = OFN_HIDEREADONLY Or OFN_PATHMUSTEXIST Or _
OFN_OVERWRITEPROMPT
Introducing
the Common Dialog Box Class
Listing One contains a class module that encapsulates
all the nitty-gritty of common dialog boxes, and automates a lot of parameters.
Let's walk through the relevant elements of the class and see which properties
are used to make things happen. While we do that, you may also get an insight
into some exciting new features of VBA 6.0 that you may not yet have
discovered.
AllowMultiSelect
property. Boolean.
Default value is False. If set to True,
the common dialog box allows multiple file selections. Ignored if the Save As
dialog box is displayed.
CheckBoxSelected
property. Read/write
Boolean. Default value is False. If set to True, the common
dialog box displays and selects the read-only checkbox. On return, the True/False
value of this property indicates whether the user selected or unselected the
checkbox.
CheckBoxVisible
property. Boolean.
Default value is False. If set to True, the common dialog box
displays the read-only checkbox. If the CheckBoxSelected
property is set to True, CheckBoxVisible
is also automatically set to True.
DialogTitle
property. String.
Specifies the title bar text. Especially useful if you don't want to use the
dialog box to open or save a file, but need a file name (or a list of file
names) for another purpose. If omitted, Windows uses the default caption
("Open" or "Save As").
Directory
property. Read/write
String. Specifies the initial path in the dialog box. If omitted, the initial
path is determined by the operating system. (See also FileName
property.) On return, this property contains the path in which the user has
selected the file(s).
ExistFlags
property. Long.
Use any combination of the following enumeration variables: PathMustExist,
FileMustExist, and PromptToCreateFile. These values correspond to
the constants OFN_PATHMUSTEXIST, OFN_FILEMUSTEXIST, and OFN_CREATEPROMPT, shown
in FIGURE 7. PathMustExist is automatically set if the Save As dialog
box is displayed. (See also the "Enumerations" section.)
Extension
property. Read/write
String. Specifies the extension appended to the file name if the user fails to
type an extension. This string can be any length, but only the first three
characters are appended. If this property isn't specified and the user doesn't
type an extension, no extension is appended. On return, this property contains
the extension of the selected file. If multiple files are selected, the return
value is an empty string.
FileName
property. Read/write
String. Specifies the file name used to initialize the "File name" edit
control. If the specified file name contains a path name, and no value is
specified for the Directory
property, Windows 98 and earlier versions use the path component of FileName
to initialize the dialog box. Windows 2000 uses this path to initialize the
dialog box, even if a value is specified for the Directory property. On return, this property contains the name
(including the path) of the selected file. If multiple files are selected, this
property contains an empty string; use the FileNames
property instead.
FileNames
property. Read-only
Collection. On return, contains the names (including the path) of all selected
files. Use FileNames.Count to obtain the number of
selected files. See the first example for its implementation. If the Save As
dialog box is displayed, this collection contains a single file name.
FileTitle
property. Read-only
String. On return, this property contains the name (without the path) of the
selected file. If multiple files are selected, this property contains an empty
string; use the FileTitles property
instead.
FileTitles
property. Read-only
Collection. On return, contains the names (without the path) of all selected
files. Use FileTitles.Count to obtain the number of
selected files. If the Save As dialog box is displayed, this collection
contains a single file title. Returning file titles is especially useful if you
have a form with a listbox that you want to fill with file names.
Filter
property. Read/write
Variant. Specifies the filters in the "Files of type" list. Can be a single
string, or an array of strings. Filters are pairs of strings, separated by a
pipe symbol (|). The first string in each pair is a string that describes the
filter (e.g. "Documents"), and the second string specifies the filter pattern
(e.g. "*.doc"). To specify multiple filter patterns for a single display
string, use a semicolon to separate the patterns (e.g. "*.doc;*.dot;*.wiz"). A
pattern string can be a combination of valid file name characters and the
asterisk (*) or question mark wildcard character (e.g. "*.do?"). Don't include
spaces in the pattern string. See the examples for its implementation. On
return, this property contains the filter selected by the user.
FilterIndex
property. Read/write
Long. Specifies the initial index of the selected filter in the "Files of type"
list. The first filter has an index value of 1, the second filter 2, etc. On
return, this property contains the index of the filter selected by the user.
RestoreCurDir
property. Boolean.
Default value is False. If set to True, the common dialog box
restores the current directory to its original value if the user changed the
directory while searching for files.
ShowOpen
method. Displays
the Open dialog box. Returns True if
one or more files were selected and False
if the user clicked the Cancel button.
ShowSave
method. Displays
the Save As dialog box. Returns True
if a file was selected and False if
the user clicked the Cancel button.
Join,
Split, and Replace
The
class module uses a number of features that didn't exist before VBA 6.0. If you
haven't played with them yet, have a look at the Property procedures in
FIGURE 8.
Property Let Filter(vFilter)
If IsArray(vFilter) Then _
vFilter = Join(vFilter, vbNullChar)
OFN.lpstrFilter = Replace(vFilter, sPipe, vbNullChar)
End Property
Property Get Filter()
With OFN
If .nFilterIndex Then
Dim sTemp()
sTemp = Split(.lpstrFilter, vbNullChar)
Filter = sTemp(.nFilterIndex * 2 - 2) & _
sPipe & sTemp(.nFilterIndex * 2 - 1)
End If
End With
End Property
FIGURE 8:
Property procedures new to VBA 6.0.
Recall
that the Filter property of the
common dialog box class can be specified as a single string or an array. So,
for example, you can use a string assignment such as this:
OFN.Filter = _
"Documents|*.doc|Templates|*.dot|Text Files|*.txt"
or you
can assign an array:
Dim f(2) As String
f(0) = "Documents|*.doc"
f(1) = "Templates|*.dot"
f(2) = "Text Files|*.txt"
OFN.Filter = f
Windows
doesn't like either format, and wants its filter strings separated by null
characters. The Property Let Filter
procedure converts the value it receives (a Variant) into the required format.
First, the procedure checks to see if it received an array by using the IsArray function. If this function
returns True, the Join function is called to create a
single string from the individual array elements. In this case, the elements
are joined with null characters. This results in a single string:
"Documents|*.doc" & Chr(0) & "Templates|*.dot" & _
Chr(0) & "Text Files|*.txt"
The
second instruction in the procedure then uses the Replace function to convert all remaining pipe characters (|) into
null characters, and adds a null character at the end. This results in a string
that Windows can handle:
"Documents"& Chr(0) & "*.doc" & Chr(0) & _
"Templates" & Chr(0) & "*.dot" & Chr(0) & _
"Text Files" & Chr(0) & "*.txt" & Chr(0)
The Property
Get Filter procedure (which
returns the filter selected by the user) reverses the process. The Split function is used to break the
string into array elements (using the null character as the separator), which
creates an array such as this:
sTemp(0) = "Documents"
sTemp(1) = "*.doc"
sTemp(2) = "Templates"
sTemp(3) = "*.dot"
sTemp(4) = "Text Files"
sTemp(5) = "*.txt"
Depending
on the value of the filter index (which is 1-based), the two matching array
elements are then joined, delimited by the pipe symbol.
Enumerations
Enumerations
were also introduced with VBA 6.0. Enumeration variables are variables declared
with an Enum type. The elements of
the Enum type are initialized to
constant values within the Enum
statement. The assigned values can't be modified at run time, and can include
both positive and negative numbers. The class module contains the following
enumeration:
Public Enum XFlags
PathMustExist = OFN_PATHMUSTEXIST
FileMustExist = OFN_FILEMUSTEXIST
PromptToCreateFile = OFN_CREATEPROMPT
End Enum
The Property
Let ExistFlags procedure takes this enumeration as its parameter:
Property Let
ExistFlags(nFlags As XFlags)
OFN.Flags = OFN.Flags Or nFlags
End Property
The
great advantage of enumeration variables is that they facilitate the input of
values. FIGURE 9 demonstrates how Microsoft's IntelliSense technology helps you
provide the correct parameters.
FIGURE 9: IntelliSense in
action: Enumerations facilitate the selection of values.
Examples
If you
want to use common dialog boxes in your applications, you must add the
ComDlg.cls module to your project. The following examples demonstrate how you
can use the ComDlg objects in your
application.
Example 1
The code
in FIGURE 10 displays the Open dialog box with a user-defined caption. The
initial directory is Word's default document path. The filter is provided as an
array, and the last filter is displayed as the default. The read-only checkbox
is pre-selected, multiple file selection is enabled, and flags are set to make
sure that only existing file paths are returned. If the Open button in the dialog box is clicked, the files are extracted from
the FileNames collection, and opened
in Word. If the read-only checkbox was selected when the dialog box closed, the
files are opened as read-only.
Sub GetWordFiles()
Dim CDLG As ComDlg
Set CDLG = New ComDlg
With CDLG
.DialogTitle = "Open a Word file"
.Directory = Options.DefaultFilePath(wdDocumentsPath)
Dim f(3) As String
f(0) = "Documents (*.doc)|*.doc"
f(1) = "Templates (*.dot)|*.dot"
f(2) = "Wizards (*.wiz)|*.wiz"
f(3) = "All Files (*.*)|*.*"
.Filter = f
.FilterIndex = 4
.CheckBoxSelected = True
.AllowMultiSelect = True
.ExistFlags = FileMustExist + PathMustExist
If .ShowOpen Then
For i = 1 To .FileNames.Count
Documents.Open FileName:=.FileNames(i), _
ReadOnly:=.CheckBoxSelected
Next
End If
End With
Set CDLG = Nothing
End Sub
FIGURE 10:
The routine that displays the Open dialog box.
FIGURE
11 shows the dialog box created with the routine shown in FIGURE 10.
FIGURE 11: The Open dialog
box created with the code in FIGURE 10.
Example 2
The
macro in FIGURE 12 displays the Save As dialog box using a non-standard
caption. No value is specified for the Directory
property, so the dialog box defaults to the path of the active workbook. The
current name of the workbook is displayed in the File name
box. A default extension is specified, so the file will have the .xls
extension, even if the user doesn't type an extension. The Filter property is specified as a single string. No FilterIndex property is specified, so
the first filter becomes the default. If the Save
button is clicked, a copy of the workbook is saved to a file, without modifying
the open workbook in memory.
Sub SaveWorkbookCopy()
With New ComDlg
.DialogTitle = "Save Copy As"
.FileName = ActiveWorkbook.FullName
.Extension = "xls"
.Filter = "Workbook (*.xls)|*.xls " & _
"|Web Page (*.htm)|*.htm|Template (*.xlt)|*.xlt"
If .ShowSave Then _
ActiveWorkbook.SaveCopyAs .FileName
End With
End Sub
FIGURE 12:
The routine to display the Save As dialog box.
FIGURE
13 shows the dialog box created with the routine in FIGURE 12.
FIGURE 13: The Save As dialog
box created with the macro in FIGURE 12.
Conclusion
The
purpose of this series is to demonstrate that there's a lot more to VBA
programming than you may think. With a bit of hacking, you can greatly enhance
your Office applications.
In this
first installment of a two-part series, I showed you how you can implement
Windows common dialog boxes in your projects. In the next installment, we'll
discover some tricks that enhance the common dialog boxes beyond your wildest
dreams. You'll be able to rename or hide any existing dialog box control, add
your own functionality to the read-only checkbox, display a status bar and
instant file information, and even add your own buttons. Watch this space for
more VBA hacks!
Dutchman
Romke Soldaat was hired by Microsoft in 1988 to co-found the Microsoft
International Product Group in Dublin, Ireland. That same year he started
working with the prototypes of WinWord, writing his first macros long before
the rest of the world. In 1992, he left Microsoft and created a number of
successful add-ons for Office. Living in Italy, he divides his time between
writing articles for this magazine, enjoying the Mediterranean climate, and
steering his Landrover through the world's most deserted areas. Romke can be
contacted at mailto:romke@soldaat.com.
Begin Listing One - ComDlg.cls
Option Explicit
DefStr S
DefLng N
DefBool B
DefVar V
' OFN constants.
Const OFN_ALLOWMULTISELECT As Long = &H200
Const OFN_CREATEPROMPT As Long = &H2000
Const OFN_EXPLORER As Long = &H80000
Const OFN_EXTENSIONDIFFERENT As Long = &H400
Const OFN_FILEMUSTEXIST As Long = &H1000
Const OFN_HIDEREADONLY As Long = &H4
Const OFN_LONGNAMES As Long = &H200000
Const OFN_NOCHANGEDIR As Long = &H8
Const OFN_NODEREFERENCELINKS As Long = &H100000
Const OFN_OVERWRITEPROMPT As Long = &H2
Const OFN_PATHMUSTEXIST As Long = &H800
Const OFN_READONLY As Long = &H1
' The maximum length of a single file path.
Const MAX_PATH As Long = 260
' This MAX_BUFFER value allows you to select approx.
' 500 files with an average length of 25 characters.
' Change this value as needed.
Const MAX_BUFFER As Long = 50 * MAX_PATH
' String constants:
Const sBackSlash As String = "\"
Const sPipe As String = "|"
' API functions to use the Windows common dialog boxes.
Private Declare Function GetOpenFileName _
Lib "COMDLG32.DLL" Alias"GetOpenFileNameA" _
(pOpenfilename As OPENFILENAME) As Long
Private Declare Function GetSaveFileName _
Lib "COMDLG32.DLL" Alias"GetSaveFileNameA" _
(pOpenfilename As OPENFILENAME) As Long
Private Declare Function GetActiveWindow _
Lib "user32" ()As Long
' Type declaration, used by GetOpenFileName and
' GetSaveFileName.
Private Type OPENFILENAME
lStructSize As Long
hwndOwner As Long
hInstance As Long
lpstrFilter As String
lpstrCustomFilter As String
nMaxCustFilter As Long
nFilterIndex As Long
lpstrFile As String
nMaxFile As Long
lpstrFileTitle As String
nMaxFileTitle As Long
lpstrInitialDir As String
lpstrTitle As String
Flags As Long
nFileOffset As Integer
nFileExtension As Integer
lpstrDefExt As String
lCustData As Long
lpfnHook As Long
lpTemplateName As String ' Can also be a Long.
End Type
' Private variables.
Private OFN As OPENFILENAME
Private colFileTitles As New Collection
Private colFileNames As New Collection
Private sFullName
Private sFileTitle
Private sPath
Private sExtension
' Public enumeration variable.
Public Enum XFlags
PathMustExist = OFN_PATHMUSTEXIST
FileMustExist = OFN_FILEMUSTEXIST
PromptToCreateFile = OFN_CREATEPROMPT
End Enum
Property Let AllowMultiSelect(bFlag)
SetFlag OFN_ALLOWMULTISELECT, bFlag
End Property
Property Let DialogTitle(sCaption)
OFN.lpstrTitle = sCaption
End Property
Property Let Filter(vFilter)
If IsArray(vFilter) Then _
vFilter = Join(vFilter, vbNullChar)
OFN.lpstrFilter = _
Replace(vFilter, sPipe, vbNullChar) & vbNullChar
End Property
Property Get Filter()
With OFN
If .nFilterIndex Then
Dim sTemp()
sTemp = Split(.lpstrFilter, vbNullChar)
Filter = sTemp(.nFilterIndex * 2 - 2) & sPipe & _
sTemp(.nFilterIndex * 2 - 1)
End If
End With
End Property
Property Let FilterIndex(nIndex)
OFN.nFilterIndex = nIndex
End Property
Property Get FilterIndex()As Long
FilterIndex = OFN.nFilterIndex
End Property
Property Let RestoreCurDir(bFlag)
SetFlag OFN_NOCHANGEDIR, bFlag
End Property
Property Let ExistFlags(nFlags As XFlags)
OFN.Flags = OFN.Flags Or nFlags
End Property
Property Let CheckBoxVisible(bFlag)
SetFlag OFN_HIDEREADONLY, Not bFlag
End Property
Property Let CheckBoxSelected(bFlag)
SetFlag OFN_READONLY, bFlag
End Property
Property Get CheckBoxSelected()As Boolean
CheckBoxSelected = OFN.Flags And OFN_READONLY
End Property
Property Let FileName(sFileName)
If Len(sFileName) <= MAX_PATH Then _
OFN.lpstrFile = sFileName
End Property
Property Get FileName()As String
FileName = sFullName
End Property
Property Get FileNames()As Collection
Set FileNames = colFileNames
End Property
Property Get FileTitle()As String
FileTitle = sFileTitle
End Property
Property Get FileTitles()As Collection
Set FileTitles = colFileTitles
End Property
Property Let Directory(sInitDir)
OFN.lpstrInitialDir = sInitDir
End Property
Property Get Directory()As String
Directory = sPath
End Property
Property Let Extension(sDefExt)
OFN.lpstrDefExt = LCase$(Left$( _
Replace(sDefExt, ".", vbNullString), 3))
End Property
Property Get Extension()As String
Extension = sExtension
End Property
Function ShowOpen()As Boolean
ShowOpen = Show(True)
End Function
Function ShowSave()As Boolean
' Set or clear appropriate flags for Save As dialog.
SetFlag OFN_ALLOWMULTISELECT, False
SetFlag OFN_PATHMUSTEXIST, True
SetFlag OFN_OVERWRITEPROMPT, True
ShowSave = Show(False)
End Function
Private Function Show(bOpen)
With OFN
.lStructSize = Len(OFN)
' Could be zero if no owner is required.
.hwndOwner = GetActiveWindow
' If the RO checkbox must be checked, we should also
' display it.
If .Flags And OFN_READONLY Then _
SetFlag OFN_HIDEREADONLY, False
' Create large buffer if multiple file selection
' is allowed.
.nMaxFile = IIf(.Flags And OFN_ALLOWMULTISELECT, _
MAX_BUFFER + 1, MAX_PATH + 1)
.nMaxFileTitle = MAX_PATH + 1
' Initialize the buffers.
.lpstrFile = .lpstrFile & String$( _
.nMaxFile - 1 - Len(.lpstrFile), 0)
.lpstrFileTitle = String$(.nMaxFileTitle - 1, 0)
' Display the appropriate dialog.
If bOpen Then
Show = GetOpenFileName(OFN)
Else
Show = GetSaveFileName(OFN)
End If
If Show Then
' Remove trailing null characters.
Dim nDoubleNullPos
nDoubleNullPos = InStr(.lpstrFile & vbNullChar, _
String$(2, 0))
If nDoubleNullPos Then
' Get the file name including the path name.
sFullName = Left$(.lpstrFile, nDoubleNullPos - 1)
' Get the file name without the path name.
sFileTitle = Left$(.lpstrFileTitle, _
InStr(.lpstrFileTitle, vbNullChar) - 1)
' Get the path name.
sPath = Left$(sFullName, .nFileOffset - 1)
' Get the extension.
If .nFileExtension Then
sExtension = Mid$(sFullName, .nFileExtension + 1)
End If
' If sFileTitle is a string,
' we have a single selection.
If Len(sFileTitle) Then
' Add to the collections.
colFileTitles.Add _
Mid$(sFullName, .nFileOffset + 1)
colFileNames.Add sFullName
Else ' Tear multiple selection apart.
Dim sTemp(), nCount
sTemp = Split(sFullName, vbNullChar)
' If array contains no elements,
' UBound returns -1.
If UBound(sTemp) > LBound(sTemp) Then
' We have more than one array element!
' Remove backslash if sPath is the root folder.
If Len(sPath) = 3 Then _
sPath = Left$(sPath, 2)
' Loop through the array, and create the
' collections; skip the first element
' (containing the path name), so start the
' counter at 1, not at 0.
For nCount = 1 To UBound(sTemp)
colFileTitles.Add sTemp(nCount)
' If the string already contains a backslash,
' the user must have selected a shortcut
' file, so we don't add the path.
colFileNames.Add If(InStr(sTemp(nCount), _
sBackSlash), sTemp(nCount),
sPath & sBackSlash & sTemp(nCount))
Next
' Clear this variable.
sFullName = vbNullString
End If
End If
' Add backslash if sPath is the root folder.
If Len(sPath) = 2 Then _
sPath = sPath & sBackSlash
End If
End If
End With
End Function
Private Sub SetFlag(nValue, bTrue)
' Wrapper routine to set or clear bit flags.
With OFN
If bTrue Then
.Flags = .Flags Or nValue
Else
.Flags = .Flags And Not nValue
End If
End With
End Sub
Private Sub Class_Initialize()
' This routine runs when the object is created.
OFN.Flags = OFN.Flags Or OFN_EXPLORER Or _
OFN_LONGNAMES Or OFN_HIDEREADONL
End Sub
End Listing One