Click here to Skip to main content
Click here to Skip to main content
Add your own
alternative version

C# does Shell, Part 4

, 26 Mar 2003 Ms-PL
This article is about the AutoComplete features enabled by windows and how to use them with C#. AutoComplete is the ability to expand strings written in an edit box. The article will develop a class for using this functionality in you applications.
using System;
using System.Runtime.InteropServices;

namespace ShellLib
{
	public class ShellBrowseForFolderDialog
	{
		// The SendMessage function sends the specified message to a window or windows. It calls the window procedure for 
		// the specified window and does not return until the window procedure has processed the message. 
		[DllImport("User32.dll")]
		public static extern Int32 SendMessage(
			IntPtr hWnd,			// handle to destination window
			UInt32 Msg,				// message
			UInt32 wParam,			// first message parameter
			Int32 lParam			// second message parameter
			);

		[DllImport("User32.dll")]
		public static extern Int32 SendMessage(
			IntPtr hWnd,			// handle to destination window
			UInt32 Msg,				// message
			UInt32 wParam,			// first message parameter
			[MarshalAs(UnmanagedType.LPWStr)]
			String lParam			// second message parameter
			);

		public enum RootTypeOptions
		{
			BySpecialFolder,
			ByPath
		}

		[Flags]
		public enum BrowseInfoFlag // BIF
		{
			BIF_RETURNONLYFSDIRS   = 0x0001,	// For finding a folder to start document searching
			BIF_DONTGOBELOWDOMAIN  = 0x0002,	// For starting the Find Computer
			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.
			BIF_RETURNFSANCESTORS  = 0x0008,
			BIF_EDITBOX            = 0x0010,	// Add an editbox to the dialog
			BIF_VALIDATE           = 0x0020,	// insist on valid result (or CANCEL)
			BIF_NEWDIALOGSTYLE     = 0x0040,	// Use the new dialog layout with the ability to resize
			// Caller needs to call OleInitialize() before using this API
			BIF_USENEWUI           = (BIF_NEWDIALOGSTYLE | BIF_EDITBOX),
			BIF_BROWSEINCLUDEURLS  = 0x0080,    // Allow URLs to be displayed or entered. (Requires BIF_USENEWUI)
			BIF_UAHINT             = 0x0100,    // Add a UA hint to the dialog, in place of the edit box. May not be 
			// combined with BIF_EDITBOX
			BIF_NONEWFOLDERBUTTON  = 0x0200,    // Do not add the "New Folder" button to the dialog.  Only applicable 
			// with BIF_NEWDIALOGSTYLE.
			BIF_NOTRANSLATETARGETS = 0x0400,    // don't traverse target as shortcut
			BIF_BROWSEFORCOMPUTER  = 0x1000,	// Browsing for Computers.
			BIF_BROWSEFORPRINTER   = 0x2000,	// Browsing for Printers
			BIF_BROWSEINCLUDEFILES = 0x4000,	// Browsing for Everything
			BIF_SHAREABLE          = 0x8000		// sharable resources displayed (remote shares, requires BIF_USENEWUI)
		}

		public enum	BrowseForFolderMessages	// BFFM
		{
			// message from browser
			BFFM_INITIALIZED        = 1,
			BFFM_SELCHANGED         = 2,
			BFFM_VALIDATEFAILEDA    = 3,				// lParam:szPath ret:1(cont),0(EndDialog)
			BFFM_VALIDATEFAILEDW    = 4,				// lParam:wzPath ret:1(cont),0(EndDialog)
			BFFM_IUNKNOWN           = 5, 				// provides IUnknown to client. lParam: IUnknown*

			// messages to browser
			// 0x400 = WM_USER
			BFFM_SETSTATUSTEXTA     = (0x0400 + 100),
			BFFM_ENABLEOK           = (0x0400 + 101),
			BFFM_SETSELECTIONA      = (0x0400 + 102),
			BFFM_SETSELECTIONW      = (0x0400 + 103),
			BFFM_SETSTATUSTEXTW     = (0x0400 + 104),
			BFFM_SETOKTEXT          = (0x0400 + 105),	// Unicode only
			BFFM_SETEXPANDED        = (0x0400 + 106)	// Unicode only
		}

		public class InitializedEventArgs : EventArgs
		{
			public InitializedEventArgs(IntPtr hwnd)
			{
				this.hwnd = hwnd;
			}
			public readonly IntPtr hwnd;
		}

		public class IUnknownEventArgs : EventArgs
		{
			public IUnknownEventArgs(IntPtr hwnd, IntPtr iunknown)
			{
				this.hwnd = hwnd;
				this.iunknown = iunknown;
			}
			public readonly IntPtr hwnd;
			public readonly IntPtr iunknown;
		}

