vbAccelerator - Contents of code file: FolderBrowser.cs

using System;
using System.Collections;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Forms;

namespace vbAccelerator.Components.Shell
{
   #region FolderBrowserFilter
   /// <summary>
   /// An object to manage filtering of items displayed
   /// in the folder browser
   /// </summary>
   public class FolderBrowserFilter
   {
      #region Member Variables
      private bool applyFilter = false;
      private FileFilterSpecificationCollection items = null;
      #endregion

      #region Implementation
      /// <summary>
      /// Gets/sets whether the items shown in the folder
      /// will be filtered.
      /// </summary>
      public bool ApplyFilter
      {
         get
         {
            return this.applyFilter;
         }
         set
         {
            this.applyFilter = value;
         }
      }

      /// <summary>
      /// Returns whether the specified file should be filtered from
      /// the view
      /// </summary>
      /// <param name="file">The file to check.</param>
      /// <param name="isFolder">Whether the file is a folder or not.</param>
      /// <returns>True to filter the item, False otherwise</returns>      
      public virtual bool ShouldFilter(string file, bool isFolder)
      {
         bool ret = false;
         if (items.Count > 0)
         {
            foreach (FileFilterSpecification filter in items)
            {
               if (isFolder)
               {
                  if (filter.ApplyToFolder)
                  {
                     if (filter.RegularExpression.IsMatch(file))
                     {
                        ret = false;
                        break;
                     }
                     else
                     {
                        ret = true;
                     }
                  }
               }
               else 
               {
                  if (filter.ApplyToFile)
                  {
                     if (filter.RegularExpression.IsMatch(file))
                     {
                        ret = false;
                        break;
                     }
                     else
                     {
                        ret = true;
                     }
                  }
               }
            }
         }
         return ret;
      }

      /// <summary>
      /// Returns the collection of file filters to apply to 
      /// the Folder Browser View.
      /// </summary>
      public FileFilterSpecificationCollection Items
      {
         get
         {
            return this.items;
         }
      }
      #endregion

      #region Constructor and Destructor
      /// <summary>
      /// Creates a default instance of a Folder Browser filter.
      /// </summary>
      public FolderBrowserFilter()
      {
         this.items = new FileFilterSpecificationCollection();
      }

      /// <summary>
      /// Creates an instance of a Folder Browser filter with
      /// the specified filters.
      /// </summary>
      /// <param name="items"></param>
      public FolderBrowserFilter(FileFilterSpecificationCollection items)
      {
         this.items = items;
      }
      #endregion

   }
   #endregion

   #region FileFilterSpecificationCollection
   
   /// <summary>
   /// Contains a collection of filters to be applied to the
   /// FolderBrowser.
   /// </summary>
   public class FileFilterSpecificationCollection : CollectionBase
   {
      /// <summary>
      /// Adds a new file filter to the filter list.
      /// </summary>
      /// <param name="filter">The filter to add</param>
      public void Add(FileFilterSpecification filter)
      {
         this.InnerList.Add(filter);
      }

      /// <summary>
      /// Returns the filter at the specified index.
      /// </summary>
      public FileFilterSpecification this[int index]
      {
         get
         {
            return (FileFilterSpecification)this.InnerList[index];
         }
         set
         {
            this.InnerList[index] = value;
         }
      }

      /// <summary>
      /// Creates a new, blank filter collection.
      /// </summary>
      public FileFilterSpecificationCollection()
      {
      }

      /// <summary>
      /// Creates a new filter collection containing the specified
      /// filters.
      /// </summary>
      /// <param name="filters">The filters to add.</param>
      public FileFilterSpecificationCollection(FileFilterSpecification[]
       filters)
      {
         foreach (FileFilterSpecification filter in filters)
         {
            this.InnerList.Add(filter);
         }
      }
   }

   #endregion

   #region FileFilterSpecification

   /// <summary>
   /// Specifies a regular expression for filtering
   /// a file or folder in the Folder Browser.
   /// </summary>
   public class FileFilterSpecification
   {
      private Regex regularExpression = null;
      private string filter = "";
      private bool applyToFile = true;
      private bool applyToFolder = false;


      /// <summary>
      /// Gets/sets whether this filter should apply to
      /// files.
      /// </summary>
      public bool ApplyToFile
      {
         get
         {
            return this.applyToFile;
         }
         set
         {
            this.applyToFile = value;
         }
      }

      /// <summary>
      /// Gets/sets whether this filter should apply to
      /// folders.
      /// </summary>
      public bool ApplyToFolder
      {
         get
         {
            return this.applyToFolder;
         }
         set
         {
            this.applyToFolder = value;
         }
      }

      /// <summary>
      /// Gets/sets the regular expression to match against
      /// filtering items
      /// </summary>
      public string Filter
      {
         get
         {
            return this.filter;
         }
         set
         {
            this.filter = value;
            regularExpression = new Regex(filter);
         }
      }

      /// <summary>
      /// Returns the compiled regular expression for 
      /// this filter.
      /// </summary>
      internal Regex RegularExpression
      {
         get
         {
            return this.regularExpression;
         }
      }

      /// <summary>
      /// Creates a new file filter without specifying
      /// its details.
      /// </summary>
      public FileFilterSpecification()
      {
      }

      /// <summary>
      /// Creates a new file filter and initialises all
      /// the members.
      /// </summary>
      /// <param name="filter">The regular expression to use
      /// when matching this filter.</param>
      /// <param name="applyToFile">Whether this filter should
      /// be applied to files.</param>
      /// <param name="applyToFolder">Whether this filter should
      /// be applied to folders.</param>
      public FileFilterSpecification(
         string filter,
         bool applyToFile,
         bool applyToFolder
         )
      {
         Filter = filter;
         this.applyToFile = applyToFile;
         this.applyToFolder = applyToFolder;
      }

   }

   #endregion

   #region ValidationFailed Event Arguments and Delegate
   /// <summary>
   /// Class to manage arguments for Validation Failure
   /// in the Folder Browser.
   /// </summary>
   public class ValidationFailedEventArgs
   {
      private string message = "";
      private bool cancel = true;

