How to implement a managed component that wraps the Browse For Folder common dialog box by using Visual C# (306285)



The information in this article applies to:

  • Microsoft Visual C# 2005
  • Microsoft Visual C# .NET (2003)
  • Microsoft Visual C# .NET (2002)

This article was previously published under Q306285
For a Microsoft Visual Basic 2005 or Microsoft Visual Basic .NET version of this article, see 811004.
This article refers to the following Microsoft .NET Framework Class Library namespaces:
  • System.Runtime.InteropServices
  • System.Text
  • System.Security.Permissions
The following file is available for download from the Microsoft Download Center:
DownloadDownload the Bffcomp.exe package now.
Release Date: October 1, 2001

For more information about how to download Microsoft support files, click the following article number to view the article in the Microsoft Knowledge Base:

119591 How to obtain Microsoft support files from online services

Microsoft scanned this file for viruses. Microsoft used the most current virus-detection software that was available on the date that the file was posted. The file is stored on security-enhanced servers that help prevent any unauthorized changes to the file.

SUMMARY

This step-by-step article describes how to write a design time component that wraps the Browse For Folder common dialog box.

Implement the design-time component

  1. Generate an empty project. To do this, create a new blank solution and name it BrowseForFolder. Add a new Visual C# .NET Class Library project to the newly-created solution, and name the project WinFormsExtras.
  2. Modify the wizard-generated code. To do this, rename the Class1.cs file from the project to FolderBrowser.cs, and delete the Class1 class that the wizard adds to this file. Rename the namespace from WinFormsExtra.cs (WinFormsExtras) to Microsoft.Samples.WinForms.Extras. On the project's Properties pages, rename the default namespace from WinFormsExtras to Microsoft.Samples.WinForms.Extras. Change the assembly name from WinFormsExtras to Microsoft.Samples.WinForms.Extras.
  3. Add the declarations that are needed for P/Invoke and ComInterop. Make sure that System.Runtime.InteropServices and System.Text are referenced, and provide declarations for the functions and interfaces that you need, as follows:
    using System.Runtime.InteropServices;
    using System.Text;
    					
       internal class Win32API
       {
          // C# representation of the IMalloc interface.
          [InterfaceType ( ComInterfaceType.InterfaceIsIUnknown ),
             Guid ( "00000002-0000-0000-C000-000000000046" )]
          public interface IMalloc
          {
             [PreserveSig] IntPtr Alloc ( [In] int cb );
             [PreserveSig] IntPtr Realloc ( [In] IntPtr pv, [In] int cb );
             [PreserveSig] void   Free ( [In] IntPtr pv );
             [PreserveSig] int    GetSize ( [In] IntPtr pv );
             [PreserveSig] int    DidAlloc ( IntPtr pv );
             [PreserveSig] void   HeapMinimize ( );
          }
    
          [DllImport("User32.DLL")]
          public static extern IntPtr GetActiveWindow ( );
    
          public class Shell32
          {
             // Styles used in the BROWSEINFO.ulFlags field.
             [Flags]    
             public enum BffStyles 
             {
                RestrictToFilesystem = 0x0001, // BIF_RETURNONLYFSDIRS
                RestrictToDomain =     0x0002, // BIF_DONTGOBELOWDOMAIN
                RestrictToSubfolders = 0x0008, // BIF_RETURNFSANCESTORS
                ShowTextBox =          0x0010, // BIF_EDITBOX
                ValidateSelection =    0x0020, // BIF_VALIDATE
                NewDialogStyle =       0x0040, // BIF_NEWDIALOGSTYLE
                BrowseForComputer =    0x1000, // BIF_BROWSEFORCOMPUTER
                BrowseForPrinter =     0x2000, // BIF_BROWSEFORPRINTER
                BrowseForEverything =  0x4000, // BIF_BROWSEINCLUDEFILES
             }
    
             // Delegate type used in BROWSEINFO.lpfn field.
             public delegate int BFFCALLBACK ( IntPtr hwnd, uint uMsg, IntPtr lParam, IntPtr lpData );
    
             [StructLayout ( LayoutKind.Sequential, Pack=8 )]
                public struct BROWSEINFO
             {
                public IntPtr       hwndOwner;
                public IntPtr       pidlRoot;
                public IntPtr       pszDisplayName;
                [MarshalAs ( UnmanagedType.LPTStr )]
                public string       lpszTitle;
                public int          ulFlags;
                [MarshalAs ( UnmanagedType.FunctionPtr )]
                public BFFCALLBACK  lpfn;
                public IntPtr       lParam;
                public int          iImage;
             }
    
             [DllImport ( "Shell32.DLL" )]
             public static extern int SHGetMalloc ( out IMalloc ppMalloc );
    
             [DllImport ( "Shell32.DLL" )]
             public static extern int SHGetSpecialFolderLocation ( 
                         IntPtr hwndOwner, int nFolder, out IntPtr ppidl );
    
             [DllImport ( "Shell32.DLL" )]
             public static extern int SHGetPathFromIDList ( 
                         IntPtr pidl, StringBuilder Path );
    
             [DllImport ( "Shell32.DLL", CharSet=CharSet.Auto )]
             public static extern IntPtr SHBrowseForFolder ( ref BROWSEINFO bi );
          }
       }
    					
  4. Create the FolderBrowser component class. To do this, add references to the System.Drawing and System.Windows.Forms .NET assemblies. Make sure to identify the namespaces that are used in the implementation of the component, and establish the component's internal data structures, as follows:
    using System.Drawing;
    using System.Windows.Forms;
    using System.ComponentModel;
    using System.Security.Permissions;
    					
       /// <summary>
       /// Component wrapping access to the Browse For Folder common dialog box.
       /// Call the ShowDialog() method to bring the dialog box up.
       /// </summary>
       public sealed class FolderBrowser : Component
       {
          private static readonly int MAX_PATH = 260;
        
          // Root node of the tree view.
          private FolderID startLocation = FolderID.Desktop;
    
          // Browse info options.
          private int publicOptions = (int) Win32API.Shell32.BffStyles.RestrictToFilesystem |
               (int) Win32API.Shell32.BffStyles.RestrictToDomain;
          private int privateOptions = (int) Win32API.Shell32.BffStyles.NewDialogStyle;
    
          // Description text to show.
          private string descriptionText = "Please select a folder below:";
    
          // Folder chosen by the user.
          private string directoryPath = String.Empty;
    
          /// <summary>
          /// Enum of CSIDLs identifying standard shell folders.
          /// </summary>
          public enum FolderID 
          {
             Desktop                   = 0x0000,
             Printers                  = 0x0004,
             MyDocuments               = 0x0005,
             Favorites                 = 0x0006,
             Recent                    = 0x0008,
             SendTo                    = 0x0009,
             StartMenu                 = 0x000b,
             MyComputer                = 0x0011,
             NetworkNeighborhood       = 0x0012,
             Templates                 = 0x0015,
             MyPictures                = 0x0027,
             NetAndDialUpConnections   = 0x0031,
          }
      }
    					
  5. Implement the ShowDialog method on the FolderBrowser class, as follows:
          /// <summary>
          /// Helper function that returns the IMalloc interface used by the shell.
          /// </summary>
          private static Win32API.IMalloc GetSHMalloc ( )
          {
             Win32API.IMalloc malloc;
             Win32API.Shell32.SHGetMalloc ( out malloc );
             return malloc;
          }
    
          /// <summary>
          /// Shows the folder browser dialog box.
          /// </summary>
          public DialogResult ShowDialog ( ) 
          {
             return ShowDialog ( null );
          }
    
          /// <summary>
          /// Shows the folder browser dialog box with the specified owner window.
          /// </summary>
          public DialogResult ShowDialog ( IWin32Window owner ) 
          {
             IntPtr pidlRoot = IntPtr.Zero;
    
             // Get/find an owner HWND for this dialog.
             IntPtr hWndOwner;
    
             if ( owner != null ) 
             {
                hWndOwner = owner.Handle;
             }
             else 
             {
                 hWndOwner = Win32API.GetActiveWindow ( );
             }
    
             // Get the IDL for the specific startLocation.
             Win32API.Shell32.SHGetSpecialFolderLocation ( hWndOwner, (int) startLocation, out pidlRoot );
    
             if (pidlRoot == IntPtr.Zero) 
             {
                return DialogResult.Cancel;
             }
    
             int mergedOptions = (int)publicOptions | (int)privateOptions;
    
             if ( ( mergedOptions & (int)Win32API.Shell32.BffStyles.NewDialogStyle ) != 0 ) 
                {
                    if ( System.Threading.ApartmentState.MTA == Application.OleRequired ( ) )
                        mergedOptions = mergedOptions & (~ (int)Win32API.Shell32.BffStyles.NewDialogStyle);
                }
    
             IntPtr pidlRet = IntPtr.Zero;
    
             try 
             {
                // Construct a BROWSEINFO.
                Win32API.Shell32.BROWSEINFO bi = new Win32API.Shell32.BROWSEINFO ( );
                IntPtr buffer = Marshal.AllocHGlobal ( MAX_PATH);
    
                bi.pidlRoot = pidlRoot;
                bi.hwndOwner = hWndOwner;
                bi.pszDisplayName = buffer;
                bi.lpszTitle = descriptionText;
                bi.ulFlags = mergedOptions;
                // The rest of the fields are initialized to zero by the constructor.
                // bi.lpfn = null;  bi.lParam = IntPtr.Zero;    bi.iImage = 0;
    
                // Show the dialog.
                pidlRet = Win32API.Shell32.SHBrowseForFolder ( ref bi );
    
                // Free the buffer you've allocated on the global heap.
                Marshal.FreeHGlobal ( buffer );
    
                if ( pidlRet == IntPtr.Zero ) 
                {
                   // User clicked Cancel.
                   return DialogResult.Cancel;
                }
    
                // Then retrieve the path from the IDList.
                StringBuilder sb = new StringBuilder ( MAX_PATH );
                if ( 0 == Win32API.Shell32.SHGetPathFromIDList ( pidlRet, sb ) )
                {
                   return DialogResult.Cancel;
                }
    
                // Convert to a string.
                directoryPath = sb.ToString ( );
             }
             finally 
             {
                Win32API.IMalloc malloc = GetSHMalloc ( );
                malloc.Free ( pidlRoot );
    
                if ( pidlRet != IntPtr.Zero ) 
                {
                   malloc.Free ( pidlRet );
                }
             }
    
             return DialogResult.OK;
          }
    					
  6. Add convenience properties to allow dialog customization. To do this, add a number of bool properties that allow the user to set and reset flags in the publicOptions field of the dialog box. The following properties are implemented:
    • OnlyFilesystem
    • ShowNetworkFolders
    • OnlySubfolders
    • ShowTextBox
    • ValidateUserInput
    • SelectComputer
    • SelectPrinter
    • SelectFiles
    • DirectoryPath
    • Description
    In addition, implement the StartLocation property as follows:
          /// <summary>
          /// Helper function used to set and reset bits in the publicOptions bitfield.
          /// </summary>
          private void SetOptionField ( int mask, bool turnOn )
          {
             if (turnOn)
                publicOptions |= mask;
             else
                publicOptions &= ~mask;
          }
    
          /// <summary>
          /// Only return file system directories. If the user selects folders
          /// that are not part of the file system, the OK button is unavailable.
          /// </summary>
          [Category ( "Navigation" )]
          [Description ( "Only return file system directories. If the user selects folders " +
                         "that are not part of the file system, the OK button is unavailable." )]
          [DefaultValue ( true )]
          public bool OnlyFilesystem
          {
             get 
             {
                return (publicOptions & (int) Win32API.Shell32.BffStyles.RestrictToFilesystem) != 0;
             }
             set 
             {
                SetOptionField ( (int) Win32API.Shell32.BffStyles.RestrictToFilesystem, value );
             }
          }
    
          /// <summary>
          /// Location of the root folder from which to start browsing. Only the specified
          /// folder and any folders beneath it in the namespace hierarchy  appear
          /// in the dialog box.
          /// </summary>
          [Category ( "Navigation" )]
          [Description ( "Location of the root folder from which to start browsing. Only the specified " +
                         "folder and any folders beneath it in the namespace hierarchy appear " +
                         "in the dialog box." )]
          [DefaultValue ( typeof(FolderID), "0")]
          public FolderID StartLocation 
          {
             get 
             {
                return startLocation;
             }
             set 
             {
                new UIPermission ( UIPermissionWindow.AllWindows ).Demand ( );
                startLocation = value;
             }
          }
    
          /// <summary>
          /// Full path to the folder selected by the user.
          /// </summary>
          [Category("Navigation")]
          [Description("Full path to the folder slected by the user.")]
          public string DirectoryPath 
          {
    	 get 
    	 {
    	    return directoryPath;
    	 }
          } 
    
    					
  7. Provide a toolbox icon for the component. To do this, right-click the project in Solution Explorer, click Add, click New Item, click Resources, and then click Bitmap File. Name the new bitmap file FolderBrowser.bmp and resize it to be 16 x 16 pixels. Edit the bitmap as desired. In Solution Explorer, select the FolderBrowser.bmp file, open the property grid, and then set Build Action to Embedded Resource. Next, associate this bitmap with your component by adding an attribute to the FolderBrowser class, as follows:
       [ToolboxBitmap(typeof(FolderBrowser))]
       public sealed class FolderBrowser : Component
       { // ...
       }
    					
  8. Build the project.

