using System.Collections; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using Vanara.Collections; using static Vanara.PInvoke.Ole32; using static Vanara.PInvoke.PropSys; namespace Vanara.Windows.Shell; /// /// A dictionary of properties that can be used to set or update property values on Shell items via the method. This class wraps the COM interface. /// /// /// public class ShellItemPropertyUpdates : IDictionary, IDisposable { private IPropertyChangeArray changes; /// Initializes a new instance of the class. public ShellItemPropertyUpdates() => PSCreatePropertyChangeArray(null, null, null, 0, typeof(IPropertyChangeArray).GUID, out changes).ThrowIfFailed(); /// Gets the number of elements contained in the . public int Count => (int)changes.GetCount(); /// Gets the COM interface for . /// The value. public IPropertyChangeArray IPropertyChangeArray => changes; /// Gets an containing the keys of the . public ICollection Keys { get { var l = new List(Count); for (uint i = 0; i < Count; i++) { using var p = new ComReleaser(changes.GetAt(i)); l.Add(p.Item.GetPropertyKey()); } return l; } } /// Gets an containing the values in the . public ICollection Values { get { var l = new List(Count); for (int i = 0; i < Count; i++) l.Add(this[i].Value); return l; } } /// Gets a value indicating whether the is read-only. bool ICollection>.IsReadOnly => false; /// Gets or sets the with the specified key. /// The . /// The key. /// The object associated with . /// key public object? this[PROPERTYKEY key] { get => TryGetValue(key, out var value) ? value : throw new ArgumentOutOfRangeException(nameof(key)); set => changes.AppendOrReplace(ToPC(key, value)); } internal KeyValuePair this[int index] { get { using var p = new ComReleaser(changes.GetAt((uint)index)); p.Item.ApplyToPropVariant(new PROPVARIANT(), out var pv); return new KeyValuePair(p.Item.GetPropertyKey(), pv.Value); } } /// Adds an element with the provided key and value to the . /// The object to use as the key of the element to add. /// The object to use as the value of the element to add. public void Add(PROPERTYKEY key, object? value) => changes.Append(ToPC(key, value)); /// Removes all items from the . public void Clear() { for (uint i = (uint)Count - 1; i >= 0; i--) changes.RemoveAt(i); } /// Determines whether the contains an element with the specified key. /// The key to locate in the . /// true if the contains an element with the key; otherwise, false. public bool ContainsKey(PROPERTYKEY key) => changes.IsKeyInArray(key).Succeeded; /// Returns an enumerator that iterates through the collection. /// A that can be used to iterate through the collection. public IEnumerator> GetEnumerator() => new IEnumFromIndexer>(changes.GetCount, i => this[(int)i]).GetEnumerator(); /// Removes the element with the specified key from the . /// The key of the element to remove. /// /// true if the element is successfully removed; otherwise, false. This method also returns false if was not /// found in the original . /// public bool Remove(PROPERTYKEY key) { var idx = IndexOf(key); if (idx == -1) return false; try { changes.RemoveAt((uint)idx); return true; } catch { return false; } } /// Gets the value associated with the specified key. /// The key whose value to get. /// /// When this method returns, the value associated with the specified key, if the key is found; otherwise, the default value for the /// type of the parameter. This parameter is passed uninitialized. /// /// /// true if the object that implements contains an element with the specified key; /// otherwise, false. /// public bool TryGetValue(PROPERTYKEY key, [NotNullWhen(true)] out object? value) { value = null; var idx = IndexOf(key); if (idx == -1) return false; try { value = this[idx]; return true; } catch { return false; } } void ICollection>.Add(KeyValuePair item) => Add(item.Key, item.Value); bool ICollection>.Contains(KeyValuePair item) => ContainsKey(item.Key) && this[item.Key] == item.Value; void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) { if (array == null) throw new ArgumentNullException(nameof(array)); if (arrayIndex + Count > array.Length) throw new ArgumentOutOfRangeException(nameof(arrayIndex)); for (int i = 0; i < Count; i++) array[i + arrayIndex] = this[i]; } void IDisposable.Dispose() { } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); bool ICollection>.Remove(KeyValuePair item) { var idx = IndexOf(item.Key); if (idx == -1) return false; if (this[idx].Value != item.Value) return false; try { changes.RemoveAt((uint)idx); return true; } catch { return false; } } private int IndexOf(PROPERTYKEY key) { for (uint i = 0; i < Count; i++) { using var p = new ComReleaser(changes.GetAt(i)); if (key == p.Item.GetPropertyKey()) return (int)i; } return -1; } private IPropertyChange ToPC(PROPERTYKEY key, object? value, PKA_FLAGS flags = PKA_FLAGS.PKA_SET) { PSCreateSimplePropertyChange(flags, key, new PROPVARIANT(value), typeof(IPropertyChange).GUID, out var pc).ThrowIfFailed(); return pc; } }