      /// <summary>
      /// Returns the text from the Edit Box which has caused 
      /// validation to fail
      /// </summary>
      public string Message
      {
         get
         {
            return this.message;
         }
      }

      /// <summary>
      /// Gets/sets whether the dialog should be Cancelled (closed)
      /// or not.
      /// </summary>
      public bool Cancel
      {
         get
         {
            return this.cancel;
         }
         set
         {
            this.cancel = value;
         }
      }

      internal ValidationFailedEventArgs(
         string message
         )
      {
         this.message = message;
      }
   }

   /// <summary>
   /// Delegate to allow ValidationFailedEvent to be called.
   /// </summary>
   public delegate void ValidationFailedEventHandler(object sender,
    ValidationFailedEventArgs e);
   #endregion

   #region SelectionChanged Event Arguments and Delegate
   /// <summary>
   /// Class to hold details of a selection change for the selection
   /// change event.
   /// </summary>
   public class SelectionChangedEventArgs
   {
      private FolderBrowser.SafePidl pidl = null;

      /// <summary>
      /// Gets the pidl of the newly selected item
      /// </summary>
      public IntPtr Pidl
      {
         get
         {
            return this.pidl.Pidl;
         }
      }

      /// <summary>
      /// Gets the path of the newly selected item
      /// </summary>
      public string Path
      {
         get
         {            
            return this.pidl.Path;
         }
      }


      internal SelectionChangedEventArgs(FolderBrowser.SafePidl pidl)
      {
         this.pidl = pidl;
      }
   }

   /// <summary>
   /// Delegate to allow SelectionChangedEvents to be propagated.
   /// </summary>
   public delegate void SelectionChangedEventHandler(object sender,
    SelectionChangedEventArgs e);
   #endregion   

   #region FolderBrowser
   /// <summary>
   /// Provides a Shell Folder Browser (not available under .NET 
   /// Framwork 1.0)
   /// </summary>
   public class FolderBrowser : IDisposable
   {
      #region ShellFolder Enumerations
      [Flags]
         private enum ESTRRET : int
      {
         STRRET_WSTR     = 0x0000,         // Use STRRET.pOleStr
         STRRET_OFFSET   = 0x0001,         // Use STRRET.uOffset to Ansi
         STRRET_CSTR     = 0x0002         // Use STRRET.cStr
      }
      [Flags]
         private enum ESHCONTF : int
      {
         SHCONTF_FOLDERS = 0x0020,
         SHCONTF_NONFOLDERS = 0x0040,
         SHCONTF_INCLUDEHIDDEN = 0x0080,
         SHCONTF_INIT_ON_FIRST_NEXT = 0x0100,   // allow EnumObject() to return
          before validating enum
         SHCONTF_NETPRINTERSRCH  = 0x0200,   // hint that client is looking for
          printers
         SHCONTF_SHAREABLE = 0x0400,   // hint that client is looking sharable
          resources (remote shares)
         SHCONTF_STORAGE = 0x0800,   // include all items with accessible
          storage and their ancestors
      }

      [Flags]
         private enum ESHGDN : int
      {
         SHGDN_NORMAL = 0,
         SHGDN_INFOLDER = 1,
         SHGDN_FORADDRESSBAR = 16384,
         SHGDN_FORPARSING = 32768
      }
      [Flags]
         private enum ESFGAO : int
      {
         SFGAO_CANCOPY = 1,
         SFGAO_CANMOVE = 2,
         SFGAO_CANLINK = 4,
         SFGAO_CANRENAME = 16,
         SFGAO_CANDELETE = 32,
         SFGAO_HASPROPSHEET = 64,
         SFGAO_DROPTARGET = 256,
         SFGAO_CAPABILITYMASK = 375,
         SFGAO_LINK = 65536,
         SFGAO_SHARE = 131072,
         SFGAO_READONLY = 262144,
         SFGAO_GHOSTED = 524288,
         SFGAO_DISPLAYATTRMASK = 983040,
         SFGAO_FILESYSANCESTOR = 268435456,
         SFGAO_FOLDER = 536870912,
         SFGAO_FILESYSTEM = 1073741824,
         SFGAO_HASSUBFOLDER = -2147483648,
         SFGAO_CONTENTSMASK = -2147483648,
         SFGAO_VALIDATE = 16777216,
         SFGAO_REMOVABLE = 33554432,
         SFGAO_COMPRESSED = 67108864
      }
      #endregion

      #region ShellFolder Structures 
      [StructLayoutAttribute(LayoutKind.Sequential, Pack=4, Size=0,
       CharSet=CharSet.Auto)]
         private struct STRRET_CSTR
      {
         public ESTRRET uType;
         [MarshalAs(System.Runtime.InteropServices.UnmanagedType.ByValArray,
          SizeConst=520)]
         public byte[]         cStr;
      }
   
      [StructLayout(LayoutKind.Explicit, CharSet=CharSet.Auto)]
         private struct STRRET_ANY
      {
         [FieldOffset(0)] 
         public ESTRRET uType;
         [FieldOffset(4)] 
         public IntPtr pOLEString; 
      }

      [StructLayoutAttribute(LayoutKind.Sequential)]
         private struct SIZE 
      {
         public int cx;
         public int cy;
      }

      [StructLayoutAttribute(LayoutKind.Sequential)]
         private struct RECT
      {
         public int left;
         public int top;
         public int right;
         public int bottom;
      }
      #endregion

