From ddd6935037e63bb76a6cd208d64fff1ce02471d4 Mon Sep 17 00:00:00 2001 From: dahall Date: Mon, 1 Jun 2020 10:37:00 -0600 Subject: [PATCH] Added ReadOnlyPropertyStore as the base object wrapping IPropertyStore. --- .../ShellProperties/ReadOnlyPropertyStore.cs | 264 +++++++++++++++++++++ 1 file changed, 264 insertions(+) create mode 100644 Windows.Shell/ShellProperties/ReadOnlyPropertyStore.cs diff --git a/Windows.Shell/ShellProperties/ReadOnlyPropertyStore.cs b/Windows.Shell/ShellProperties/ReadOnlyPropertyStore.cs new file mode 100644 index 00000000..fb0430cb --- /dev/null +++ b/Windows.Shell/ShellProperties/ReadOnlyPropertyStore.cs @@ -0,0 +1,264 @@ +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 != null) + { + Marshal.ReleaseComObject(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) + { + try + { + value = (T)ps.GetValue(key); + return true; + } + catch { } + value = default; + return false; + } + + /// 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(); + } + } +} \ No newline at end of file