using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Runtime.InteropServices; using System.Runtime.InteropServices.ComTypes; using Vanara.Collections; 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 = SHCONTF.SHCONTF_FOLDERS, /// Include items that are not folders in the enumeration. NonFolders = SHCONTF.SHCONTF_NONFOLDERS, /// Include hidden items in the enumeration. This does not include hidden system items. (To include hidden system items, use IncludeSuperHidden.) IncludeHidden = SHCONTF.SHCONTF_INCLUDEHIDDEN, /// The calling application is looking for printer objects. Printers = SHCONTF.SHCONTF_NETPRINTERSRCH, /// The calling application is looking for resources that can be shared. Shareable = SHCONTF.SHCONTF_SHAREABLE, /// Include items with accessible storage and their ancestors, including hidden items. Storage = SHCONTF.SHCONTF_STORAGE, // /// 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 = SHCONTF.SHCONTF_FASTITEMS, /// Windows Vista and later. Enumerate items as a simple list even if the folder itself is not structured in that way. FlatList = SHCONTF.SHCONTF_FLATLIST, /// /// 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 = SHCONTF.SHCONTF_INCLUDESUPERHIDDEN } /// A folder or container of instances. public class ShellFolder : ShellItem, IEnumerable { internal IShellFolder iShellFolder; private ShellFolderCategorizer categories; 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 ??= 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).ThrowIfFailed(); 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); } } /// Gets the underlying instance. /// The underlying instance. public IShellFolder IShellFolder => iShellFolder; /// /// Retrieves a handler, typically the Shell folder object that implements IShellFolder for a particular item. Optional parameters /// that control the construction of the handler are passed in the bind context. /// /// Type of the interface to get, usually IShellFolder or IStream. /// /// A relative PIDL that identifies the subfolder. This value can refer to an item at any level below the parent folder in the /// namespace hierarchy. /// /// /// A pointer to an IBindCtx interface on a bind context object that can be used to pass parameters to the construction of the /// handler. If this parameter is not used, set it to . Because support for this parameter is optional for /// folder object implementations, some folders may not support the use of bind contexts. /// /// Information that can be provided in the bind context includes a BIND_OPTS structure that includes a grfMode member that /// indicates the access mode when binding to a stream handler. Other parameters can be set and discovered using /// IBindCtx::RegisterObjectParam and IBindCtx::GetObjectParam. /// /// /// Receives the interface pointer requested in . public T BindToObject(PIDL relativePidl, IBindCtx bindCtx = null) where T : class => iShellFolder.BindToObject(relativePidl, bindCtx); /// Requests a pointer to an object's storage interface. /// Type of the interface to get, usuall IStream, IStorage, or IPropertySetStorage. /// The PIDL that identifies the subfolder relative to its parent folder. /// /// The optional address of an IBindCtx interface on a bind context object to be used during this operation. If this parameter is /// not used, set it to . Because support for pbc is optional for folder object implementations, some folders /// may not support the use of bind contexts. /// /// Receives the interface pointer requested in . public T BindToStorage(PIDL relativePidl, IBindCtx bindCtx = null) where T : class => iShellFolder.BindToStorage(relativePidl, bindCtx); /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// public override void Dispose() { iShellFolder = null; base.Dispose(); } /// Gets the registered categorizers. /// The categorizers. public ShellFolderCategorizer Categories => categories ??= new(IShellFolder); /// /// 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 */, HWND parentWindow = default) { if (iShellFolder.EnumObjects(parentWindow, (SHCONTF)filter, out var eo).Failed) Debug.WriteLine($"Unable to enum children in folder."); foreach (var p in eo.Enumerate(20)) { ShellItem i = null; try { i = this[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(HWND 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(parentWindow, Array.ConvertAll(children, i => (IntPtr)i.PIDL.LastId)); } /// 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(HWND parentWindow) where TInterface : class => iShellFolder.CreateViewObject(parentWindow); /// 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, HWND parentWindow) { iShellFolder.SetNameOf(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(); private IShellFolder GetInstance() => iShellItem.BindToHandler(null, BHID.BHID_SFObject.Guid()); } }