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();
}
}
}