Implement the test project

  1. Generate the empty project. To do this, add a new Visual C# Windows Application project named CsClient to the BrowseForFolder solution. Add a reference to the Microsoft.Samples.WinForms.Extras assembly that you just created.
  2. Add a component to the toolbox. To do this, open the Toolbox window, right-click the window, and then select Customize Toolbox. On the .NET Framework Components tab, click Browse and browse to the Microsoft.Samples.WinForms.Extras.dll file that you just created. When the FolderBrowser component appears in the list, select it and dismiss the dialog box.
  3. Customize the form. To do this, with Form1 open in design mode, open the Toolbox window and drag a button and a FolderBrowser component to your design area.
  4. Customize the FolderBrowser component. To do this, click the folderBrowser1 icon and note all the properties that you can customize in the property grid. Customize these properties as desired.
  5. Add code to display the dialog box. To do this, double-click the button that you dragged onto the form and add the following code to the button handler:
          private void button1_Click(object sender, System.EventArgs e)
          {
             if ( DialogResult.OK == folderBrowser1.ShowDialog() )
                MessageBox.Show ( folderBrowser1.DirectoryPath );
          }
    					
  6. Build the solution, and then run the CsClient project.

Modification Type:MinorLast Reviewed:10/4/2006
Keywords:kbdownload kbCOMInterop kbHOWTOmaster KB306285 kbAudDeveloper