      #region COM Interop for IEnumIDList
      [ComImportAttribute()]
         [GuidAttribute("000214F2-0000-0000-C000-000000000046")]
         [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
         //helpstring("IEnumIDList interface")
         private interface IEnumIDList
      {
         [PreserveSig]
         int Next(
            int celt, 
            ref IntPtr rgelt, 
            out int pceltFetched);

         void Skip(
            int celt);

         void Reset();
    
         void Clone(
            ref IEnumIDList ppenum);
      };
      #endregion

      #region Com Interop for IUnknown
      [ComImport, Guid("00000000-0000-0000-C000-000000000046")]
         [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
         private interface IUnknown
      {
         [PreserveSig]
         IntPtr QueryInterface(ref Guid riid, out IntPtr pVoid);
      
         [PreserveSig]
         IntPtr AddRef();

         [PreserveSig]
         IntPtr Release();
      }
      #endregion

      #region COM Interop for IShellFolder
      [ComImportAttribute()]
         [GuidAttribute("000214E6-0000-0000-C000-000000000046")]
         [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
         //helpstring("IShellFolder interface")
         private interface IShellFolder
      {
         void ParseDisplayName(
            IntPtr hwndOwner, 
            IntPtr pbcReserved, 
            [MarshalAs(UnmanagedType.LPWStr)] string lpszDisplayName,
            out int pchEaten, 
            out IntPtr ppidl, 
            out int pdwAttributes);

         void EnumObjects(
            IntPtr hwndOwner, 
            [MarshalAs(UnmanagedType.U4)] ESHCONTF grfFlags,
            ref IEnumIDList ppenumIDList
            );

         void BindToObject(
            IntPtr pidl, 
            IntPtr pbcReserved, 
            ref Guid riid, 
            ref IShellFolder ppvOut);

         void BindToStorage(
            IntPtr pidl, 
            IntPtr pbcReserved, 
            ref Guid riid, 
            IntPtr ppvObj
            );

         [PreserveSig]
         int CompareIDs(
            IntPtr lParam, 
            IntPtr pidl1, 
            IntPtr pidl2);

         void CreateViewObject(
            IntPtr hwndOwner, 
            ref Guid riid, 
            IntPtr ppvOut);

         void GetAttributesOf(
            int cidl, 
            [MarshalAs(UnmanagedType.LPArray, SizeParamIndex=0)] IntPtr[]
             apidl, 
            [MarshalAs(UnmanagedType.U4)] ref ESFGAO rgfInOut); 

         void GetUIObjectOf(
            IntPtr hwndOwner, 
            int cidl, 
            ref IntPtr apidl, 
            ref Guid riid, 
            out int prgfInOut, 
            ref IUnknown ppvOut);

         void GetDisplayNameOf(
            IntPtr pidl, 
            [MarshalAs(UnmanagedType.U4)] ESHGDN uFlags,  
            ref STRRET_CSTR lpName);

         void SetNameOf(
            IntPtr hwndOwner, 
            IntPtr pidl,
            [MarshalAs(UnmanagedType.LPWStr)] string lpszName,
            [MarshalAs(UnmanagedType.U4)] ESHCONTF uFlags,
            ref IntPtr ppidlOut);
      };
      #endregion

      #region IFolderFilter Com Interop
      [ComImportAttribute()]
         [GuidAttribute("9CC22886-DC8E-11d2-B1D0-00C04F8EEB3E")]
         [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
         private interface IFolderFilter
      {
         [PreserveSig]
         int ShouldShow(
            IShellFolder psf, 
            IntPtr pidlFolder, 
            IntPtr pidlItem);

         [PreserveSig]
         int GetEnumFlags(
            IShellFolder psf, 
            IntPtr pidlFolder, 
            IntPtr phwnd, 
            [MarshalAs(UnmanagedType.U4)] out ESHCONTF pgrfFlags);
      };

      #endregion

      #region IFolderFilterSite Com Interop
      [ComImportAttribute()]
         [GuidAttribute("C0A651F5-B48B-11d2-B5ED-006097C686F6")]
         [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
         //helpstring("IFolderFilterSite"),
         private interface IFolderFilterSite
      {
         void SetFilter(
            [MarshalAs(UnmanagedType.Interface)] IFolderFilter punk);
      }
      #endregion

      #region FolderBrowser IMalloc Com Interop
      [ComImportAttribute()]
         [GuidAttribute("00000002-0000-0000-C000-000000000046")]
         [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
         //helpstring("IMalloc interface")
         private interface IMalloc
      {
         [PreserveSig]
         IntPtr Alloc(int cb);

         [PreserveSig]
         IntPtr Realloc(
            IntPtr pv,
            int cb);
      
         [PreserveSig]
         void Free(IntPtr pv);

         [PreserveSig]
         int GetSize(IntPtr pv);

         [PreserveSig]
         int DidAlloc(IntPtr pv);

         [PreserveSig]
         void  HeapMinimize();
      };
      #endregion

      #region SafePidl
      /// <summary>
      /// Class to manage a Shell Pointer to ID List (Pidl)      
      /// </summary>
      public class SafePidl : IDisposable
      {
         private IntPtr pidl = IntPtr.Zero;
         private bool shouldFree = true;

         /// <summary>
         /// Returns the Pointer to ID List for this object.
         /// </summary>
         public IntPtr Pidl
         {
            get
            {
               return pidl;
            }
         }
         /// <summary>
         /// Returns the path name for this Pidl
         /// </summary>
         public string Path
         {
            get
            {
               StringBuilder path = new StringBuilder(260, 260);
               UnManagedMethods.SHGetPathFromIDList(pidl, path);
               return path.ToString();
            }
         }

         private IntPtr PathToPidl(string path)
         {
            IntPtr newPidl = IntPtr.Zero ;
            IShellFolder ishFolder = null;
            if (UnManagedMethods.SHGetDesktopFolder(out ishFolder) == 0) // S_OK
            {
               // we have it:
               int cParsed = 0;
               int afItem = 0;            
               ishFolder.ParseDisplayName(
                  IntPtr.Zero, 
                  IntPtr.Zero, 
                  path, 
                  out cParsed, 
                  out newPidl, 
                  out afItem);
               Marshal.ReleaseComObject(ishFolder);
            }
            return newPidl;
         }


         /// <summary>
         /// Enables the Pidl to be freed.
         /// </summary>
         public void Dispose()
         {
            Dispose(true);
            GC.SuppressFinalize(this);
         }
         /// <summary>
         /// Enables the Pidl to be freed when disposing is true.
         /// </summary>
         /// <param name="disposing">Whether disposing or not</param>
         protected virtual void Dispose(bool disposing) 
         {
            if (disposing) 
            {
               if (pidl != IntPtr.Zero)
               {
                  if (shouldFree)
                  {
                     Allocator.Free(pidl);   
                  }
               }
               pidl = IntPtr.Zero;
            }
         }


         internal SafePidl(IntPtr pidl, bool shouldFree)
         {
            this.pidl = pidl;
            this.shouldFree = shouldFree;
         }
         /// <summary>
         /// Constructs a new Pidl for the specified path
         /// </summary>
         /// <param name="path">Path to get Pidl for</param>
         public SafePidl(string path)
         {
            if (path.Length > 0)
            {
               if (System.IO.Directory.Exists(path))
               {
                  this.pidl = PathToPidl(path);
               }
               else
               {
                  throw new System.IO.DirectoryNotFoundException(
                     String.Format("The path {0} could not be found.", path));
               }
            }
         }
      }
      #endregion

      #region Allocator
      /// <summary>
      /// Class to manage the Shell's IMalloc interface.
      /// </summary>
      public class Allocator : IDisposable
      {
         private static IMalloc alloc = null;
         private static bool disposed = false;

         /// <summary>
         /// Frees the specified memory (if allocated by the
         /// Shell).
         /// </summary>
         /// <param name="ptr">Pointer to memory to free</param>
         public static void Free(IntPtr ptr)
         {
            if (!disposed)
            {
               if (alloc == null)
               {
                  UnManagedMethods.SHGetMalloc(out alloc);
               }
               if (alloc != null)
               {
                  try
                  {
                     alloc.Free(ptr);
                  }
                  catch (Exception ex)
                  {
                     Console.WriteLine("Problem {0}", ex.Message);
                  }
               }
            }
         }

         /// <summary>
         /// Frees the reference to the Shell's IMalloc object
         /// </summary>
         public void Dispose()
         {
            Dispose(true);
            GC.SuppressFinalize(this);
         }

         /// <summary>
         /// Frees the reference to the SHell's IMalloc object
         /// when disposing is true.
         /// </summary>
         /// <param name="disposing"></param>
         protected virtual void Dispose(bool disposing)
         {
            if (disposing)
            {
               if (!disposed)
               {
                  if (alloc != null)
                  {
                     Marshal.ReleaseComObject(alloc);
                     alloc = null;
                  }
                  disposed = true;
               }
            }

         }
      }
      #endregion

      #region BrowseItemFilter
      
      [ComVisible(true)]
      [Guid("43104713-CF18-43d4-8A3F-BC45AD280D57")]
      private class BrowseItemFilter : IFolderFilter
      {
         FolderBrowserFilter filter = null;

         public int ShouldShow(
            IShellFolder psf,            // A pointer to the folder's
             IShellFolder interface.
            IntPtr pidlFolder,      // The folder's PIDL.
            IntPtr pidlItem)      // The item's PIDL.
         {
            // Get the display name of the item:
            int ret = 0;
            if (filter != null)
            {
               // Get the display name of this item:
               STRRET_CSTR name = new STRRET_CSTR();
               psf.GetDisplayNameOf(
                  pidlItem,
                  ESHGDN.SHGDN_NORMAL | ESHGDN.SHGDN_FORPARSING, 
                  ref name);
               string displayName = "";
               if (name.uType == ESTRRET.STRRET_WSTR)
               {
                  // first 4 bytes of name.cStr is a pointer
                  // to a string;
                  IntPtr strPtr = (IntPtr)(
                     (((int)name.cStr[3]) << 24) |
                     (((int)name.cStr[2]) << 16) |
                     (((int)name.cStr[1]) << 8) |
                     (int)name.cStr[0]);
                  displayName = Marshal.PtrToStringAuto(strPtr);
               }
               else
               {
                  // name.cStr contains the string as ANSI..
                  // there must be a better way than this?
                  for (int i = 0; i < 260; i++)
                  {
                     if (name.cStr[i] == 0)
                     {
                        break;
                     }
                     else
                     {
                        displayName += Convert.ToChar(name.cStr[i]);
                     }
                  }
               }
               // determine if folder:
               ESFGAO attrib = ESFGAO.SFGAO_FOLDER;
               IntPtr[] apidl = new IntPtr[1];
               apidl[0] = pidlItem;
               psf.GetAttributesOf(1, apidl, ref attrib);               
               bool isFolder = ((attrib & ESFGAO.SFGAO_FOLDER) == 
                ESFGAO.SFGAO_FOLDER);
               if (filter.ShouldFilter(
                  displayName, 
                  isFolder)
                  )
               {
                  ret = 1;
               }
            }
            return ret;
         }

         public int GetEnumFlags(
            IShellFolder psf, 
            IntPtr pidlFolder, 
            IntPtr phwnd, 
            out ESHCONTF pgrfFlags
            )
         {
            //            
            pgrfFlags = ESHCONTF.SHCONTF_FOLDERS 
               | ESHCONTF.SHCONTF_NONFOLDERS
               | ESHCONTF.SHCONTF_SHAREABLE 
               | ESHCONTF.SHCONTF_STORAGE ;
            return 0;
         }

         public BrowseItemFilter(FolderBrowserFilter filter)
         {
            this.filter = filter;
         }
      }

      #endregion

      #region Internal Delegates
      private delegate int BrowseCallBackProc(IntPtr hwnd, int msg, IntPtr lp,
       IntPtr wp);
      #endregion

      #region FolderBrowser Structs
      [StructLayout(LayoutKind.Sequential)]
      private struct BrowseInfo
      {
         public IntPtr hwndOwner;
         public IntPtr pidlRoot;
         [MarshalAs(UnmanagedType.LPTStr)]
         public string displayname;
         [MarshalAs(UnmanagedType.LPTStr)]
         public string title;
         public int flags;
         [MarshalAs(UnmanagedType.FunctionPtr)]
         public BrowseCallBackProc callback;
         public IntPtr lparam;
      }
      #endregion   

      #region FolderBrowser UnManaged Methods
      /// <summary>
      /// A class that defines all the unmanaged methods used in the assembly
      /// </summary>
      private class UnManagedMethods
      {
         [DllImport("Shell32.dll", CharSet=CharSet.Auto)]
         internal extern static System.IntPtr SHBrowseForFolder(ref BrowseInfo
          bi);
      
         [DllImport("Shell32.dll", CharSet=CharSet.Auto)]
         [return : MarshalAs(UnmanagedType.Bool)]
         internal extern static bool SHGetPathFromIDList(IntPtr pidl,
          [MarshalAs(UnmanagedType.LPTStr)] System.Text.StringBuilder pszPath);

         [DllImport("shell32", CharSet = CharSet.Auto)]
         internal extern static int SHGetDesktopFolder(out IShellFolder ppshf);

         [DllImport("User32.Dll")]
         [return : MarshalAs(UnmanagedType.Bool)]
         internal extern static bool SendMessage(IntPtr hwnd, int msg, IntPtr
          wp, IntPtr lp);
   
         [DllImport("shell32", CharSet = CharSet.Auto)]
         internal extern static int SHGetMalloc(out IMalloc ppMalloc);

         internal const int WM_USER = 0x0400;

         internal const int BFFM_ENABLEOK = (WM_USER + 101);
         internal const int BFFM_SETSELECTIONA = (WM_USER + 102);
         internal const int BFFM_SETSELECTIONW = (WM_USER + 103);
         internal const int BFFM_SETSTATUSTEXTA = (WM_USER + 100);
         internal const int BFFM_SETSTATUSTEXTW = (WM_USER + 104);
         internal const int BFFM_SETOKTEXT = (WM_USER + 105);
         internal const int BFFM_SETEXPANDED = (WM_USER + 106);

         internal const int BFFM_INITIALIZED = 1;
         internal const int BFFM_SELCHANGED = 2;
         internal const int BFFM_VALIDATEFAILEDA = 3;
         internal const int BFFM_VALIDATEFAILEDW = 4;
         internal const int BFFM_IUNKNOWN = 5;

         internal const int WM_SYSCOMMAND = 0x112;
         internal const int SC_CLOSE = 0xF060;

         [DllImport("user32")]
         internal extern static int GetWindowRect(
            IntPtr hwnd, 
            ref RECT lpRect);

         [DllImport("user32")]
         internal extern static int SetWindowPos(
            IntPtr hwnd, 
            IntPtr hWndInsertAfter, 
            int x, 
            int y, 
            int cx, 
            int cy, 
            int wFlags);

         internal const int SWP_FRAMECHANGED = 0x20;        //  The frame
          changed: send WM_NCCALCSIZE
         internal const int SWP_HIDEWINDOW = 0x80;
         internal const int SWP_NOACTIVATE = 0x10;
         internal const int SWP_NOCOPYBITS = 0x100;
         internal const int SWP_NOMOVE = 0x2;
         internal const int SWP_NOOWNERZORDER = 0x200;      //  Don't do owner
          Z ordering
         internal const int SWP_NOREDRAW = 0x8;
         internal const int SWP_NOSIZE = 0x1;
         internal const int SWP_NOZORDER = 0x4;
         internal const int SWP_SHOWWINDOW = 0x40;
      }
      #endregion
      
      #region Private Enumerations
      /// <summary>
      /// Shell Folder Browser flags
      /// </summary>
      [Flags]
         private enum BrowseFlags : int
      {
         BIF_RETURNONLYFSDIRS   = 0x0001,
         BIF_DONTGOBELOWDOMAIN = 0x0002,
         BIF_STATUSTEXT = 0x0004,
         BIF_RETURNFSANCESTORS = 0x0008,
         BIF_EDITBOX = 0x0010,
         BIF_VALIDATE = 0x0020,
         BIF_NEWDIALOGSTYLE = 0x0040,
         BIF_BROWSEINCLUDEURLS = 0x0080,
         BIF_UAHINT = 0x0100,
         BIF_NONEWFOLDERBUTTON = 0x0200,
         BIF_BROWSEFORCOMPUTER = 0x1000,
         BIF_BROWSEFORPRINTER = 0x2000,
         BIF_BROWSEINCLUDEFILES = 0x4000,
         BIF_SHAREABLE = 0x8000,
      }
      #endregion

      #region Member Variables
      private BrowseFlags flags = BrowseFlags.BIF_NEWDIALOGSTYLE |
       BrowseFlags.BIF_RETURNFSANCESTORS;
      private string statusText = "";
      private bool displayed = false;   
      private bool okEnabled = true;
      private string okButtonText = "";
      private string selectedPath = "";
      private string title = "";
      private string rootPath = "";
      private string initialPath = "";
      private IntPtr handle = IntPtr.Zero;
      private string displayName = "";
      private SafePidl pidlReturned;
      private FolderBrowserFilter filter = null;
      #endregion

      #region Events

      /// <summary>
      /// Fired when the dialog is initialized
      /// </summary>
      public event EventHandler Initialized;
      
      /// <summary>
      /// Fired when selection changes
      /// </summary>
      public event SelectionChangedEventHandler SelectionChanged;

      /// <summary>
      /// Fired when a TextBox is shown and the user chooses
      /// OK but the Textbox contains an invalid filename or
      /// path.
      /// </summary>
      public event ValidationFailedEventHandler ValidationFailed;

      #endregion

      #region Properties
      /// <summary>
      /// Gets/sets the filter object which can be used to filter
      /// which items appear in the Folder Browser
      /// </summary>
      public FolderBrowserFilter Filter
      {
         get
         {
            return filter;
         }
         set
         {
            filter = value;
         }
      }

      /// <summary>
      /// Gets the handle of the Folder Browser dialog when it is
      /// displayed.
      /// </summary>
      public IntPtr Handle
      {
         get
         {
            return handle;
         }
      }

      /// <summary>
      /// Gets/Sets the initially selected folder for the dialog.
      /// </summary>
      public string InitialPath
      {
         get
         {
            return initialPath;
         }
         set
         {
            initialPath = value;
         }
      }

      /// <summary>
      /// Gets/sets the string that is displayed above the 
      /// tree view control in the dialog box.
      /// </summary>
      public string Title
      {
         get
         {
            return title;
         }
         set
         {
            title = value;
         }
      }

      /// <summary>
      /// Gets/sets the root path for the dialog.
      /// </summary>
      public string RootPath
      {
         get
         {
            return rootPath;
         }
         set
         {
            rootPath = value;
         }
      }


      private bool FlagSet(BrowseFlags flag)
      {
         return ((flags & flag) == flag);
      }
      private void FlagSet(BrowseFlags flag, bool value)
      {
         if (value)
         {
            flags |= flag;
         }
         else
         {
            flags &= ~flag;
         }
      }

      /// <summary>
      /// Gets/sets whether file system directories only are selectable. 
      /// If set, and the user selects a folder that is not part of the file
       system, 
      /// the OK button is grayed. 
      /// </summary>
      public bool FileSystemDirectoriesOnly
      {
         get
         {
            return FlagSet(BrowseFlags.BIF_RETURNONLYFSDIRS);
         }
         set
         {
            FlagSet(BrowseFlags.BIF_RETURNONLYFSDIRS, value);
         }
      }

      /// <summary>
      /// Gets/sets whether network folders below the domain level 
      /// are shown in the dialog box's tree view control. 
      /// </summary>
      public bool ShowDomainNetworkFolders
      {
         get
         {
            return (FlagSet(BrowseFlags.BIF_DONTGOBELOWDOMAIN) ? false : true);
         }
         set
         {
            FlagSet(BrowseFlags.BIF_DONTGOBELOWDOMAIN, (value ? false : true));
         }
      }

      /// <summary>
      /// Gets/sets whether a status area is included in the dialog box. 
      /// The status text can be set using the StatusText property.
      /// </summary>
      public bool ShowStatusText
      {
         get
         {
            return FlagSet(BrowseFlags.BIF_STATUSTEXT);
         }
         set
         {
            FlagSet(BrowseFlags.BIF_STATUSTEXT, value);
         }
      }

      /// <summary>
      /// Gets/sets the status text shown in the status area.  See
      /// also ShowStatusText.
      /// </summary>
      public string StatusText
      {
         get
         {
            return statusText;
         }
         set
         {
            statusText = value;
            if (displayed)
            {
               setStatusText();
            }
         }
      }

      private void setStatusText()
      {
         int msg = (Environment.OSVersion.Platform == PlatformID.Win32NT) ? 
            UnManagedMethods.BFFM_SETSTATUSTEXTW : 
            UnManagedMethods.BFFM_SETSTATUSTEXTA;
         IntPtr strptr = Marshal.StringToHGlobalAuto(statusText);

         UnManagedMethods.SendMessage(
            handle, 
            msg, 
            IntPtr.Zero, 
            strptr);
         
         Marshal.FreeHGlobal(strptr);

      }

      /// <summary>
      /// Gets/sets whether file system ancestors only can be selected. 
      /// An ancestor is a subfolder that is beneath the root folder in the 
      /// namespace hierarchy. If set, and the user selects an ancestor of the
       root 
      /// folder that is not part of the file system, the OK button is grayed.
      /// </summary>
      public bool FileSystemAncestorsOnly
      {
         get
         {
            return FlagSet(BrowseFlags.BIF_RETURNFSANCESTORS);
         }
         set
         {
            FlagSet(BrowseFlags.BIF_RETURNFSANCESTORS, value);
         }
      }

      /// <summary>
      /// Gets/sets whether to include an edit control in the browse dialog 
      /// box that allows the user to type the name of an item.  Requires
      /// IE4.0 or above.
      /// </summary>
      public bool ShowEditBox
      {
         get
         {
            return FlagSet(BrowseFlags.BIF_EDITBOX);
         }
         set
         {
            FlagSet(BrowseFlags.BIF_EDITBOX, value);
         }
      }
      
      /// <summary>
      /// Gets/sets whether the ValidateFailedEvent is called if the user has
       typed an 
      /// invalid name into the edit box.  Ignored if no EditBox.
      /// </summary>
      public bool ValidateEditBox
      {
         get
         {
            return FlagSet(BrowseFlags.BIF_VALIDATE);
         }
         set
         {
            FlagSet(BrowseFlags.BIF_VALIDATE, value);
         }
      }

      /// <summary>
      /// Gets/sets whether the new FolderBrowser user interface should be
       used. 
      /// 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. 
      /// </summary>
      public bool NewDialogStyle
      {
         get
         {
            return FlagSet(BrowseFlags.BIF_NEWDIALOGSTYLE);
         }
         set
         {
            FlagSet(BrowseFlags.BIF_NEWDIALOGSTYLE, value);
         }

      }

      /// <summary>
      /// Gets/sets whether to show URLs in folders which support browsing 
      /// for URLs.
      /// </summary>
      public bool IncludeUrls
      {
         get
         {
            return FlagSet(BrowseFlags.BIF_BROWSEINCLUDEURLS);
         }
         set
         {
            FlagSet(BrowseFlags.BIF_BROWSEINCLUDEURLS, value);
         }
      }

      /// <summary>
      /// Gets/sets whether a usage hint will be shown in the dialog 
      /// box in place of the edit box. ShowEditBox overrides this flag. 
      /// Only valid when NewDialogStyle is set.
      /// </summary>
      public bool ShowUsageHint
      {
         get
         {
            return FlagSet(BrowseFlags.BIF_UAHINT);
         }
         set
         {
            FlagSet(BrowseFlags.BIF_UAHINT, value);
         }
      }

      /// <summary>
      /// Do not include the "New Folder" button in the browse dialog box. 
      /// </summary>
      public bool NoNewFolderButton
      {
         get
         {
            return FlagSet(BrowseFlags.BIF_NONEWFOLDERBUTTON);
         }
         set
         {
            FlagSet(BrowseFlags.BIF_NONEWFOLDERBUTTON, value);
         }
      }

      /// <summary>
      /// Gets/sets whether the dialog only return computers. When set, if the 
      /// user selects anything other than  a computer, the OK button is grayed.
      /// </summary>
      public bool BrowseForComputer
      {
         get
         {
            return FlagSet(BrowseFlags.BIF_BROWSEFORCOMPUTER);
         }
         set
         {
            FlagSet(BrowseFlags.BIF_BROWSEFORCOMPUTER, value);
         }
      }

      /// <summary>
      /// Gets/sets whether the dialog only returns printers. When set, if the
      /// user selects anything other than a printer, the OK button is grayed.
      /// </summary>
      public bool BrowseForPrinter
      {
         get
         {
            return FlagSet(BrowseFlags.BIF_BROWSEFORPRINTER);
         }
         set
         {
            FlagSet(BrowseFlags.BIF_BROWSEFORPRINTER, value);
         }
      }

      /// <summary>
      /// Gets/sets whether the browse dialog box will display files 
      /// as well as folders. 
      /// </summary>
      public bool IncludeFiles
      {
         get
         {
            return FlagSet(BrowseFlags.BIF_BROWSEFORPRINTER);
         }
         set
         {
            FlagSet(BrowseFlags.BIF_BROWSEFORPRINTER, value);
         }
      }

      /// <summary>
      /// Gets/sets whether the browse dialog box displays shareable resources 
      /// on remote systems. It is intended for applications that want to
       expose 
      /// remote shares on a local system. NewDialogStyle must also be set.
      /// </summary>
      public bool ShowRemoteShares
      {
         get
         {
            return FlagSet(BrowseFlags.BIF_SHAREABLE);
         }
         set
         {
            FlagSet(BrowseFlags.BIF_SHAREABLE, value);
         }
      }

      /// <summary>
      /// Gets/sets whether the OK button is enabled or not.
      /// </summary>
      public bool OkButtonEnabled
      {
         get
         {
            return okEnabled;
         }
         set
         {
            okEnabled = value;
            if (displayed)
            {
               setEnableOkButton();
            }
         }
      }

      private void setEnableOkButton()
      {
         IntPtr lp = (okEnabled ? new IntPtr(1) : IntPtr.Zero);
         
         UnManagedMethods.SendMessage(
            handle, 
            UnManagedMethods.BFFM_ENABLEOK, 
            IntPtr.Zero, 
            lp);
      }

      /// <summary>
      /// Gets/sets the selected path
      /// </summary>
      public string SelectedPath
      {
         get
         {
            return selectedPath;
         }
         set
         {
            selectedPath = value;
            if (displayed)
            {
               setSelectedPath();
            }
         }
      }

      private void setSelectedPath()
      {
         int msg = (Environment.OSVersion.Platform == PlatformID.Win32NT) ? 
            UnManagedMethods.BFFM_SETSELECTIONA : 
            UnManagedMethods.BFFM_SETSELECTIONW;
         
         IntPtr strptr = Marshal.StringToHGlobalAuto(selectedPath);

         UnManagedMethods.SendMessage(
            handle, 
            msg, 
            new IntPtr(1), 
            strptr);
         
         Marshal.FreeHGlobal(strptr);
      }

      /// <summary>
      /// Gets/sets the text on the OK Button in the dialog.
      /// </summary>
      public string OkButtonText
      {
         get
         {
            return okButtonText;
         }
         set
         {
            okButtonText = value;
            setOkButtonText();
         }
      }

      private void setOkButtonText()
      {      
         IntPtr strptr = Marshal.StringToHGlobalUni(okButtonText);

         UnManagedMethods.SendMessage(
            handle, 
            UnManagedMethods.BFFM_SETOKTEXT, 
            new IntPtr(1), 
            strptr);
         
         Marshal.FreeHGlobal(strptr);
      }
      #endregion

      #region BrowseCallback
      private int BrowseCallbackProc(
         IntPtr hwnd, 
         int msg, 
         IntPtr lParam, 
         IntPtr lpData
         )
      {
         int ret = 0;

         switch(msg)
         {
            case UnManagedMethods.BFFM_INITIALIZED:
               handle = hwnd;
               if (lpData != IntPtr.Zero)
               {   // lpData contains the PIDF:
                  int selMsg = (Environment.OSVersion.Platform ==
                   PlatformID.Win32NT) ? 
                     UnManagedMethods.BFFM_SETSELECTIONA : 
                     UnManagedMethods.BFFM_SETSELECTIONW;
                  UnManagedMethods.SendMessage(
                     hwnd, 
                     selMsg, 
                     System.IntPtr.Zero, 
                     lpData);
               }
               if (okButtonText != null)
               {
                  if (okButtonText.Trim().Length > 0)
                  {
                     setOkButtonText();
                  }
               }
               if (statusText != null)
               {
                  if (statusText.Trim().Length > 0)
                  {
                     setStatusText();
                  }
               }
               if (Initialized != null)
               {
                  Initialized(this, null);
               }
               break;

            case UnManagedMethods.BFFM_IUNKNOWN:
               // IFolderFilter only available under XP:               
               if ((filter != null) && (isXpOrAbove()))
               {
                  // Check whether the user has asked to apply a filter:
                  if (filter.ApplyFilter)
                  {
                     // connect the filter:
                     if (lParam != IntPtr.Zero)
                     {
                        // lParam is the IUnknown interface:
                        Guid iidFolderFilterSite = new Guid(
                           "C0A651F5-B48B-11d2-B5ED-006097C686F6"); //
                            IID_IFolderFilterSite
                        IntPtr ptrFolderFilterSite = IntPtr.Zero;
                        Marshal.QueryInterface(
                           lParam,
                           ref iidFolderFilterSite,
                           out ptrFolderFilterSite);

                        if (ptrFolderFilterSite != IntPtr.Zero)
                        {
                           IFolderFilterSite folderFilterSite = 
                              (IFolderFilterSite)Marshal.GetTypedObjectForIUnkno
                              wn(
                              ptrFolderFilterSite,
                              System.Type.GetType("IFolderFilterSite"));
                           
                           BrowseItemFilter itemFilter = new
                            BrowseItemFilter(filter);

                           folderFilterSite.SetFilter(itemFilter);
                        }
                     }
                  }
               }
               break;

            case UnManagedMethods.BFFM_SELCHANGED:
               SafePidl pidl = new SafePidl(lParam, false);
               selectedPath = pidl.Path;
               if (SelectionChanged != null)
               {
                  SelectionChangedEventArgs e = new
                   SelectionChangedEventArgs(pidl);
                  SelectionChanged(this, e);                  
               }
               break;

            case UnManagedMethods.BFFM_VALIDATEFAILEDA:
               if (ValidationFailed != null)
               {
                  if (Environment.OSVersion.Platform != PlatformID.Win32NT)
                  {
                     // platform SDK incorrectly states that the message
                     // is contained in lpData - it is in lParam
                     ValidationFailedEventArgs e = new
                      ValidationFailedEventArgs(
                        Marshal.PtrToStringAnsi(lParam));
                     ValidationFailed(this, e);
                  
                     ret = (e.Cancel ? 0 : 1);
                  }
               }
               break;

            case UnManagedMethods.BFFM_VALIDATEFAILEDW:
               if (ValidationFailed != null)
               {
                  if (Environment.OSVersion.Platform == PlatformID.Win32NT)
                  {
                     // platform SDK incorrectly states that the message
                     // is contained in lpData - it is in lParam
                     ValidationFailedEventArgs e = new
                      ValidationFailedEventArgs(
                        Marshal.PtrToStringUni(lParam));
                     ValidationFailed(this, e);
                  
                     ret = (e.Cancel ? 0 : 1);
                  }
               }
               break;               
         }

         return ret;
      }
      #endregion BrowseCallback

      #region Methods
      /// <summary>
      /// Shows the dialog
      /// </summary>
      /// <param name="owner">The window to use as the owner</param>
      /// <returns></returns>
      public DialogResult ShowDialog(System.Windows.Forms.IWin32Window owner)
      {
         if (handle != IntPtr.Zero)
            throw new InvalidOperationException();

         BrowseInfo bi = new BrowseInfo();
         
         if (owner != null)
            bi.hwndOwner = owner.Handle;

         return showDialog(ref bi);
      }

      /// <summary>
      /// Shows the dialog using active window as the owner
      /// </summary>
      public DialogResult ShowDialog()
      {
         return ShowDialog(Form.ActiveForm);
      }

      /// <summary>
      /// Closes the folder browser dialog (if it is displayed)
      /// </summary>
      public void CloseDialog()
      {
         if (handle != IntPtr.Zero)
         {
            UnManagedMethods.SendMessage(
               handle, 
               UnManagedMethods.WM_SYSCOMMAND, 
               (IntPtr)UnManagedMethods.SC_CLOSE, 
               IntPtr.Zero);
            handle = IntPtr.Zero;
         }
      }

      private DialogResult showDialog(ref BrowseInfo bi)
      {
         DialogResult dialogResult = DialogResult.Cancel;
         
         // Initialise the BrowseInfo structure:
         SafePidl pidlRoot = new SafePidl(rootPath);
         SafePidl pidlInitial = new SafePidl(initialPath);
                  
         bi.title = title;
         bi.displayname = new string('\0', 260);
         bi.callback = new BrowseCallBackProc(this.BrowseCallbackProc);
         bi.flags = (int)flags;
         bi.pidlRoot = pidlRoot.Pidl;
         bi.lparam = pidlInitial.Pidl;

         // Do the folder browsing:
         IntPtr pidl = UnManagedMethods.SHBrowseForFolder(ref bi);
         if (pidl != IntPtr.Zero)
         {
            pidlReturned = new SafePidl(pidl, true);
            displayName = bi.displayname;
            dialogResult = DialogResult.OK;
         }

         //Reset the handle
         handle = IntPtr.Zero;

         // Clear up:
         pidlRoot.Dispose();
         pidlInitial.Dispose();

         return dialogResult;
      }

      /// <summary>
      /// Gets/sets the dialog position when it is shown
      /// </summary>
      public System.Drawing.Point Position
      {
         get
         {
            System.Drawing.Point pos = new System.Drawing.Point(0, 0);
            if (this.handle != IntPtr.Zero)
            {
               RECT rcDialog = new RECT();
               UnManagedMethods.GetWindowRect(this.handle, ref rcDialog);
               pos.X = rcDialog.left;
               pos.Y = rcDialog.top;
            }
            return pos;
         }
         set
         {
            if (this.handle != IntPtr.Zero)
            {
               UnManagedMethods.SetWindowPos(
                  this.handle,
                  IntPtr.Zero,
                  value.X,
                  value.Y,
                  0,
                  0,
                  UnManagedMethods.SWP_NOSIZE |
                   UnManagedMethods.SWP_NOOWNERZORDER |
                  UnManagedMethods.SWP_NOZORDER);
               }
         }
      }
         
      /// <summary>
      /// Gets/sets the size of the dialog
      /// </summary>
      public System.Drawing.Size Size
      {
         get
         {
            System.Drawing.Size size = new System.Drawing.Size(0, 0);
            if (this.Handle != IntPtr.Zero)
            {
               RECT rcDialog = new RECT();
               UnManagedMethods.GetWindowRect(this.handle, ref rcDialog);
               size.Width = rcDialog.right - rcDialog.left;
               size.Height = rcDialog.bottom - rcDialog.top;
            }
            return size;
         }
         set
         {
            if (this.handle != IntPtr.Zero)
            {
               UnManagedMethods.SetWindowPos(
                  this.handle,
                  IntPtr.Zero,
                  0,
                  0,
                  value.Width,
                  value.Height,
                  UnManagedMethods.SWP_NOMOVE |
                   UnManagedMethods.SWP_NOOWNERZORDER |
                  UnManagedMethods.SWP_NOZORDER);
               }
         }
      }
      
      private bool isXpOrAbove()
      {
         bool ret = false;
         if (Environment.OSVersion.Version.Major > 5)
         {
            ret = true;
         }
         else if ((Environment.OSVersion.Version.Major == 5) &&
            (Environment.OSVersion.Version.Minor >= 1))
         {
            ret = true;
         }
         return ret;
                                           

      }
      #endregion Methods

      #region Constructor and Destructor
      /// <summary>
      /// Releases any objects associated with the 
      /// FolderBrowser.
      /// </summary>
      public void Dispose()
      {
         Dispose(true);
         GC.SuppressFinalize(this);
      }
      /// <summary>
      /// Releases any objects associated with the 
      /// FolderBrowser when disposing is true.
      /// </summary>
      /// <param name="disposing">Whether disposing or not</param>
      protected virtual void Dispose(bool disposing)
      {
         if (disposing)
         {
            if (pidlReturned != null)
            {
               pidlReturned.Dispose();
               pidlReturned = null;
            }
            Allocator a = new Allocator();
            a.Dispose();
            a = null;
         }
      }

      /// <summary>
      /// Constructs a new instance of the FolderBrowser
      /// class
      /// </summary>
      public FolderBrowser()
      {
         filter = new FolderBrowserFilter();
      }
      #endregion
   }
   #endregion
}