CODEGATOR

.NET info worth sinking your teeth into!
Welcome to CODEGATOR Sign in | Join | Help
in Search

Martin Cook

Yet another C# developer with a blog.

A managed wrapper for the IShellFolder interface

Yesterday I published my wrapper for the Windows Shell PIDL structure. Today I thought I might publish my wrapper for the IShellFolder COM interface. This class works hand-in-hand with yesterday's PIDL class, so it makes sense to publish all the code together.

The history for this class is similar to my PIDL wrapper: I originally wrote it in ANSI C++ in the mid-to-late 1990's. I ported the code to C# in 2002/2003. I have perodically gone back and tweaked the resulting .NET code over time. The version I am publishing here will work with .NET 1.x, 2.x or 3.x.

The class has several constructors and open methods for creating folder objects. You can use an existing PIDL, a path to a shell object, or one of the enumerated entries in Environment.SpecialFolder to describe the object you want to get a folder for. Of course, once you have a folder object you can do anything with it that you would be able to do with a raw IShellFolder interface reference.

Oh yea, I also included a method for iterating through a collection of child objects. So, once you get a folder object, say for the "C" drive, you could iterate through all the child files and folders for that drive. Of course, the iteration also works with any other kind of Shell object that is capable of containing children.

 

Once again, this code uses .NET's PInvoke mechanism to interface with the Windows Shell. Because of that, there is a bunch of COM interface and structure definitions in the listing. It's really ugly but there isn't anything I can do about it. Whenever you start working with COM interfaces in .NET you have to put up with all the PInvoke ugliness. Sorry. I have isolated all the PInvoke stuff into a nested class called NativeMethods, hopefully that helps.

 

Here is the code listing for the class:

 

[ComVisible(false)]
public sealed class CGFolder : IDisposable
{

	private static class NativeMethods
	{

		public static Guid IID_IShellFolder = new Guid(
			"{000214E6-0000-0000-C000-000000000046}"
			);

		[DllImport("shell32.dll")]
		public static extern Int32 SHGetDesktopFolder(
			out IShellFolder ppshf
			);

		public enum SHCONTF
		{
			SHCONTF_FOLDERS = 0x0020,
			SHCONTF_NONFOLDERS = 0x0040,
			SHCONTF_INCLUDEHIDDEN = 0x0080,
			SHCONTF_INIT_ON_FIRST_NEXT = 0x0100,
			SHCONTF_NETPRINTERSRCH = 0x0200,
			SHCONTF_SHAREABLE = 0x0400,
			SHCONTF_STORAGE = 0x0800
		} 

		[Flags]
		public enum SHCIDS : uint
		{
			SHCIDS_ALLFIELDS = 0x80000000,
			SHCIDS_CANONICALONLY = 0x10000000,
			SHCIDS_BITMASK = 0xFFFF0000,
			SHCIDS_COLUMNMASK = 0x0000FFFF
		} 

		[Flags]
		public enum FOLDERFLAGS
		{
			FWF_AUTOARRANGE = 0x1,
			FWF_ABBREVIATEDNAMES = 0x2,
			FWF_SNAPTOGRID = 0x4,
			FWF_OWNERDATA = 0x8,
			FWF_BESTFITWINDOW = 0x10,
			FWF_DESKTOP = 0x20,
			FWF_SINGLESEL = 0x40,
			FWF_NOSUBFOLDERS = 0x80,
			FWF_TRANSPARENT = 0x100,
			FWF_NOCLIENTEDGE = 0x200,
			FWF_NOSCROLL = 0x400,
			FWF_ALIGNLEFT = 0x800,
			FWF_NOICONS = 0x1000,
			FWF_SHOWSELALWAYS = 0x2000,
			FWF_NOVISIBLE = 0x4000,
			FWF_SINGLECLICKACTIVATE = 0x8000,
			FWF_NOWEBVIEW = 0x10000,
			FWF_HIDEFILENAMES = 0x20000,
			FWF_CHECKSELECT = 0x40000
		} 
		
