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