Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

Do you need a standard dialog to browse for a folder ?


  • Please log in to reply
5 replies to this topic
Azerty
  • Members
  • 72 posts
  • Last active: Jan 16 2009 10:08 AM
  • Joined: 19 Dec 2006
Hello all

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.

majkinetor
  • Moderators
  • 4512 posts
  • Last active: May 20 2019 07:41 AM
  • Joined: 24 May 2006
You probably missed that this is already in AHK itself.

However,

I didn't test it, but I know that native SHBrowseForFolder provide more options then available in AHK interface, like option to show files along...
Posted Image

Azerty
  • Members
  • 72 posts
  • Last active: Jan 16 2009 10:08 AM
  • Joined: 19 Dec 2006
majkinetor :
You're right, but callback is much appreciated for my own use.
Moreover, all flags permitted by native UI are available.

As I said, I only added 2 cents :)

...And a pretty method to encapsulate API callbacks in wrappers I've not seen previously (or did I miss it ?)

automaticman
  • Members
  • 658 posts
  • Last active: Nov 20 2012 06:10 PM
  • Joined: 27 Oct 2006
? Can this be transformed into a dir cloner (incl. all subdirs) and then deleting in the clone all specific file types in it? e.g.
copy dirA dirB
if not (*.c or *.cpp or *.java or *.pl or *.py or *.h or *.ahk or *.txt)
{
  delete in dirB
}
Why? Then you could have a copy of your original source code collection which you can analyze using various tools which don't offer comfortable filtering features.

majkinetor
  • Moderators
  • 4512 posts
  • Last active: May 20 2019 07:41 AM
  • Joined: 24 May 2006

...And a pretty method to encapsulate API callbacks in wrappers I've not seen previously (or did I miss it ?)

It can be furhter improved for practicity and speed.

Can this be transformed into a dir cloner (incl. all subdirs) and then deleting in the clone all specific file types in it? e.g.

Mhm... I don't think so. Its probably easier to create specialised module for that.
Posted Image

  • Guests
  • Last active:
  • Joined: --
#Include com.ahk



com_CoInitialize()



pbrowse := COM_ActiveXObject("Shell.Application")

pprompt := COM_Invoke(pbrowse, "BrowseForFolder", "0", "Enter", "0x050", "0")

path := COM_Invoke(COM_Invoke(pprompt, "self"), "Path")

com_CoUnInitialize()

exitapp