		public enum FOLDERVIEWMODE
		{
			FVM_FIRST = 1,
			FVM_ICON = 1,
			FVM_SMALLICON = 2,
			FVM_LIST = 3,
			FVM_DETAILS = 4,
			FVM_THUMBNAIL = 5,
			FVM_TILE = 6,
			FVM_THUMBSTRIP = 7,
			FVM_LAST = 7
		}

		[StructLayout(LayoutKind.Explicit)]
		public struct STRRET
		{
			[FieldOffset(0)]
			UInt32 uType;
			[FieldOffset(4)]
			IntPtr pOleStr;
			[FieldOffset(4)]
			IntPtr pStr;
			[FieldOffset(4)]
			UInt32 uOffset;
			[FieldOffset(4)]
			IntPtr cStr;
		}

		[Flags()]
		public enum SFGAO : uint
		{
			SFGAO_CANCOPY = 0x000000001,
			SFGAO_CANMOVE = 0x000000002,
			SFGAO_CANLINK = 0x000000004,
			SFGAO_STORAGE = 0x000000008,
			SFGAO_CANRENAME = 0x00000010,
			SFGAO_CANDELETE = 0x00000020,
			SFGAO_HASPROPSHEET = 0x00000040,
			SFGAO_DROPTARGET = 0x00000100,
			SFGAO_CAPABILITYMASK = 0x00000177,
			SFGAO_ENCRYPTED = 0x00002000,
			SFGAO_ISSLOW = 0x00004000,
			SFGAO_GHOSTED = 0x00008000,
			SFGAO_LINK = 0x00010000,
			SFGAO_SHARE = 0x00020000,
			SFGAO_READONLY = 0x00040000,
			SFGAO_HIDDEN = 0x00080000,
			SFGAO_DISPLAYATTRMASK = 0x000FC000,
			SFGAO_FILESYSANCESTOR = 0x10000000,
			SFGAO_FOLDER = 0x20000000,
			SFGAO_FILESYSTEM = 0x40000000,
			SFGAO_HASSUBFOLDER = 0x80000000,
			SFGAO_CONTENTSMASK = 0x80000000,
			SFGAO_VALIDATE = 0x01000000,
			SFGAO_REMOVABLE = 0x02000000,
			SFGAO_COMPRESSED = 0x04000000,
			SFGAO_BROWSABLE = 0x08000000,
			SFGAO_NONENUMERATED = 0x00100000,
			SFGAO_NEWCONTENT = 0x00200000,
			SFGAO_CANMONIKER = 0x00400000,
			SFGAO_HASSTORAGE = 0x00400000,
			SFGAO_STREAM = 0x00400000,
			SFGAO_STORAGEANCESTOR = 0x00800000,
			SFGAO_STORAGECAPMASK = 0x70C50008
		}

		[Flags()]
		public enum SHGDN
		{
			SHGDN_NORMAL = 0,
			SHGDN_INFOLDER = 1,
			SHGDN_FORADDRESSBAR = 16384,
			SHGDN_FORPARSING = 32768
		} 
		
		[StructLayout(LayoutKind.Sequential)]
		public struct RECT
		{
			public int left;
			public int top;
			public int right;
			public int bottom;

			public RECT(
				Rectangle r
				)
			{
				left = r.Left;
				top = r.Top;
				right = r.Right;
				bottom = r.Bottom;
			}

		} 

		[StructLayout(LayoutKind.Sequential)]
		public struct FOLDERSETTINGS
		{
			public FOLDERFLAGS ViewMode;
			public FOLDERVIEWMODE fFlags;
		} 
		
		public enum SVUIA_STATUS
		{
			SVUIA_DEACTIVATE = 0,
			SVUIA_ACTIVATE_NOFOCUS = 1,
			SVUIA_ACTIVATE_FOCUS = 2,
			SVUIA_INPLACEACTIVATE = 3
		} 

