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.