using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; 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 class PropertyStore : IDictionary, IDisposable, INotifyPropertyChanged { /// The IPropertyStore instance. This can be null. protected IPropertyStore iprops; /// Initializes a new instance of the class. protected PropertyStore() { } /// Initializes a new instance of the class. /// The ps. protected PropertyStore(IPropertyStore ps) { iprops = ps; } /// Occurs when a property value changes. public event PropertyChangedEventHandler PropertyChanged; /// Gets the number of properties in the current property store. public int Count => (int)(iprops?.GetCount() ?? 0); /// Gets or sets a value indicating whether this property store has uncommitted changes. /// true if this instance is dirty; otherwise, false. public bool IsDirty { get; protected set; } /// Gets a value indicating whether the is read-only. public virtual bool IsReadOnly => false; /// Gets an containing the keys of the . public ICollection Keys { get { var keys = new List(Count); for (uint i = 0; i < Count; i++) keys.Add(iprops.GetAt(i)); return keys; } } /// Gets an containing the values in the . public ICollection Values { get { var vals = new List(Count); for (uint i = 0; i < Count; i++) vals.Add(this[iprops.GetAt(i)]); return vals; } } /// 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 object this[string knownKey] { get => this[GetPropertyKeyFromName(knownKey)]; set => this[GetPropertyKeyFromName(knownKey)] = value; } /// 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 object this[PROPERTYKEY key] { get { if (!TryGetValue(key, out object r)) throw new ArgumentOutOfRangeException(nameof(key)); return r; } set { if (iprops == null) throw new InvalidOperationException("Property store does not exist."); if (IsReadOnly) throw new InvalidOperationException("Property store is read-only."); iprops.SetValue(key, new PROPVARIANT(value)); OnPropertyChanged(key.ToString()); } } /// Gets the property key for a canonical property name. /// A property name. /// The requested property key. public static PROPERTYKEY GetPropertyKeyFromName(string name) { if (name == 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; } /// Adds a property with the provided key and value to the property store. /// The PROPERTYKEY for the new property. /// The value of the new property. public void Add(PROPERTYKEY key, object value) { if (iprops == null) throw new InvalidOperationException("Property store does not exist."); iprops.SetValue(key, new PROPVARIANT(value)); OnPropertyChanged(key.ToString()); } /// Commits all changes to the property store. public void Commit() { iprops?.Commit(); IsDirty = false; } /// 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 == 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 (iprops != null) { if (IsDirty) Commit(); Marshal.ReleaseComObject(iprops); iprops = null; } } /// Returns an enumerator that iterates through the collection. /// A that can be used to iterate through the collection. public IEnumerator> GetEnumerator() => Enum().GetEnumerator(); /// Gets the property. /// The type of the value. /// The key. /// The cast value of the property. /// key public TVal GetProperty(PROPERTYKEY key) { if (!TryGetValue(key, out var ret)) throw new ArgumentOutOfRangeException(nameof(key)); return ret; } /// 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) { if (!TryGetValue(key, out PROPVARIANT ret)) throw new ArgumentOutOfRangeException(nameof(key)); return PropertyDescription.Create(key)?.FormatForDisplay(ret, flags); } /// Gets the PROPVARIANT value for a key. /// The key. /// The PROPVARIANT value. /// key public PROPVARIANT GetPropVariant(PROPERTYKEY key) { if (!TryGetValue(key, out PROPVARIANT ret)) throw new ArgumentOutOfRangeException(nameof(key)); return ret; } /// 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, out object value) { var ret = TryGetValue(key, out PROPVARIANT pv); value = ret ? pv.Value : null; return ret; } /// 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. /// /// /// true if the object that implements contains an element with the specified key; otherwise, false. /// public bool TryGetValue(PROPERTYKEY key, out TVal value) { var ret = TryGetValue(key, out PROPVARIANT val); value = ret ? (TVal)val.Value : default; return ret; } /// Adds an item to the . /// The object to add to the . void ICollection>.Add(KeyValuePair item) { Add(item.Key, item.Value); } /// Removes all items from the . /// void ICollection>.Clear() { throw new InvalidOperationException(); } /// Determines whether the contains a specific value. /// The object to locate in the . /// true if is found in the ; otherwise, false. bool ICollection>.Contains(KeyValuePair item) => TryGetValue(item.Key, out object o) && Equals(o, item.Value); /// Returns an enumerator that iterates through a collection. /// An object that can be used to iterate through the collection. IEnumerator IEnumerable.GetEnumerator() => 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 . /// /// bool IDictionary.Remove(PROPERTYKEY key) { throw new InvalidOperationException(); } /// Removes the first occurrence of a specific object from the . /// The object to remove from the . /// /// true if was successfully removed from the ; otherwise, false. This /// method also returns false if is not found in the original . /// /// bool ICollection>.Remove(KeyValuePair item) { throw new InvalidOperationException(); } /// Called when a property has changed. protected virtual void OnPropertyChanged(string propertyName) { IsDirty = true; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } private IEnumerable> Enum() { for (uint i = 0; i < Count; i++) { var k = iprops.GetAt(i); yield return new KeyValuePair(k, this[k]); } yield break; } private bool TryGetValue(PROPERTYKEY key, out PROPVARIANT value) { value = new PROPVARIANT(); if (iprops != null) { try { iprops.GetValue(key, value); return true; } catch { } } return false; } } }