using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using Vanara.PInvoke; using static Vanara.PInvoke.Ole32; using static Vanara.PInvoke.PropSys; namespace Vanara.Windows.Shell { /// Encapsulates the IPropertyStore object. /// /// public abstract class ReadOnlyPropertyStore : IReadOnlyDictionary, IDisposable { /// The IPropertyStore instance. protected IPropertyStore iPropertyStore; private PropertyDescriptionDictionary descriptions; /// Initializes a new instance of the class. protected ReadOnlyPropertyStore() { } /// Gets the number of properties in the current property store. public int Count => Run(ps => (int)(ps?.GetCount() ?? 0)); /// Value that allows matching this property store's keys to their property descriptions. /// The property descriptions. public virtual IReadOnlyDictionary Descriptions => descriptions ??= new PropertyDescriptionDictionary(this); /// Gets a value indicating whether the is read-only. public virtual bool IsReadOnly => true; /// Gets an containing the keys of the . public IEnumerable Keys => Run(ps => GetKeyEnum(ps).ToList()); /// Gets an containing the values in the . public IEnumerable Values => Run(ps => GetKeyEnum(ps).Select(k => TryGetValue(ps, k, out object v) ? v : null).ToList()); /// Gets or sets the value of the property with the specified known key. /// The value. /// The known key of the property (e.g. "System.Title"}. /// The value of the property. public virtual object this[string knownKey] => this[GetPropertyKeyFromName(knownKey)]; /// Gets or sets the value of the property with the specified PROPERTYKEY. /// The value. /// The PROPERTYKEY of the property. /// The value of the property. public virtual object this[PROPERTYKEY key] => TryGetValue(key, out var r) ? r : null; /// Gets the property key for a canonical property name. /// A property name. /// The requested property key. public static PROPERTYKEY GetPropertyKeyFromName(string name) { if (name is null) throw new ArgumentNullException(nameof(name)); var hr = PSGetPropertyKeyFromName(name, out var pk); if (hr == HRESULT.TYPE_E_ELEMENTNOTFOUND) throw new ArgumentOutOfRangeException(nameof(name)); hr.ThrowIfFailed(); return pk; } /// 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) => Keys.Contains(key); /// /// Copies the elements of the to an , starting at a particular index. /// /// /// The one-dimensional that is the destination of the elements copied from . The must have zero-based indexing. /// /// The zero-based index in at which copying begins. /// arrayIndex - The number of items exceeds the length of the supplied array. /// array public void CopyTo(KeyValuePair[] array, int arrayIndex) { if (array.Length < (arrayIndex + Count)) throw new ArgumentOutOfRangeException(nameof(arrayIndex), "The number of items exceeds the length of the supplied array."); if (array is null) throw new ArgumentNullException(nameof(array)); var i = arrayIndex; foreach (var kv in this) array[i++] = kv; } /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. public virtual void Dispose() { if (iPropertyStore is not null) { Marshal.FinalReleaseComObject(iPropertyStore); iPropertyStore = null; } } /// Gets the property. /// The type of the value. /// The key. /// The cast value of the property. /// key public TVal GetProperty(PROPERTYKEY key) => TryGetValue(key, out var ret) ? ret : throw new ArgumentOutOfRangeException(nameof(key)); /// Gets the property description related to a property key. /// The key. /// The related property description, if one exists; otherwise . public PropertyDescription GetPropertyDescription(PROPERTYKEY key) => PropertyDescription.Create(key); /// Gets the string value of the property. /// The key. /// The formatting flags. /// The string value of the property. /// key public string GetPropertyString(PROPERTYKEY key, PROPDESC_FORMAT_FLAGS flags = PROPDESC_FORMAT_FLAGS.PDFF_DEFAULT) { using var pv = GetPropVariant(key); return PropertyDescription.Create(key)?.FormatForDisplay(pv, flags); } /// Gets the PROPVARIANT value for a key. /// The key. /// The PROPVARIANT value. /// key public PROPVARIANT GetPropVariant(PROPERTYKEY key) { return Run(ps => { var pv = new PROPVARIANT(); ps.GetValue(key, pv); return pv; }); } /// 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. /// /// /// if the object that implements contains an element with the /// specified key; otherwise, . /// public bool TryGetValue(PROPERTYKEY key, out object value) => TryGetValue(key, out value); /// Gets the value associated with the specified key. /// The type of the returned value. /// 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. /// /// /// if the object that implements contains an element with the /// specified key; otherwise, . /// public virtual bool TryGetValue(PROPERTYKEY key, out TVal value) { var result = Run(ps => { var ret = TryGetValue(ps, key, out var val); return (ret, ret ? val : default); }); value = result.Item2; return result.ret; } /// Returns an enumerator that iterates through the collection. /// A that can be used to iterate through the collection. IEnumerator> IEnumerable>.GetEnumerator() => (Run(ps => GetKeyEnum(ps).Select(k => new KeyValuePair(k, TryGetValue(ps, k, out object pv) ? pv : null))) ?? new KeyValuePair[0]).GetEnumerator(); /// Returns an enumerator that iterates through a collection. /// An object that can be used to iterate through the collection. IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable>)this).GetEnumerator(); /// Gets the value associated with the specified key. /// The IPropertyStore instance. /// The key whose value to get. /// /// When this method returns, the value associated with the specified key, if the key is found; otherwise, default(T). /// /// if the property store contains an element with the specified key; otherwise, . protected static bool TryGetValue(IPropertyStore ps, PROPERTYKEY key, out T value) { var ret = ps.GetValue(key); value = ret is null ? default : (T)ret; return ret is not null; } /// The IPropertyStore instance. This can be null. protected virtual IPropertyStore GetIPropertyStore() => iPropertyStore; /// Gets an enumeration of the keys in the property store. /// Keys in the property store. protected virtual IEnumerable GetKeyEnum(IPropertyStore ps) { if (ps is null) yield break; for (uint i = 0; i < Count; i++) yield return ps.GetAt(i); } /// Runs the specified action with a retrived IPropertyStore instance. /// The action to run. protected void Run(Action action) { iPropertyStore ??= GetIPropertyStore(); if (iPropertyStore is null) return; action(iPropertyStore); } /// Runs the specified action with a retrived IPropertyStore instance. /// The return type of the action and method. /// The action to run. /// The return value from . protected T Run(Func action) { iPropertyStore ??= GetIPropertyStore(); return iPropertyStore is null ? default : action.Invoke(iPropertyStore); } private class PropertyDescriptionDictionary : IReadOnlyDictionary { private ReadOnlyPropertyStore store; public PropertyDescriptionDictionary(ReadOnlyPropertyStore ps) => store = ps; public IEnumerable Values => store.Keys.Select(k => store.GetPropertyDescription(k)).ToList(); public int Count => store.Count; public IEnumerable Keys => store.Keys; public PropertyDescription this[PROPERTYKEY key] => store.GetPropertyDescription(key); public bool ContainsKey(PROPERTYKEY key) => store.ContainsKey(key); public IEnumerator> GetEnumerator() => store.Keys.Select(k => new KeyValuePair(k, store.GetPropertyDescription(k))).ToList().GetEnumerator(); public bool TryGetValue(PROPERTYKEY key, out PropertyDescription value) { if (store.ContainsKey(key)) { value = this[key]; return true; } value = null; return false; } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } } }