using System; using System.Text; using System.Runtime.InteropServices; using System.Windows; using System.Windows.Interop; namespace DTS.Common.Dialogs { /// /// Represents a common dialog box (Win32::SHBrowseForFolder()) that allows a user to select a folder. /// public class BrowseForFolderDialog { #region Public Properties /// /// Gets the current and or final selected folder path. /// public string SelectedFolder { get; protected set; } /// /// Gets or sets the string that is displayed above the tree View control in the dialog box (must set BEFORE calling ShowDialog()). /// public string Title { get => BrowseInfo.lpszTitle; set => BrowseInfo.lpszTitle = value; } /// /// Gets or sets the initially selected folder path. /// public string InitialFolder { get; set; } /// /// Gets or sets the initially selected and expanded folder path. Overrides SelectedFolder. /// public string InitialExpandedFolder { get; set; } /// /// Gets or sets the text for the dialog's OK button. /// public string OKButtonText { get; set; } /// /// Provides direct access to the Win32::SHBrowseForFolder() BROWSEINFO structure used to create the dialog in ShowDialog(). /// public BROWSEINFOW BrowseInfo { get; protected set; } /// /// Provides direct access to the ulFlags field of the Win32::SHBrowseForFolder() structure used to create the dialog in ShowDialog(). /// public BrowseInfoFlags BrowserDialogFlags { get => BrowseInfo.ulFlags; set => BrowseInfo.ulFlags = value; } #endregion #region Public Constructors /// /// Constructs a BrowseForFolderDialog with default BrowseInfoFlags set to BIF_NEWDIALOGSTYLE. /// public BrowseForFolderDialog() { BrowseInfo = new BROWSEINFOW(); BrowseInfo.hwndOwner = IntPtr.Zero; BrowseInfo.pidlRoot = IntPtr.Zero; BrowseInfo.pszDisplayName = new String(' ', 260); BrowseInfo.lpszTitle = "Select a folder:"; BrowseInfo.ulFlags = BrowseInfoFlags.BIF_NEWDIALOGSTYLE; BrowseInfo.lpfn = new BrowseCallbackProc(BrowseEventHandler); BrowseInfo.lParam = IntPtr.Zero; BrowseInfo.iImage = -1; } #endregion #region Public ShowDialog() Overloads /// /// Shows the dialog (Win32::SHBrowseForFolder()). /// public Nullable ShowDialog() { return PInvokeSHBrowseForFolder(null); } /// /// Shows the dialog (Win32::SHBrowseForFolder()) with its hwndOwner set to the handle of 'owner'. /// public Nullable ShowDialog(Window owner) { return PInvokeSHBrowseForFolder(owner); } #endregion #region PInvoke Stuff private Nullable PInvokeSHBrowseForFolder(Window owner) { WindowInteropHelper windowhelper; if (null != owner) { windowhelper = new WindowInteropHelper(owner); BrowseInfo.hwndOwner = windowhelper.Handle; } var pidl = SHBrowseForFolderW(BrowseInfo); if (IntPtr.Zero != pidl) { var pathsb = new StringBuilder(260); if (SHGetPathFromIDList(pidl, pathsb)) { SelectedFolder = pathsb.ToString(); Marshal.FreeCoTaskMem(pidl); return true; } } return false; } private int BrowseEventHandler(IntPtr hwnd, MessageFromBrowser uMsg, IntPtr lParam, IntPtr lpData) { switch (uMsg) { case MessageFromBrowser.BFFM_INITIALIZED: { // The dialog box has finished initializing. // lParam Not used, value is NULL. if (!string.IsNullOrEmpty(InitialExpandedFolder)) SendMessageW(hwnd, MessageToBrowser.BFFM_SETEXPANDED, new IntPtr(1), InitialExpandedFolder); else if (!string.IsNullOrEmpty(InitialFolder)) SendMessageW(hwnd, MessageToBrowser.BFFM_SETSELECTIONW, new IntPtr(1), InitialFolder); if (!string.IsNullOrEmpty(OKButtonText)) SendMessageW(hwnd, MessageToBrowser.BFFM_SETOKTEXT, new IntPtr(1), OKButtonText); break; } case MessageFromBrowser.BFFM_SELCHANGED: { // The selection has changed in the dialog box. // lParam A pointer to an item identifier list (PIDL) identifying the newly selected item. var pathsb = new StringBuilder(260); if (SHGetPathFromIDList(lParam, pathsb)) { SelectedFolder = pathsb.ToString(); } break; } case MessageFromBrowser.BFFM_VALIDATEFAILEDA: // ANSI { // The user typed an invalid name into the dialog's edit box. A nonexistent folder is considered an invalid name. // lParam A pointer to a string containing the invalid name. An application can use this data in an error dialog informing the user that the name was not valid. // Return zero to dismiss the dialog or nonzero to keep the dialog displayed break; } case MessageFromBrowser.BFFM_VALIDATEFAILEDW: // Unicode { // The user typed an invalid name into the dialog's edit box. A nonexistent folder is considered an invalid name. // lParam A pointer to a string containing the invalid name. An application can use this data in an error dialog informing the user that the name was not valid. // Return zero to dismiss the dialog or nonzero to keep the dialog displayed break; } case MessageFromBrowser.BFFM_IUNKNOWN: { // An IUnknown interface is available to the dialog box. // lParam A pointer to an IUnknown interface. break; } } return 0; } public delegate int BrowseCallbackProc(IntPtr hwnd, MessageFromBrowser uMsg, IntPtr lParam, IntPtr lpData); [Flags] public enum BrowseInfoFlags : uint { /// /// No specified BIF_xxx flags. /// BIF_None = 0x0000, /// /// Only return file system directories. If the user selects folders that are not part of the file system, the OK button is grayed. /// BIF_RETURNONLYFSDIRS = 0x0001, // For finding a folder to start document searching /// /// Do not include network folders below the domain level in the dialog box's tree View control. /// BIF_DONTGOBELOWDOMAIN = 0x0002, // For starting the Find Computer /// /// Include a status area in the dialog box. /// BIF_STATUSTEXT = 0x0004, // Top of the dialog has 2 lines of text for BROWSEINFO.lpszTitle and one line if // this flag is set. Passing the message BFFM_SETSTATUSTEXTA to the hwnd can set the // rest of the text. This is not used with BIF_USENEWUI and BROWSEINFO.lpszTitle gets // all three lines of text. /// /// Only return file system ancestors. An ancestor is a subfolder that is beneath the root folder in the namespace hierarchy. /// BIF_RETURNFSANCESTORS = 0x0008, /// /// Include an edit control in the browse dialog box that allows the user to type the name of an item. /// BIF_EDITBOX = 0x0010, // Add an editbox to the dialog /// /// 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. /// BIF_VALIDATE = 0x0020, // insist on valid result (or CANCEL) /// /// Use the new user interface. Setting this flag provides the user with a larger dialog box that can be resized. /// BIF_NEWDIALOGSTYLE = 0x0040, // Use the new dialog layout with the ability to resize // Caller needs to call OleInitialize() before using this API /// /// Use the new user interface, including an edit box. This flag is equivalent to BIF_EDITBOX | BIF_NEWDIALOGSTYLE. /// BIF_USENEWUI = BIF_NEWDIALOGSTYLE | BIF_EDITBOX, /// /// The browse dialog box can display URLs. The BIF_USENEWUI and BIF_BROWSEINCLUDEFILES flags must also be set. /// BIF_BROWSEINCLUDEURLS = 0x0080, // Allow URLs to be displayed or entered. (Requires BIF_USENEWUI) /// /// When combined with BIF_NEWDIALOGSTYLE, adds a usage hint to the dialog box in place of the edit box. /// BIF_UAHINT = 0x0100, // Add a UA hint to the dialog, in place of the edit box. May not be combined with BIF_EDITBOX /// /// Do not include the New Folder button in the browse dialog box. /// BIF_NONEWFOLDERBUTTON = 0x0200, // Do not add the "New Folder" button to the dialog. Only applicable with BIF_NEWDIALOGSTYLE. /// /// When the selected item is a shortcut, return the PIDL of the shortcut itself rather than its target. /// BIF_NOTRANSLATETARGETS = 0x0400,// don't traverse target as shortcut /// /// Only return computers. If the user selects anything other than a computer, the OK button is grayed. /// BIF_BROWSEFORCOMPUTER = 0x1000, // Browsing for Computers. /// /// Only allow the selection of printers. If the user selects anything other than a printer, the OK button is grayed. /// BIF_BROWSEFORPRINTER = 0x2000, // Browsing for Printers /// /// The browse dialog box will display files as well as folders. /// BIF_BROWSEINCLUDEFILES = 0x4000,// Browsing for Everything /// /// The browse dialog box can display shareable resources on remote systems. /// BIF_SHAREABLE = 0x8000 // sharable resources displayed (remote shares, requires BIF_USENEWUI) } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] public class BROWSEINFOW { /// /// A handle to the owner window for the dialog box. /// public IntPtr hwndOwner; /// /// A pointer to an item identifier list (PIDL) specifying the location of the root folder from which to start browsing. /// public IntPtr pidlRoot; // PCIDLIST_ABSOLUTE /// /// The address of a buffer to receive the display name of the folder selected by the user. The size of this buffer is assumed to be MAX_PATH characters. /// public string pszDisplayName; // Output parameter! (length must be >= MAX_PATH) /// /// The address of a null-terminated string that is displayed above the tree View control in the dialog box. /// public string lpszTitle; /// /// Flags specifying the options for the dialog box. /// public BrowseInfoFlags ulFlags; /// /// A BrowseCallbackProc delegate that the dialog box calls when an event occurs. /// public BrowseCallbackProc lpfn; /// /// An application-defined value that the dialog box passes to the BrowseCallbackProc delegate, if one is specified. /// public IntPtr lParam; /// /// A variable to receive the image associated with the selected folder. The image is specified as an index to the system image list. /// public int iImage; // Output parameter! } // message from browser public enum MessageFromBrowser : uint { /// /// The dialog box has finished initializing. /// BFFM_INITIALIZED = 1, /// /// The selection has changed in the dialog box. /// BFFM_SELCHANGED = 2, /// /// (ANSI) The user typed an invalid name into the dialog's edit box. A nonexistent folder is considered an invalid name. /// BFFM_VALIDATEFAILEDA = 3, /// /// (Unicode) The user typed an invalid name into the dialog's edit box. A nonexistent folder is considered an invalid name. /// BFFM_VALIDATEFAILEDW = 4, /// /// An IUnknown interface is available to the dialog box. /// BFFM_IUNKNOWN = 5 } // messages to browser public enum MessageToBrowser : uint { /// /// Win32 API macro - start of user defined window message range. /// WM_USER = 0x0400, /// /// (ANSI) Sets the status text. Set lpData to point to a null-terminated string with the desired text. /// BFFM_SETSTATUSTEXTA = WM_USER + 100, /// /// Enables or disables the dialog box's OK button. lParam - To enable, set to a nonzero value. To disable, set to zero. /// BFFM_ENABLEOK = WM_USER + 101, /// /// (ANSI) Specifies the path of a folder to select. /// BFFM_SETSELECTIONA = WM_USER + 102, /// /// (Unicode) Specifies the path of a folder to select. /// BFFM_SETSELECTIONW = WM_USER + 103, /// /// (Unicode) Sets the status text. Set lpData to point to a null-terminated string with the desired text. /// BFFM_SETSTATUSTEXTW = WM_USER + 104, /// /// Sets the text that is displayed on the dialog box's OK button. /// BFFM_SETOKTEXT = WM_USER + 105, // Unicode only /// /// Specifies the path of a folder to expand in the Browse dialog box. /// BFFM_SETEXPANDED = WM_USER + 106 // Unicode only } [DllImport("shell32.dll")] private static extern IntPtr SHBrowseForFolderW([MarshalAs(UnmanagedType.LPStruct), In, Out] BROWSEINFOW bi); [DllImport("shell32.dll")] private static extern bool SHGetPathFromIDList(IntPtr pidl, StringBuilder path); [DllImport("user32.dll")] public static extern IntPtr SendMessageW(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam); [DllImport("user32.dll")] public static extern IntPtr SendMessageW(IntPtr hWnd, MessageToBrowser msg, IntPtr wParam, [MarshalAs(UnmanagedType.LPWStr)] string str); #endregion } }