		[ComImportAttribute()]
		[GuidAttribute(
		"000214F2-0000-0000-C000-000000000046")]
		[InterfaceTypeAttribute(
		ComInterfaceType.InterfaceIsIUnknown)]
		public interface IEnumIDList
		{
			[PreserveSig]
			int Next(
				int celt, 
				ref IntPtr 
				rgelt, out 
				int pceltFetched
				);
			[PreserveSig]
			int Skip(int celt);
			[PreserveSig]
			int Reset();
			[PreserveSig]
			int Clone(
				ref IEnumIDList ppenum
				);
		} 

		[ComImport()]
		[Guid(
		"000214E2-0000-0000-C000-000000000046")]
		[InterfaceType(
		ComInterfaceType.InterfaceIsIUnknown)]
		public interface IShellBrowser
		{
			[PreserveSig]
			int GetWindow(out IntPtr phwnd);
			[PreserveSig]
			int ContextSensitiveHelp(bool fEnterMode);
			[PreserveSig]
			int InsertMenusSB(
				IntPtr hmenuShared, 
				ref IntPtr lpMenuWidths
				);
			[PreserveSig]
			int SetMenuSB(
				IntPtr hmenuShared, 
				IntPtr holemenuRes, 
				IntPtr hwndActiveObject
				);
			[PreserveSig]
			int RemoveMenusSB(
				IntPtr hmenuShared
				);
			[PreserveSig]
			int SetStatusTextSB(
				string pszStatusText
				);
			[PreserveSig]
			int EnableModelessSB(
				bool fEnable
				);
			[PreserveSig]
			int TranslateAcceleratorSB(
				IntPtr pmsg, 
				short wID
				);
			[PreserveSig]
			int BrowseObject(
				IntPtr pidl, 
				uint wFlags
				);
			[PreserveSig]
			int GetViewStateStream(
				long grfMode, 
				ref UCOMIStream ppStrm
				);
			[PreserveSig]
			int GetControlWindow(
				uint id, 
				ref IntPtr phwnd
				);
			[PreserveSig]
			int SendControlMsg(
				uint id, 
				uint uMsg, 
				short wParam, 
				long lParam, 
				ref long pret
				);
			[PreserveSig]
			int QueryActiveShellView(
				ref IShellView ppshv
				);
			[PreserveSig]
			int OnViewWindowActive(
				IShellView pshv
				);
			[PreserveSig]
			int SetToolbarItems(
				IntPtr lpButtons, 
				uint nButtons, 
				uint uFlags
				);
		} 

