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.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 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()
{
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)
{
if (iShellFolder.EnumObjects(IWin2Ptr(parentWindow, false), (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(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), 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(System.Windows.Forms.IWin32Window parentWindow) where TInterface : class =>
iShellFolder.CreateViewObject(IWin2Ptr(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, 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 ?? (desktopIfNull ? User32.FindWindow("Progman", null) : default);
private IShellFolder GetInstance() => iShellItem.BindToHandler(null, BHID.BHID_SFObject.Guid());
}
}