Sometimes you want to choose a folder but the usual system dialogs require you to choose a file as well. The API function SHBrowseForFolder does not show any files: it does exactly what the name implies.
/* a test/demo program */ DEFINE VARIABLE folder AS CHARACTER NO-UNDO. DEFINE VARIABLE canceled AS LOGICAL NO-UNDO. RUN BrowseForFolder.p ("choose the directory where you want to dump your data", OUTPUT folder, OUTPUT canceled). MESSAGE "folder=" folder SKIP "canceled=" canceled VIEW-AS ALERT-BOX. /* ========================================================== file: BrowseForFolder.p ========================================================== */ {windows.i} DEFINE INPUT PARAMETER DialogTitle AS CHARACTER NO-UNDO. DEFINE OUTPUT PARAMETER FolderName AS CHARACTER NO-UNDO. DEFINE OUTPUT PARAMETER Canceled AS LOGICAL NO-UNDO. DEFINE VARIABLE MAX_PATH AS INTEGER INITIAL 260. DEFINE VARIABLE lpbi AS MEMPTR. /* pointer to BROWSEINFO structure */ DEFINE VARIABLE pszDisplayName AS MEMPTR. DEFINE VARIABLE lpszTitle AS MEMPTR. DEFINE VARIABLE lpItemIDList AS INTEGER NO-UNDO. DEFINE VARIABLE ReturnValue AS INTEGER NO-UNDO. SET-SIZE(lpbi) = 32. SET-SIZE(pszDisplayName) = MAX_PATH. SET-SIZE(lpszTitle) = LENGTH(DialogTitle) + 1. PUT-STRING(lpszTitle,1) = DialogTitle. PUT-LONG(lpbi, 1) = 0. /* hwnd for parent */ PUT-LONG(lpbi, 5) = 0. PUT-LONG(lpbi, 9) = GET-POINTER-VALUE(pszDisplayName). PUT-LONG(lpbi,13) = GET-POINTER-VALUE(lpszTitle). PUT-LONG(lpbi,17) = 1. /* BIF_RETURNONLYFSDIRS = only accept a file system directory */ PUT-LONG(lpbi,21) = 0. /* lpfn, callback function */ PUT-LONG(lpbi,25) = 0. /* lParam for lpfn */ PUT-LONG(lpbi,29) = 0. RUN SHBrowseForFolder IN hpApi ( INPUT GET-POINTER-VALUE(lpbi), OUTPUT lpItemIDList ). /* parse the result: */ IF lpItemIDList=0 THEN DO: Canceled = YES. FolderName = "". END. ELSE DO: Canceled = NO. FolderName = FILL(" ", MAX_PATH). RUN SHGetPathFromIDList IN hpApi(lpItemIDList, OUTPUT FolderName, OUTPUT ReturnValue). FolderName = TRIM(FolderName). END. /* free memory: */ SET-SIZE(lpbi)=0. SET-SIZE(pszDisplayName)=0. SET-SIZE(lpszTitle)=0. RUN CoTaskMemFree (lpItemIDList). PROCEDURE CoTaskMemFree EXTERNAL "ole32.dll" : DEFINE INPUT PARAMETER lpVoid AS LONG. END PROCEDURE.
Documentation says that SHBrowseForFolder is not supported on Windows NT. However the above procedure was tested on Windows NT and seemed to work fine.
The memory occupied by lpItemIDList can be freed by CoTaskMemFree. This was discovered by Todd G. Nist who explains "This will free the memory the shell allocated for the ITEMIDLIST structure which consists of one or more consecutive ITEMIDLIST structures packed on byte boundaries, followed by a 16-bit zero value. An application can walk a list of item identifiers by examining the size specified in each SHITEMID structure and stopping when it finds a size of zero. A pointer to an item identifier list, is called a PIDL (pronounced piddle.) "
An different but very interesting approach is to use COM Automation: the "shell.application" interface contains a BrowseForFolder function. There is an example in article 18823 of the Progress Knowledgebase.
There is a different example by Julian Lyndon-Smith on page BrowseForFolder using COM
SHBrowseForFolder supports the use of a callback function from where you can specify an initial folder or perform some validations. Unfortunately, callback functions can not be written in Progress 4GL so you will have to wrap it in a DLL. This has been done by Cyril O'Floinn, see BrowseForFolder with an initial folder