Here's my 2 cents in library : a wrapper for SHBrowseForFolder
/*
AHK wrapper for BrowseForFolder v1.0
Recommanded fileName : BrowseForFolder.ahk
Author : LHdx 2008/02
Permission is granted to use copy for commercial or non commercial use provided credit to author remains in source code.
WARNING : depending on your usage of this wrapper, you *might* have to check shell32.dll version. It is not done directly in this code
to reduce overhead.
USE AT YOUR OWN RISKS !
Base reference : http://msdn2.microsoft.com/en-us/library/bb762115(VS.85).aspx
Usage : BrowseForFolder(
Owner window HWND (can be NULL),
Flags (described in function code) joined in a string where they are separated by spaces,
Root of browse (can be a CSIDL variable or a string containing a real directory path),
Title of window,
Hint text,
user callback function (see below),
user callback data (see below),
initial status text
)
Note : you may supply an AutoHotkey function which receives 4 args as a callback function. Il will be able treat notifications
as described in http://msdn2.microsoft.com/en-us/library/bb762598(VS.85).aspx but needs not be RegisterCallback()'ed (done in the
wrapper). Below are the messages it receives (<WM_USER) or it can send to window (>=WM_USER) to change behavior.
#define WM_USER 1024
#define BFFM_INITIALIZED 1
#define BFFM_SELCHANGED 2
#define BFFM_VALIDATEFAILED 3
#define BFFM_SETSTATUSTEXT (WM_USER + 100)
#define BFFM_ENABLEOK (WM_USER + 101)
#define BFFM_SETSELECTION (WM_USER + 102)
#define BFFM_SETOKTEXT (WM_USER + 105)
#define BFFM_SETEXPANDED (WM_USER + 106)
*/
BrowseForFolder(pOwner=0, pFlags="", pRoot="", pPath="", pTitle="", pHint="Please, choose a folder...", pUserCallbackFunction="", pUserCallbackData=0, pStatusText="") {
Local lStaticSplit, lUserFlags, lBrowseInfo, lCslRoot, lMyUserData
Local lPidl, lResultat, lUserCallback
Static lCallback=0
staticFlags=
(LTrim %
BIF_RETURNONLYFSDIRS|0x1|Only returns file system directories. If the user selects folders that are not part of the file system, the OK button is grayed.
BIF_DONTGOBELOWDOMAIN|0x2|Does not include network folders below the domain level in the tree view control.
BIF_STATUSTEXT|0x4|Includes a status area in the dialog box. The callback function can set the status text by sending messages to the dialog box.
BIF_RETURNFSANCESTORS|0x8|Only returns file system ancestors. If the user selects anything other than a file system ancestor, the OK button is grayed.
BIF_EDITBOX|0x10|Version 4.71. Include an edit control in the browse dialog box that allows the user to type the name of an item.
BIF_VALIDATE|0x20|Version 4.71. If the user types an invalid name into the edit box, the browse dialog box will call the application's BrowseCallbackProc with the BFFM_VALIDATEFAILED message. This flag is ignored if BIF_EDITBOX is not specified.
BIF_NEWDIALOGSTYLE|0x40|Version 5.0. Use the new user interface. Setting this flag provides the user with a larger dialog box that can be resized. The dialog box has several new capabilities including: drag-and-drop capability within the dialog box, reordering, shortcut menus, new folders, delete, and other shortcut menu commands. To use this flag, you must call OleInitialize or CoInitialize before calling SHBrowseForFolder.
BIF_BROWSEINCLUDEURLS|0x80|Version 5.0. The browse dialog box can display URLs. The BIF_USENEWUI and BIF_BROWSEINCLUDEFILES flags must also be set. If these three flags are not set, the browser dialog box will reject URLs. Even when these flags are set, the browse dialog box will only display URLs if the folder that contains the selected item supports them. When the folder's IShellFolder::GetAttributesOf method is called to request the selected item's attributes, the folder must set the SFGAO_FOLDER attribute flag. Otherwise, the browse dialog box will not display the URL.
BIF_UAHINT|0x100|Version 6.0. When combined with BIF_NEWDIALOGSTYLE, adds a usage hint to the dialog box in place of the edit box. BIF_EDITBOX overrides this flag.
BIF_NONEWFOLDERBUTTON|0x200|Version 6.0. Do not include the New Folder button in the browse dialog box.
BIF_NOTRANSLATETARGETS|0x400|Version 6.0. When the selected item is a shortcut, return the PIDL of the shortcut itself rather than its target.
BIF_BROWSEFORCOMPUTER|0x1000|Only returns computers. If the user selects anything other than a computer, the OK button is grayed.
BIF_BROWSEFORPRINTER|0x2000|Only returns printers. If the user selects anything other than a printer, the OK button is grayed.
BIF_BROWSEINCLUDEFILES|0x4000|Version 4.71. The browse dialog box will display files as well as folders.
BIF_SHAREABLE|0x8000|Version 5.0. The browse dialog box can display shareable resources on remote systems. It is intended for applications that want to expose remote shares on a local system. The BIF_NEWDIALOGSTYLE flag must also be set.
BIF_USENEWUI|0x50|Version 5.0. Use the new user interface, including an edit box. This flag is equivalent to BIF_EDITBOX | BIF_NEWDIALOGSTYLE. To use BIF_USENEWUI, you must call OleInitialize or CoInitialize before calling SHBrowseForFolder.
)
staticRootCsl=
(LTrim %
CSIDL_DESKTOP|0x0
CSIDL_INTERNET|0x1
CSIDL_PROGRAMS|0x2
CSIDL_CONTROLS|0x3
CSIDL_PRINTERS|0x4
CSIDL_PERSONAL|0x5
CSIDL_FAVORITES|0x6
CSIDL_STARTUP|0x7
CSIDL_RECENT|0x8
CSIDL_SENDTO|0x9
CSIDL_BITBUCKET|0xA
CSIDL_STARTMENU|0xB
CSIDL_MYDOCUMENTS|0xC
CSIDL_MYMUSIC|0xD
CSIDL_MYVIDEO|0xE
CSIDL_DESKTOPDIRECTORY|0x10
CSIDL_DRIVES|0x11
CSIDL_NETWORK|0x12
CSIDL_NETHOOD|0x13
CSIDL_FONTS|0x14
CSIDL_TEMPLATES|0x15
CSIDL_COMMON_STARTMENU|0x16
CSIDL_COMMON_PROGRAMS|0x17
CSIDL_COMMON_STARTUP|0x18
CSIDL_COMMON_DESKTOPDIRECTORY|0x19
CSIDL_APPDATA|0x1A
CSIDL_PRINTHOOD|0x1B
CSIDL_LOCAL_APPDATA|0x1C
CSIDL_ALTSTARTUP|0x1D
CSIDL_COMMON_ALTSTARTUP|0x1E
CSIDL_COMMON_FAVORITES|0x1F
CSIDL_INTERNET_CACHE|0x20
CSIDL_COOKIES|0x21
CSIDL_HISTORY|0x22
CSIDL_COMMON_APPDATA|0x23
CSIDL_WINDOWS|0x24
CSIDL_SYSTEM|0x25
CSIDL_PROGRAM_FILES|0x26
CSIDL_MYPICTURES|0x27
CSIDL_PROFILE|0x28
CSIDL_PROGRAM_FILES_COMMON|0x2B
CSIDL_COMMON_TEMPLATES|0x2D
CSIDL_COMMON_DOCUMENTS|0x2E
CSIDL_COMMON_ADMINTOOLS|0x2F
CSIDL_ADMINTOOLS|0x30
CSIDL_CONNECTIONS|0x31
CSIDL_COMMON_MUSIC|0x35
CSIDL_COMMON_PICTURES|0x36
CSIDL_COMMON_VIDEO|0x37
CSIDL_RESOURCES|0x38
CSIDL_RESOURCES_LOCALIZED|0x39
CSIDL_COMMON_OEM_LINKS|0x3A
CSIDL_CDBURN_AREA|0x3B
CSIDL_COMPUTERSNEARME|0x3D
)
; I'll register my own callback only once
If (lCallback=0)
lCallback:=RegisterCallback("BrowseForFolder_PrivateHelper", "", 4)
; Prepare internal callback to wrap up user callback function
If (pUserCallbackFunction)
lUserCallback:=RegisterCallback(pUserCallbackFunction, "", 4)
; Parse user flags
lUserFlags=0
If (pFlags) {
pFlags:=" " pFlags " "
Loop, Parse, staticFlags, `n
{
StringSplit, lStaticSplit, A_LoopField, |
If InStr(pFlags, " " lStaticSplit1 " ")
lUserFlags|=lStaticSplit2
}
}
; Lookup root of browse
lCslRoot=-1
If (pRoot) {
; It might be a CSIDL_
Loop, Parse, staticRootCsl, `n
{
StringSplit, lStaticSplit, A_LoopField, |
If (pRoot = lStaticSplit1)
lCslRoot := lStaticSplit2
}
If (lCslRoot>-1) {
DllCall("shell32\SHGetFolderLocation", "UInt", 0, "Int", lCslRoot, "UInt", 0, "UInt", 0, "UInt *", lPidl)
} Else {
; No, it was not a CSIDL_, so it should be a valid path
lPidl:=DllCall("shell32\ILCreateFromPathA", "Str", pRoot, "UInt")
}
}
Else
; None given
lPidl = 0
; Prepare storage for structures
VarSetCapacity(lBrowseInfo, 32)
VarSetCapacity(lResultat, 260)
VarSetCapacity(lMyUserData, 20)
; BROWSEINFO structure, see http://msdn2.microsoft.com/en-us/library/bb773205(VS.85).aspx
NumPut(pOwner, lBrowseInfo)
NumPut(lPidl, lBrowseInfo, 4)
NumPut(&pHint, lBrowseInfo, 12)
NumPut(lUserFlags, lBrowseInfo, 16)
NumPut(lCallback, lBrowseInfo, 20)
NumPut(&lMyUserData, lBrowseInfo, 24)
; Prepare internal structure such as :
; struct {
; char *mTitle; // Title for window
; char *mPath; // Pre-selected path upon display of dialog
; char *mStatusText; // Initial status text
; cdecl int (*mUserCallback)(HWND, UINT, LPARAM, LPARAM); // Pointer to user callback function
; LPARAM mUserData; // User data for free use in user callback function
; }
If (pTitle)
NumPut(&pTitle, lMyUserData, 0)
NumPut(&pPath, lMyUserData, 4)
If (pStatusText)
NumPut(&pStatusText, lMyUserData, 8)
If (lUserCallback)
NumPut(lUserCallback+0, lMyUserData, 12)
NumPut(pUserCallbackData, lMyUserData, 16)
lNewPidl:=DllCall("shell32\SHBrowseForFolder", "uint", &lBrowseInfo)
; Unregister internal callback used to wrap up user callback function
If (lUserCallback)
DllCall("GlobalFree", UInt, lUserCallback)
; In http://msdn2.microsoft.com/en-us/library/bb776438.aspx, it is said that : Call ILFree to release the ITEMIDLIST when you are finished with it.
; ; I'm sure it's as good as CoTaskFree()
If (lPidl)
DllCall("shell32\ILFree", "UInt", lPidl)
If (lNewPidl) {
DllCall("shell32\SHGetPathFromIDList", "UInt", lNewPidl, "str", lResultat)
DllCall("shell32\ILFree", "UInt", lNewPidl)
Return %lResultat%
}
Return ""
}
; This one is for internal use only...
BrowseForFolder_PrivateHelper(pHwnd, pMsg, pLparam, pUserData)
{
If (pMsg=1) { ; BFFM_INITIALIZED
; Set browse window title
lText:=NumGet(pUserData+0, 0)
If (lText)
DllCall("user32\SendMessage", "uint", pHwnd, "uint", 12, "uint", 0, "uint", lText) ; WM_SETTEXT
; Set browse window status
lText:=NumGet(pUserData+0, 8)
If (lText)
DllCall("user32\SendMessage", "uint", pHwnd, "uint", 1124, "uint", 1, "uint", lText) ; BFFM_SETSTATUSTEXTA
; Call user callback if any given
If (lUserCallback:=NumGet(pUserData+0, 12))
DllCall(lUserCallback+0, "uint", pHwnd, "uint", pMsg, "uint", pLparam, "uint", NumGet(pUserData+0, 16))
; Next DllCall is reported after callback because it generates a recursive callback and user callback *should*
; be able to treat BFFM_INITIALIZED before receiving first BFFM_SELCHANGED. Untested : WTF if user decided to
; call BFFM_SETSELECTION in its own callback... so, user, BE CAREFULL !
; Select intial selection if user wished it
DllCall("user32\SendMessage", "uint", pHwnd, "uint", 1126, "uint", 1, "uint", NumGet(pUserData+0, 4)) ; BFFM_SETSELECTIONA
}
Else {
; Any message other than BFFM_INITIALIZED should be treated by user-defined callback
If (lUserCallback:=NumGet(pUserData+0, 12))
Return DllCall(lUserCallback+0, "uint", pHwnd, "uint", pMsg, "uint", pLparam, "uint", NumGet(pUserData+0, 16))
}
Return 0
}
You've got a sample there :
#singleinstance force
gSourceDir:=GetFullPathName(A_WorkingDir)
Gui, Add, Text, x6 y7 w110 h20 +Right, &Source directory :
Gui, Add, Edit, x126 y7 w370 h20 vSourceDir, %gSourceDir%
Gui, Add, Button, x506 y7 w80 h20 gGuiSelectSourceDir, Select...
Gui, Add, Button, x116 y267 w100 h30 gGuiCancel, Cancel
Gui, Show, x131 y91 h303 w600, Sample...
return
GuiCancel:
ExitApp
GuiSelectSourceDir:
GuiControlGet, lCurrentDir, , SourceDir
Gui, +LastFound
WinGet, lGuiId, ID
lCurrentDir:=BrowseForFolder(lGuiId, "BIF_RETURNONLYFSDIRS BIF_NONEWFOLDERBUTTON BIF_USENEWUI", "CSIDL_DRIVES", lCurrentDir, "Choose source folder...", "Please, choose the source folder, cannot be a root drive !", "CheckRootDrive")
If (lCurrentDir)
GuiControl, , SourceDir, %lCurrentDir%
Return
CheckRootDrive(hwnd, msg, param, unused) {
if (msg=2) { ; BFFM_SELCHANGED, param is PIDL to new selection
VarSetCapacity(lPath,260)
if (DllCall("shell32\SHGetPathFromIDList", "UInt", param, "Str", lPath)) {
lEnable := 1 - RegExMatch(lPath, "^[A-Z]:\\$")
DllCall("SendMessage", "UInt", hwnd, "UInt", 1024+101, "UInt", 0, "UInt", lEnable)
} Else
DllCall("SendMessage", "UInt", hwnd, "UInt", 1024+101, "UInt", 0, "UInt", 0)
}
Return
}
GetFullPathName(pPath) {
Local lChemin, lPointeur, lLastError
VarSetCapacity(lChemin, 32768)
lPointeur=0
lLastError:=A_LastError
DllCall("GetFullPathName", str, pPath, uint, 32768, str, lChemin, uintp, lPointeur)
DllCall("GetLongPathName", str, lChemin, str, lChemin, uint, 32768)
Return lChemin
}
Note : this code shows what seems a but in OS (at least XP SP2 in my case) : if you start the sample script in a root dir, the BFFM_SETENABLE is not handled correcly on initialisation. Any tested suggestion to circumvent will be welcome.
Comments are welcome.




