Windows' Browse Folders Dialog provides
the means to retrieve from a user their selection of the Shell's file system and special folders. The following and related code
page discuss adding callback functionality to a VB5 application to provide the ability to pre-select a folder on the dialog's
display.
Once again, VBnet is pleased to present methods
developed by Brad Martinez (http://www.mvps.org/btmtz/),
author of the Browse for Folders routines presented on this site, as well as author of the Common
Controls Replacement Project BrowseDialog
control.
So what is a pidl anyway?
With the inception of Win95 and WinNT4 the shell began using a scheme referred to as an 'item identifier' (item ID) to identify
every object in the namespace, including not only file system files and folders, but also 'virtual' objects implemented by the
shell itself, such as the Desktop, Network Neighborhood, Control Panel, Recycle Bin and Dial-Up Networking folders, to name a
few.
Item IDs can be thought of as something similar to a file
system's path. Item IDs that are fully qualified (AKA absolute or complex) are refereed to as an 'item ID list', with the root
item ID being the Desktop folder (similar to the root of a path). A single item ID (AKA simple item ID list) is only valid if
referenced by its parent folder (similar to a relative path). Item IDs are always referenced by a memory pointer, and are
consequently titled 'PIDLs' (piddle), or pointer to an item ID list. With the browse dialog, we'll only be dealing with absolute
PIDLs.
One important point to note about PIDLs is that the shell
allocates memory for each pidl returned from an API function, which must subsequently be freed by the shell's task memory
allocator to prevent a memory leak. The API function CoTaskMemFree defined below was designed just for this purpose.
Converting a file-system path to a pidl
Aside from what SHGetSpecialFolderLocation returns in the way of pre-defined special shell folders (including virtual folders),
there are a two ways that I'm aware of to covert any file system path to a pidl, one the hard and proper way, the other the easy
and undocumented way. First the hard and proper way...
Encapsulate the shell's IShellFolder interface and call
its ParseDisplayName member function. This entails defining the IShellFolder member functions in ODL or IDL (Object/Interface
Description Language, see the SDK for more info) and compiling a type library. But you're in luck, the EnumDeskVB example on my
site (http://www.mvps.org/btmtz/) contains a file named ISHF_Ex.tlb in
which I've wrapped the IShellFolder interface into a type library. See the GetPIDLFromPath function in the EnumDeskVB project's
IShellFolder.bas file to see how ParseDisplayName is called.
For the easy and undocumented way, Shell32.dll contains a
rather neat little undocumented function that essentially duplicates what GetPIDLFromPath does (calls ParseDisplayName with the
path passed to it converted to Unicode and returns the path's pidl) called SHSimpleIDListFromPath:
Declare Function SHSimpleIDListFromPath Lib _
"shell32" Alias "#162" _
(ByVal szPath As String) As Long
Some things to consider about this function: First, as far as I know, the function is exported at the specified ordinal in all
current versions of Shell32.dll (including Win95 and WinNT's v4.00.*, IE3's v4.70, IE4 and WinNT 5.0's v4.71, and Win98's v4.72),
but there is nothing that says it will be at this ordinal or will be available at all in any future versions of the library.
Secondly, szPath must be specified as an ANSI string in
when this function is called from Win95, and as a Unicode string when called on WinNT (see the UndocShell example on my site to
see how this can be done).
Finally the function's name is misleading. The function
returns an absolute pidl relative to the desktop folder (as opposed to a "simple" pidl relative to its parent
IShellFolder, see the top of this message).
How to specify a Browse dialog's 'root' folder
Easy enough, just specify the folder's pidl for the BROWSEINFO struct's pidlRoot member. Special shell folder PIDLs can be
retrieved by calling the SHGetSpecialFolderLocation API function. To obtain a pidl for a file system path, see "How to
convert a file system path to a pidl".
And don't forget, that the shell allocates memory for every pidl returned from an API function and must subsequently be freed via
the shell's task allocator by calling the SHSimpleIDListFromPath API function. Again, see "So what is a pidl anyway?"
for more information.
Implementing a Browse Callback
When calling SHBrowseForFolder, in order to for the browse dialog to open and have a specified folder pre-selected, you must
implement the BrowseCallbackProc application-defined callback function in a BAS module. Windows defines the structure of the
BrowseCallback procedure with the following syntax:
Public Function BrowseCallbackProc(ByVal hWnd As Long, _
ByVal uMsg As Long, _
ByVal lParam As Long, _
ByVal lpData As Long) As Long
The value of the callback function's memory address
(returned by the AddressOf operator) must be specified for the "lpfn" member of the BROWSEINFO struct. Since VB
restricts use of the AddressOf operator to function parameters, the callback function's memory address can be obtained indirectly
with the following method:
Dim bi As BROWSEINFO
bi.lpfn = FARPROC(AddressOf BrowseCallbackProc)
Public Function FARPROC(ByVal pfn As Long) As Long
'A dummy procedure that receives and
'returns the return value of the
'AddressOf operator
FARPROC = pfn
End Function
After SHBrowseForFolder has been called, and the browse
dialog subsequently calls the callback function, the BrowseCallbackProc function's parameters contain the following values:
- hWnd - Handle of the browse dialog
window.
- uMsg - Message received by the
browse dialog, which one of the following Values:
|
'The browse dialog box has
finished initializing. lParam is NULL.
Public Const BFFM_INITIALIZED = 1
'The selection has
changed. lParam is a pointer to the item
'identifier list for the newly selected folder.
Public Const BFFM_SELECTIONCHANGED = 2 |
- lParam - Message-specific value. See
the description of uMsg above.
- lpData - Application-defined value
that was specified in the lParam member of the BROWSEINFO structure.
Once one of the above messages has been received in the
callback function, any one of the following messages can be sent to the browse dialog's window (specified by the hWnd value):
Public Const WM_USER = &H400
'Sets the status text to the null-terminated string
'specified by the lParam parameter, wParam is ignored
'and should be set to 0.
Public Const BFFM_SETSTATUSTEXTA = (WM_USER + 100)
Public Const BFFM_SETSTATUSTEXTW = (WM_USER + 104)
'If the lParam parameter is non-zero, enables the
'OK button, or disables it if lParam is zero.
'(docs erroneously said wParam!)
'wParam is ignored and should be set to 0.
Public Const BFFM_ENABLEOK = (WM_USER + 101)
'Selects the specified folder. If the wParam
'parameter is FALSE, the lParam parameter is the
'PIDL of the folder to select , or it is the path
'of the folder if wParam is the C value TRUE (or 1).
'Note that after this message is sent, the browse
'dialog receives a subsequent BFFM_SELECTIONCHANGED message.
Public Const BFFM_SETSELECTIONA = (WM_USER + 102)
Public Const BFFM_SETSELECTIONW = (WM_USER + 103)
The associated code in SHBrowseForFolder: Pre-selecting Folders using a Browse Callback demonstrates
using the Browse callback to select a predetermined folder using either of the folder's absolute path or file system PIDL.
|