		public class SelChangedEventArgs : EventArgs
		{
			public SelChangedEventArgs(IntPtr hwnd, IntPtr pidl)
			{
				this.hwnd = hwnd;
				this.pidl = pidl;
			}
			public readonly IntPtr hwnd;
			public readonly IntPtr pidl;
		}

		public class ValidateFailedEventArgs : EventArgs
		{
			public ValidateFailedEventArgs(IntPtr hwnd, string invalidSel)
			{
				this.hwnd = hwnd;
				this.invalidSel = invalidSel;
			}
			public readonly IntPtr hwnd;
			public readonly string invalidSel;
		}
		
		
		public delegate void InitializedHandler(ShellBrowseForFolderDialog sender, InitializedEventArgs args);
		public delegate void IUnknownHandler(ShellBrowseForFolderDialog sender, IUnknownEventArgs args);
		public delegate void SelChangedHandler(ShellBrowseForFolderDialog sender, SelChangedEventArgs args);
		public delegate int ValidateFailedHandler(ShellBrowseForFolderDialog sender, ValidateFailedEventArgs args);  

		public event InitializedHandler OnInitialized;
		public event IUnknownHandler OnIUnknown;
		public event SelChangedHandler OnSelChanged;
		public event ValidateFailedHandler OnValidateFailed;

		public void EnableOk(IntPtr hwnd, bool Enabled)
		{
			SendMessage(hwnd, (uint)BrowseForFolderMessages.BFFM_ENABLEOK, 0, Enabled ? 1 : 0); 
		}

		public void SetExpanded(IntPtr hwnd, string path)
		{
			SendMessage(hwnd, (uint)BrowseForFolderMessages.BFFM_SETEXPANDED, 1, path);
		}

		public void SetOkText(IntPtr hwnd, string text)
		{
			SendMessage(hwnd, (uint)BrowseForFolderMessages.BFFM_SETOKTEXT, 0, text);
		}

		public void SetSelection(IntPtr hwnd, string path)
		{
			SendMessage(hwnd, (uint)BrowseForFolderMessages.BFFM_SETSELECTIONW, 1, path);
		}

		public void SetStatusText(IntPtr hwnd, string text)
		{
			SendMessage(hwnd, (uint)BrowseForFolderMessages.BFFM_SETSTATUSTEXTW, 1, text);
		}


		private Int32 myBrowseCallbackProc(IntPtr hwnd, UInt32 uMsg, Int32 lParam, Int32 lpData)
		{
			switch ((BrowseForFolderMessages)uMsg)
			{
				case BrowseForFolderMessages.BFFM_INITIALIZED:
					System.Diagnostics.Debug.WriteLine("BFFM_INITIALIZED");

					if (OnInitialized != null)
					{
						InitializedEventArgs args = new InitializedEventArgs(hwnd);
						OnInitialized(this,args);
					}

					break;

				case BrowseForFolderMessages.BFFM_IUNKNOWN:
					System.Diagnostics.Debug.WriteLine("BFFM_IUNKNOWN");

					if (OnIUnknown != null)
					{
						IUnknownEventArgs args = new IUnknownEventArgs(hwnd,(IntPtr)lParam);
						OnIUnknown(this,args);
					}

					break;

				case BrowseForFolderMessages.BFFM_SELCHANGED:
					System.Diagnostics.Debug.WriteLine("BFFM_SELCHANGED");
					
					if (OnSelChanged != null)
					{
						SelChangedEventArgs args = new SelChangedEventArgs(hwnd,(IntPtr)lParam);
						OnSelChanged(this,args);
					}
				
					break;
				
				case BrowseForFolderMessages.BFFM_VALIDATEFAILEDA:
					System.Diagnostics.Debug.WriteLine("BFFM_VALIDATEFAILEDA");
				
					if (OnValidateFailed != null)
					{
						string failedSel = Marshal.PtrToStringAnsi((IntPtr)lParam);
						ValidateFailedEventArgs args = new ValidateFailedEventArgs(hwnd,failedSel);
						return OnValidateFailed(this,args);
					}
					break;

				case BrowseForFolderMessages.BFFM_VALIDATEFAILEDW:
					System.Diagnostics.Debug.WriteLine("BFFM_VALIDATEFAILEDW");
					
					if (OnValidateFailed != null)
					{
						string failedSel = Marshal.PtrToStringUni((IntPtr)lParam);
						ValidateFailedEventArgs args = new ValidateFailedEventArgs(hwnd,failedSel);
						return OnValidateFailed(this,args);
					}
										
					break;
			}

			return 0;
		}