		[ComImport()]
		[Guid("000214E3-0000-0000-C000-000000000046")]
		[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
		public interface IShellView
		{
			[PreserveSig]
			int GetWindow(out IntPtr phwnd);
			[PreserveSig]
			int ContextSensitiveHelp(bool fEnterMode);
			[PreserveSig]
			int TranslateAcceleratorA(IntPtr pmsg);
			[PreserveSig]
			int EnableModeless(bool fEnable);
			[PreserveSig]
			int UIActivate(SVUIA_STATUS uState);
			[PreserveSig]
			int Refresh();
			[PreserveSig]
			int CreateViewWindow(
				IShellView psvPrevious, 
				ref FOLDERSETTINGS pfs, 
				IShellBrowser psb, 
				ref RECT prcView, 
				out IntPtr phWnd
				);
			[PreserveSig]
			int DestroyViewWindow();
			[PreserveSig]
			int GetCurrentInfo(ref FOLDERSETTINGS pfs);
			[PreserveSig]
			int AddPropertySheetPages(
				long dwReserved,
				 ref IntPtr pfnPtr, 
				 int lparam
				 );
			[PreserveSig]
			int SaveViewState();
			[PreserveSig]
			int SelectItem(IntPtr pidlItem, uint uFlags);
			[PreserveSig]
			int GetItemObject(
				uint uItem, 
				ref Guid riid, 
				ref IntPtr ppv
				);
		} 

		[ComImport]
		[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
		[Guid("000214E6-0000-0000-C000-000000000046")]
		public interface IShellFolder
		{
			[PreserveSig]
			Int32 ParseDisplayName(
				IntPtr hwnd, 
				IntPtr pbc, 
				[MarshalAs(UnmanagedType.LPWStr)] 
				string pszDisplayName, 
				ref UInt32 pchEaten, 
				out IntPtr ppidl, 
				ref UInt32 pdwAttributes
				);
			[PreserveSig]
			Int32 EnumObjects(
				IntPtr hwnd, 
				SHCONTF grfFlags, 
				out IEnumIDList ppenumIDList
				);
			[PreserveSig]
			Int32 BindToObject(
				IntPtr pidl, 
				IntPtr pbc, 
				ref Guid riid, 
				out IntPtr ppv
				);
			[PreserveSig]
			Int32 BindToStorage(
				IntPtr pidl, 
				IntPtr pbc, 
				ref Guid riid, 
				out IntPtr ppv
				);
			[PreserveSig]
			Int32 CompareIDs(
				SHCIDS lParam, 
				IntPtr pidl1, 
				IntPtr pidl2
				);
			[PreserveSig]
			Int32 CreateViewObject(
				IntPtr hwndOwner, 
				ref Guid riid, 
				out IShellView ppv
				);
			[PreserveSig]
			Int32 GetAttributesOf(
				UInt32 cidl, 
				[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] 
				IntPtr[] apidl, 
				ref SFGAO rgfInOut
				);
			[PreserveSig]
			Int32 GetUIObjectOf(
				IntPtr hwndOwner, 
				UInt32 cidl, 
				IntPtr[] apidl, 
				Guid riid, 
				ref UInt32 rgfReserved, 
				out IntPtr ppv
				);
			[PreserveSig]
			Int32 GetDisplayNameOf(
				IntPtr pidl, 
				SHGDN uFlags, 
				out STRRET pName
				);
			[PreserveSig]
			Int32 SetNameOf(
				IntPtr hwnd, 
				IntPtr pidl, 
				[MarshalAs(UnmanagedType.LPWStr)] 
				string pszName, 
				UInt32 uFlags, 
				out IntPtr ppidlOut
				);
		} 
	} 

	private static readonly NativeMethods.IShellFolder 
		c_desktopFolder;
	private NativeMethods.IShellFolder m_folder;
	private CGPidl m_pidl;

	public CGPidl Pidl
	{
		get {return m_pidl;}
	} 
	
	public object Interface
	{
		get {return m_folder;}
	} 
	
	[SecurityPermission(SecurityAction.LinkDemand, 
        Flags=SecurityPermissionFlag.UnmanagedCode)]
	static CGFolder()
	{

		// Get a reference to the desktop folder.
		NativeMethods.SHGetDesktopFolder(
			out c_desktopFolder
			);

	} 
	
	public CGFolder()
	{

	} 
	
	[SecurityPermission(SecurityAction.LinkDemand,
        Flags=SecurityPermissionFlag.UnmanagedCode)]
	public CGFolder(
		Environment.SpecialFolder specialFolder
		)
	{
		Open(specialFolder);
	} 
	
	[SecurityPermission(SecurityAction.LinkDemand, 
        Flags=SecurityPermissionFlag.UnmanagedCode)]
	public CGFolder(
		string fullPath
		)
	{
		Open(fullPath);
	} 
	
	[SecurityPermission(SecurityAction.LinkDemand, 
        Flags=SecurityPermissionFlag.UnmanagedCode)]
	public CGFolder(
		CGPidl pidl
		)
	{
		Open(pidl);
	} 
	
	public void Dispose()
	{
		Close();
	} 
	
	[SecurityPermission(SecurityAction.LinkDemand, 
        Flags=SecurityPermissionFlag.UnmanagedCode)]
	public void Open(
		Environment.SpecialFolder specialFolder
		)
	{

		// Free any open resources.
		Close();

		// Create the identifier.
		m_pidl = new CGPidl(specialFolder);

		// Create the folder reference.
		InitializeFolder();

	} 
	
	[SecurityPermission(SecurityAction.LinkDemand, 
        Flags=SecurityPermissionFlag.UnmanagedCode)]
	public void Open(
		string fullPath
		)
	{

		// Create the identifier.
		m_pidl = new CGPidl(fullPath);

		// Create the folder reference.
		InitializeFolder();

	} 
	
	[SecurityPermission(SecurityAction.LinkDemand, 
        Flags=SecurityPermissionFlag.UnmanagedCode)]
	public void Open(
		CGPidl pidl
		)
	{

		// Clone the identifier.
		m_pidl = (CGPidl)pidl.Clone();

		// Create the folder reference.
		InitializeFolder();
		
	} 
	
	public void Close()
	{

		// Should we cleanup the identifier?
		if (m_pidl != null)
			m_pidl.Dispose();

		m_pidl = null;

		// Should we cleanup the folder?
		if (m_folder != null)
			Marshal.ReleaseComObject(m_folder);

        m_folder = null;

	} 
	
	[SecurityPermission(SecurityAction.LinkDemand, 
        Flags=SecurityPermissionFlag.UnmanagedCode)]
	public System.Collections.ArrayList GetChildren(
		bool showHiddenObjects,
		bool showNonFolders,
		bool sortResults
		)
	{

		ArrayList children = new ArrayList();
		NativeMethods.IEnumIDList enumList = null;

		try
		{
			// Sanity check the folder before attempting to use it.
			if (m_pidl == null || !m_pidl.IsFolder || m_folder == null)
				return children;

			// Get the enumerator for the object.
			int hr = m_folder.EnumObjects(
				IntPtr.Zero,
				NativeMethods.SHCONTF.SHCONTF_FOLDERS |
				(showNonFolders ? 
					NativeMethods.SHCONTF.SHCONTF_NONFOLDERS : 0) |
				(showHiddenObjects ? 
					NativeMethods.SHCONTF.SHCONTF_INCLUDEHIDDEN : 0),
				out enumList
				);

			// Did we fail?
			if (hr != 0)
				Marshal.ThrowExceptionForHR(hr);
        
			IntPtr pidl = IntPtr.Zero;
			int fetched = 0;

			// Loop and walk through the child objects.
			while (enumList.Next(1, ref pidl, out fetched) == 0 &&
				fetched == 1)
			{
				
				// Create a new identifier for the object.
				CGPidl child = new CGPidl(
					m_pidl.Pidl, 
					pidl
					);

				// Add the object to the array.
				children.Add(child);

			} // End while there are more child objects.

			// Should we sort the results?
			if (sortResults)
				children.Sort();

		} // End try

		finally
		{

			// Should we cleanup the enumerator reference?
			if (enumList != null)
				Marshal.ReleaseComObject(enumList);

			enumList = null;

		} // End finally

		// Return the list of children.
		return children;

	} 
	
	[SecurityPermission(SecurityAction.LinkDemand, 
        Flags=SecurityPermissionFlag.UnmanagedCode)]
	private void InitializeFolder()
	{

		// Should we simply obtain a reference to the desktop?
		if (m_pidl.IsDesktop)
			NativeMethods.SHGetDesktopFolder(
				out m_folder
				);
		else
		{

			NativeMethods.IShellFolder parentFolder = null;

			try
			{

				IntPtr ptr;

				// Get a reference to the folder.
				int hr = c_desktopFolder.BindToObject(
					m_pidl.Pidl,
					IntPtr.Zero,
					ref NativeMethods.IID_IShellFolder,
					out ptr
					);

				// Did we fail?
				if (hr != 0)
					Marshal.ThrowExceptionForHR(hr);

				// Unwrap the inteface.
				m_folder = 
					(NativeMethods.IShellFolder)
					Marshal.GetTypedObjectForIUnknown(
					ptr,
					typeof(NativeMethods.IShellFolder)
					);

			} // End try

			finally
			{

				// Should we cleanup the parent folder?
				if (parentFolder != null)
					Marshal.ReleaseComObject(parentFolder);

				parentFolder = null;

			} // End finally

		} // End else we should handle the general case.
	} 
} 

 

I hope someone find this code useful. I also hope to be back to my regular coding/blogging routine soon.

 

Have fun! Smile

Comments

 

Chris said:

Hi

I think this blog is just what I’m looking for, I want to host a IShellFolder within my application. How can I get a custom control (c#) to host a CGFolder?

Thanks for your time

Chris

October 19, 2007 8:56 AM
 

Martin said:

Hello Chris,

I'm going to assume you're interested in a tree-view (explorer-like) control that would allow a user to browse around with their mouse. I actually have written controls like that in the past but I'm not free to share that code on CODEGATOR. I think CodeProject might have a few examples of working code - try there.

Thanks for visiting CODEGATOR!

October 25, 2007 6:30 AM
 

Shafeeque said:

I want to create a Pulldown menu in the Windows explorer. Can I use this class for the same. Please help me.

December 4, 2007 8:10 AM
 

Martin said:

Hello Shafeeque,

Extending Explorer as you are describing requires the construction of a Windows Shell Extension. My IShellFolder wrapper might help you perform some action with a Shell Extension but it can't be used to create one. There are many sources of information available if you want to learn more about creating Shell Extensions, including CodeProject and Google. I should warn you that the process is somewhat involved, and that the associated code may or may not port well to Vista.

Thanks for visiting CODEGATOR!

December 7, 2007 12:08 PM
 

Tony Peguero said:

Thanks for posting this.

I'm curious how the CGFolder class is supposed to be used in practice though, since it appears that none of the methods of an IShellFolder are exposed publicly. So once I have a properly constructed CGFolder, what can I do with it, apart from enumerate its children? If, for instance, I wanted to extract the icon for a folder, how would I go about it?

December 16, 2007 4:56 PM
 

Martin said:

The underlying IShellFolder interface is exposed via a property on the class.

December 18, 2007 12:20 PM
 

Tony Peguero said:

Yes, but it is only exposed as a plain "object". Unless I'm missing something, it can't be cast to an IShellFolder, because that interface is hidden within the NativeMethods class, which is private.

You say that you've been using this code for years, so my assumption is that either:

A) Some kind of mistake crept in while you were preparing the code for publication.

or B) The IShellFolder interface is hidden intentionally, in which case there is presumably some way to cast the value of CGFolder.Interface that I'm not aware of.

If it is the latter case, how would it normally be used?

December 18, 2007 3:54 PM
 

Martin said:

My library has all the NativeMethods stuff for this and several other classes in a common shared class. I copied the relevant PInvoke stuff into this class during publication in order to simplify things. Casting isn't a problem for me because all the code that uses this class (and the interface) are in the same assembly. You may copy my class and tweak it to meet your needs.

Thanks for your input Tony!

December 20, 2007 6:36 AM
 

Shafeeque said:

Thanks fro replying me patiently.

In my BHO i have an instance of  WebBrowserClass, is it possible to get IShellBrowser from this. Once I get this I think I can use the InsertMenusSB method to insert a Menu to the windows explorer. Please help me in this regard.

December 28, 2007 8:19 AM
 

Martin said:

Shafeeque you should be able to get at the IShellBrowser interface by using the WebBrowser's IUnknown interface to get at the IServiceProvider interface and then use the IServiceProvider's QueryService method to get the IShellBrowser interface.

I hope that helps.

December 28, 2007 3:25 PM

Leave a Comment

(required) 
(optional)
(required) 
Submit

About Martin

I work as a software engineer specializing in designing and building object-oriented business solutions for Windows platforms using C#. I have been programming professionally for roughly 20 years.

This Blog

Syndication

Terms of Service | Privacy Statement