using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using Vanara.Extensions; using Vanara.InteropServices; namespace Vanara.PInvoke { public static partial class Shell32 { /// Represents a managed pointer to an ITEMIDLIST. [PInvokeData("Shtypes.h", MSDNShortId = "bb773321")] [DebuggerDisplay("[{ToString()}]")] public class PIDL : SafeHANDLE, IEnumerable, IEquatable, IEquatable { /// Initializes a new instance of the class. /// The raw pointer to a native ITEMIDLIST. /// if set to true clone the list before storing it. /// if set to true will release the memory associated with the ITEMIDLIST when done. public PIDL(IntPtr pidl, bool clone = false, bool own = true) : base(clone ? IntILClone(pidl) : pidl, clone || own) { } /// Initializes a new instance of the class. /// An existing that will be copied and managed. public PIDL(PIDL pidl) : this(pidl.handle, true) { } /// Initializes a new instance of the class from a file path. /// A string that contains the path. public PIDL(string path) : this(IntILCreateFromPath(path)) { } /// Initializes a new instance of the class from an array of bytes. /// The bytes. public PIDL(byte[] bytes) { using var p = new PinnedObject(bytes); SetHandle(IntILClone(p)); } /// Initializes a new instance of the class from a stream holding an absolute ITEMIDLIST. /// The stream interface instance from which the ITEMIDLIST loads. public PIDL(System.Runtime.InteropServices.ComTypes.IStream stream) { ILLoadFromStreamEx(stream, out var p).ThrowIfFailed(); SetHandle(p.ReleaseOwnership()); } /// Initializes a new instance of the class. internal PIDL() { } /// Gets a value representing a NULL PIDL. /// The null equivalent. public static PIDL Null { get; } = new PIDL(); /// Gets a value indicating whether this list is empty. /// true if this list is empty; otherwise, false. public bool IsEmpty => ILIsEmpty(handle); /// Gets the last SHITEMID in this ITEMIDLIST. /// The last SHITEMID. public PIDL LastId => new PIDL(ILFindLastID(handle), true); /// Gets an ITEMIDLIST with the last ID removed. If this is the topmost ID, a clone of the current is returned. public PIDL Parent { get { var p = new PIDL(this); p.RemoveLastId(); return p; } } /// Gets the size, in bytes, of the ITEMIDLIST. public uint Size => ILGetSize(handle); /// Combines the specified instances to create a new one. /// The first . /// The second . /// A managed instance that contains both supplied lists in their respective order. public static PIDL Combine(PIDL firstPidl, PIDL secondPidl) => ILCombine(firstPidl.handle, secondPidl.handle); /// Performs an explicit conversion from to . /// The current . /// The result of the conversion. public static explicit operator IntPtr(PIDL pidl) => pidl.handle; /// Performs an implicit conversion from to . /// The pointer to a raw ITEMIDLIST. /// The result of the conversion. public static implicit operator PIDL(IntPtr p) => new PIDL(p); /// Combines the specified instances to create a new one. /// The first . /// The second . /// A managed instance that contains both supplied lists in their respective order. public static PIDL operator +(PIDL first, PIDL second) => Combine(first, second); /// Implements the operator ==. /// The first . /// The second . /// The result of the operator. public static bool operator ==(PIDL first, PIDL second) => first?.Equals(second) ?? second is null; /// Implements the operator !=. /// The first . /// The second . /// The result of the operator. public static bool operator !=(PIDL first, PIDL second) => !(first == second); /// Appends the specified to the existing list. /// The to append. public void Append(PIDL appendPidl) => UpdateHandle(IntILCombine(handle, appendPidl.DangerousGetHandle())); /// Dumps this instance to a string a list of binary values. /// A binary string of the contents. public string Dump() => string.Join(" ", handle.ToIEnum((int)Size).Select(b => $"{b:X2}").ToArray()); /// Determines whether the specified , is equal to this instance. /// The to compare with this instance. /// true if the specified is equal to this instance; otherwise, false. public override bool Equals(object obj) => obj switch { null => IsEmpty, PIDL pidl => Equals(pidl), IntPtr ptr => Equals(ptr), _ => base.Equals(obj), }; /// Indicates whether the current object is equal to another object of the same type. /// An object to compare with this object. /// true if the current object is equal to the parameter; otherwise, false. public bool Equals(IntPtr other) => ILIsEqual(handle, other); /// Indicates whether the current object is equal to another object of the same type. /// An object to compare with this object. /// true if the current object is equal to the parameter; otherwise, false. public bool Equals(PIDL other) => ReferenceEquals(this, other) || Equals(other?.handle ?? IntPtr.Zero); /// Gets the ID list as an array of bytes. /// An array of bytes representing the ID list. public byte[] GetBytes() => handle.ToArray((int)Size); /// Returns an enumerator that iterates through the collection. /// A that can be used to iterate through the collection. public IEnumerator GetEnumerator() => new InternalEnumerator(handle); /// Returns a hash code for this instance. /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. public override int GetHashCode() => new Vanara.Collections.EnumerableEqualityComparer().GetHashCode(handle.ToIEnum((int)Size)); /// Inserts the specified before the existing list. /// The to insert. public void Insert(PIDL insertPidl) => UpdateHandle(IntILCombine(insertPidl.handle, handle)); /// Determines if this instance is a parent or ancestor of a supplied PIDL. /// Child instance to test. /// If true, narrows test to immediate children only. /// true if this instance is a parent or ancestor of a supplied PIDL. public bool IsParentOf(PIDL childPidl, bool immediate = true) => ILIsParent((IntPtr)this, (IntPtr)childPidl, immediate); /// Removes the last identifier from the list. public bool RemoveLastId() => ILRemoveLastID(handle); /// Saves an ITEMIDLIST structure to a stream. /// An IStream instance where the ITEMIDLIST is saved. /// The stream must be opened for writing, or returns an error. public void SaveToStream(System.Runtime.InteropServices.ComTypes.IStream stream) => ILSaveToStream(stream, new(handle, false, false)).ThrowIfFailed(); /// Returns a that represents this instance. /// A that represents this instance. public override string ToString() => ToString(SIGDN.SIGDN_NORMALDISPLAY); /// /// Returns a that represents this instance base according to the format provided by . /// /// The desired display name format. /// A that represents this instance. public string ToString(SIGDN displayNameFormat) { try { SHGetNameFromIDList(this, displayNameFormat, out var name).ThrowIfFailed(); return name; } catch (Exception ex) { Debug.WriteLine($"Error: PIDL:ToString = {ex}"); } return null; } /// Returns an enumerator that iterates through a collection. /// An object that can be used to iterate through the collection. IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); /// /// Internal method that actually releases the handle. This is called by /// for valid handles and afterwards zeros the handle. /// /// true to indicate successful release of the handle; false otherwise. protected override bool InternalReleaseHandle() { ILFree(handle); return true; } /// Updates the handle after freeing the existing handle. /// The new handle. protected virtual void UpdateHandle(IntPtr newHandle) { ILFree(handle); SetHandle(newHandle); } private class InternalEnumerator : IEnumerator { private readonly IntPtr pidl; private IntPtr currentPidl; private bool start; public InternalEnumerator(IntPtr handle) { start = true; pidl = handle; currentPidl = IntPtr.Zero; } public PIDL Current => currentPidl == IntPtr.Zero ? null : ILCloneFirst(currentPidl); object IEnumerator.Current => Current; public void Dispose() { } public bool MoveNext() { if (start) { start = false; currentPidl = pidl; return true; } IntPtr newPidl = ILNext(currentPidl); if (ILIsEmpty(newPidl)) return false; currentPidl = newPidl; return true; } public void Reset() { start = true; currentPidl = IntPtr.Zero; } } } } }