		public ShellBrowseForFolderDialog()
		{
			hwndOwner = IntPtr.Zero;
			RootType = RootTypeOptions.BySpecialFolder;
			RootSpecialFolder = ShellApi.CSIDL.CSIDL_DESKTOP;
			RootPath = "";
			m_DisplayName = "";
			Title = "";
			UserToken = IntPtr.Zero;
			m_FullName = "";

			// Default flags values
			DetailsFlags = BrowseInfoFlag.BIF_BROWSEINCLUDEFILES 
				| BrowseInfoFlag.BIF_EDITBOX 
				| BrowseInfoFlag.BIF_NEWDIALOGSTYLE 
				| BrowseInfoFlag.BIF_SHAREABLE
				| BrowseInfoFlag.BIF_STATUSTEXT
				| BrowseInfoFlag.BIF_USENEWUI
				| BrowseInfoFlag.BIF_VALIDATE;

		}

		public void ShowDialog()
		{
			m_FullName = "";
			m_DisplayName = "";

			// Get shell's memory allocator, it is needed to free some memory later
			IMalloc pMalloc;
			pMalloc = ShellFunctions.GetMalloc();
		
			IntPtr pidlRoot;
			
			if (RootType == RootTypeOptions.BySpecialFolder)
			{
				ShellApi.SHGetFolderLocation(hwndOwner,(int)RootSpecialFolder,UserToken,0,out pidlRoot);
			}
			else	// m_RootType = RootTypeOptions.ByPath
			{
				uint iAttribute;
				ShellApi.SHParseDisplayName(RootPath,IntPtr.Zero,out pidlRoot,0,out iAttribute);
			}
									
			ShellApi.BROWSEINFO bi = new ShellApi.BROWSEINFO();
			
			bi.hwndOwner = hwndOwner;
			bi.pidlRoot = pidlRoot;
			bi.pszDisplayName = new String(' ',256);
			bi.lpszTitle = Title;
			bi.ulFlags = (uint)DetailsFlags; 
			bi.lParam = 0;
			bi.lpfn = new ShellApi.BrowseCallbackProc(this.myBrowseCallbackProc);
			
			// Show dialog
			IntPtr pidlSelected;
			pidlSelected = ShellLib.ShellApi.SHBrowseForFolder(ref bi);

			// Save the display name
			m_DisplayName = bi.pszDisplayName.ToString();

			IShellFolder isf = ShellFunctions.GetDesktopFolder();

			ShellApi.STRRET ptrDisplayName;
			isf.GetDisplayNameOf(pidlSelected,(uint)ShellApi.SHGNO.SHGDN_NORMAL | (uint)ShellApi.SHGNO.SHGDN_FORPARSING,out ptrDisplayName);
			
			String sDisplay;
			ShellLib.ShellApi.StrRetToBSTR(ref ptrDisplayName,pidlRoot,out sDisplay);
			m_FullName = sDisplay;

			if (pidlRoot != IntPtr.Zero)
				pMalloc.Free(pidlRoot);
			
			if (pidlSelected != IntPtr.Zero)
				pMalloc.Free(pidlSelected);
			
			Marshal.ReleaseComObject(isf);
			Marshal.ReleaseComObject(pMalloc);
		}


		///  <summary> Handle to the owner window for the dialog box.  </summary>
		public IntPtr hwndOwner;

		/// <summary> Select the root type </summary>
		public RootTypeOptions RootType;

		/// <summary> valid only if RootType is RootTypeOptions.ByPath </summary>
		public string RootPath;

		/// <summary> valid only if RootType is RootTypeOptions.BySpecialFolder </summary>
		public ShellApi.CSIDL RootSpecialFolder;

		/// <summary> Address of a buffer to receive the display name of the folder selected by the user. </summary>
		public string DisplayName
		{
			get
			{
				return m_DisplayName;
			}
		}
		private string m_DisplayName;

		/// <summary> 
		/// Address of a null-terminated string that is displayed above the tree view control in the dialog box. 
		/// </summary>
		public string Title;

		/// <summary> Token that can be used to represent a particular user. </summary>
		public IntPtr UserToken;

		/// <summary> Return the result of the dialog </summary>
		public string FullName
		{
			get
			{
				return m_FullName;
			}
		}
		private string m_FullName;

		public BrowseInfoFlag DetailsFlags;

	}

}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)

Share

About the Author

Arik Poznanski
Software Developer (Senior) Verint
Israel Israel
Arik Poznanski is a senior software developer at Verint. He completed two B.Sc. degrees in Mathematics & Computer Science, summa cum laude, from the Technion in Israel.
 
Arik has extensive knowledge and experience in many Microsoft technologies, including .NET with C#, WPF, Silverlight, WinForms, Interop, COM/ATL programming, C++ Win32 programming and reverse engineering (assembly, IL).
Follow on   Twitter   Google+

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.141223.1 | Last Updated 27 Mar 2003
Article Copyright 2003 by Arik Poznanski
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid