using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Runtime.InteropServices; using Vanara.PInvoke; using static Vanara.PInvoke.Shell32; // ReSharper disable UnusedMember.Global // ReSharper disable MemberCanBePrivate.Global namespace Vanara.Windows.Shell { /// A filter for the types of children to enumerate. [Flags] public enum FolderItemFilter { /// Include items that are folders in the enumeration. Folders = 0x00020, /// Include items that are not folders in the enumeration. NonFolders = 0x00040, /// Include hidden items in the enumeration. This does not include hidden system items. (To include hidden system items, use IncludeSuperHidden.) IncludeHidden = 0x00080, /// The calling application is looking for printer objects. Printers = 0x00200, /// The calling application is looking for resources that can be shared. Shareable = 0x00400, /// Include items with accessible storage and their ancestors, including hidden items. Storage = 0x00800, // /// Windows 7 and later. Child folders should provide a navigation enumeration. // NAVIGATION_ENUM = 0x01000, /// Windows Vista and later. The calling application is looking for resources that can be enumerated quickly. FastItems = 0x02000, /// Windows Vista and later. Enumerate items as a simple list even if the folder itself is not structured in that way. FlatList = 0x04000, /// /// Windows 7 and later. Include hidden system items in the enumeration. This value does not include hidden non-system items. (To include hidden /// non-system items, use IncludeHidden.) /// IncludeSuperHidden = 0x10000 } // TODO: BindToObject // TODO: BindToStorage /// A folder or container of instances. public class ShellFolder : ShellItem, IEnumerable { internal IShellFolder iShellFolder; private static ShellFolder desktop; /// Initializes a new instance of the class. /// The file system path of the item. public ShellFolder(string path) : base(path) { iShellFolder = GetInstance(); } /// Initializes a new instance of the class. /// A known folder value. public ShellFolder(KNOWNFOLDERID knownFolder) : base(knownFolder) { iShellFolder = GetInstance(); } /// Initializes a new instance of the class. /// The ID list. public ShellFolder(PIDL idList) : base(idList) { iShellFolder = GetInstance(); } /// Initializes a new instance of the class. /// A ShellItem instance whose IsFolder property is true. public ShellFolder(ShellItem shellItem) : this(shellItem.iShellItem) { } /// Initializes a new instance of the class. internal ShellFolder(IShellItem iShellItem) : base(iShellItem) { iShellFolder = GetInstance(); } /// Initializes a new instance of the class. protected ShellFolder() { } /// Gets a reference to the primary Desktop. /// The desktop instance. public static ShellFolder Desktop => desktop ?? (desktop = new ShellFolder(KNOWNFOLDERID.FOLDERID_Desktop)); /// Gets the with the specified child name. /// The instance matching . /// Name of the child item. /// The instance matching , if it exists. public ShellItem this[string childName] { get { if (string.IsNullOrEmpty(childName)) throw new ArgumentNullException(nameof(childName)); object ppv; if (IsMinVista) SHCreateItemFromRelativeName(iShellItem, childName, BindContext, typeof(IShellItem).GUID, out ppv).ThrowIfFailed(); else { SFGAO attr = 0; iShellFolder.ParseDisplayName(IntPtr.Zero, null, childName, out uint _, out var tempPidl, ref attr); ppv = new ShellItemImpl(PIDL.Combine(PIDL, tempPidl), false); } return Open((IShellItem)ppv); } } /// Gets a child reference from a parent and child PIDL. /// A valid relative PIDL. /// A child reference. public ShellItem this[PIDL relativePidl] { get { if (relativePidl == null || relativePidl.IsInvalid) throw new ArgumentNullException(nameof(relativePidl)); object ppv; if (IsMinVista) SHCreateItemWithParent(PIDL.Null, iShellFolder, relativePidl, typeof(IShellItem).GUID, out ppv); else ppv = new ShellItemImpl(PIDL.Combine(PIDL, relativePidl), false); return Open((IShellItem)ppv); } } /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// public override void Dispose() { if (iShellFolder != null) { Marshal.ReleaseComObject(iShellFolder); iShellFolder = null; } base.Dispose(); } /// /// Enumerates all children of this item. If this item is not a folder/container, this method will return an empty enumeration. /// /// A filter for the types of children to enumerate. /// The parent window. /// An enumerated list of children matching the filter. public IEnumerable EnumerateChildren(FolderItemFilter filter = FolderItemFilter.Folders | FolderItemFilter.IncludeHidden | FolderItemFilter.NonFolders | FolderItemFilter.IncludeSuperHidden, System.Windows.Forms.IWin32Window parentWindow = null) { IEnumIDList eo = null; try { eo = iShellFolder.EnumObjects(IWin2Ptr(parentWindow, false), (SHCONTF)filter); } catch (Exception e) { Debug.WriteLine($"Unable to enum children in folder: {e.Message}"); } if (eo != null) { foreach (var p in new Collections.IEnumFromNext((out IntPtr p) => eo.Next(1, out p, out var f).Succeeded && f == 1, () => eo.Reset())) { ShellItem i = null; try { i = this[new PIDL(p)]; } catch (Exception e) { Debug.WriteLine($"Unable to open folder child: {e.Message}"); } if (i != null) yield return i; } Marshal.ReleaseComObject(eo); } } /// Gets an object that can be used to carry out actions on the specified file objects or folders. /// The interface to retrieve, typically IShellView. /// The owner window that the client should specify if it displays a dialog box or message box.. /// The file objects or subfolders relative to the parent folder for which to get the interface. /// The interface pointer requested. public TInterface GetChildrenUIObjects(System.Windows.Forms.IWin32Window parentWindow, params ShellItem[] children) where TInterface : class { if (children is null || children.Length == 0 || children.Any(i => i is null || !IsChild(i))) throw new ArgumentException("At least one child ShellItem instances is null or is not a child of this folder."); return iShellFolder.GetUIObjectOf(IWin2Ptr(parentWindow), (uint)children.Length, Array.ConvertAll(children, i => (IntPtr)i.PIDL.LastId), typeof(TInterface).GUID) as TInterface; } /// Returns an enumerator that iterates through the collection. /// A that can be used to iterate through the collection. public IEnumerator GetEnumerator() => EnumerateChildren().GetEnumerator(); /// Requests an object that can be used to obtain information from or interact with a folder object. /// The interface to retrieve, typically IShellView. /// The owner window. /// The interface pointer requested. public TInterface GetViewObject(System.Windows.Forms.IWin32Window parentWindow) where TInterface : class { try { return iShellFolder.CreateViewObject(IWin2Ptr(parentWindow), typeof(TInterface).GUID) as TInterface; } catch (InvalidCastException e) { throw new NotImplementedException(null, e); } } /// Determines if the supplied is an immediate descendant of this folder. /// The child item to test. /// true if is an immediate descendant of this folder. public bool IsChild(ShellItem item) => Equals(item.Parent); /// Renames a child of this folder. /// The relative child IDL. /// The new name. /// The display type. /// The parent window to use if any messages need to be shown the user. /// A reference to the newly named item. public ShellItem RenameChild(PIDL relativeChildPidl, string newName, ShellItemDisplayString displayType, System.Windows.Forms.IWin32Window parentWindow) { iShellFolder.SetNameOf(IWin2Ptr(parentWindow), relativeChildPidl, newName, (SHGDNF)displayType, out PIDL newPidl); return this[newPidl]; } /// Returns an enumerator that iterates through a collection. /// An object that can be used to iterate through the collection. IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); internal static HWND IWin2Ptr(System.Windows.Forms.IWin32Window wnd, bool desktopIfNull = true) => wnd?.Handle ?? User32_Gdi.FindWindow("Progman", null); /// Enumerates all the children of the current item. Not valid before Vista. /// An enumeration of the child objects. protected override IEnumerable EnumerateChildren() => EnumerateChildren(); private IShellFolder GetInstance() => (IShellFolder)iShellItem.BindToHandler(null, BHID.BHID_SFObject.Guid(), typeof(IShellFolder).GUID); } }