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!