2017-11-27 13:11:20 -05:00
using System ;
using System.Collections ;
using System.Collections.Generic ;
2018-01-20 20:19:18 -05:00
using System.ComponentModel ;
2019-05-08 16:59:52 -04:00
using System.Linq ;
2018-01-20 20:19:18 -05:00
using System.Runtime.InteropServices ;
2018-01-22 20:23:01 -05:00
using Vanara.PInvoke ;
2017-11-27 13:11:20 -05:00
using static Vanara . PInvoke . Ole32 ;
using static Vanara . PInvoke . PropSys ;
namespace Vanara.Windows.Shell
{
2018-01-20 20:19:18 -05:00
/// <summary>Encapsulates the IPropertyStore object.</summary>
/// <seealso cref="IDictionary{PROPERTYKEY, Object}"/>
/// <seealso cref="System.IDisposable"/>
public class PropertyStore : IDictionary < PROPERTYKEY , object > , IDisposable , INotifyPropertyChanged
2017-11-27 13:11:20 -05:00
{
2018-01-20 20:19:18 -05:00
/// <summary>Initializes a new instance of the <see cref="PropertyStore"/> class.</summary>
protected PropertyStore ( ) { }
2017-11-27 13:11:20 -05:00
2018-01-20 20:19:18 -05:00
/// <summary>Occurs when a property value changes.</summary>
public event PropertyChangedEventHandler PropertyChanged ;
2017-11-27 13:11:20 -05:00
2018-01-20 20:19:18 -05:00
/// <summary>Gets the number of properties in the current property store.</summary>
2019-05-08 21:01:44 -04:00
public int Count = > Run ( ps = > ( int ) ( ps ? . GetCount ( ) ? ? 0 ) ) ;
2017-11-27 13:11:20 -05:00
2018-01-20 20:19:18 -05:00
/// <summary>Gets or sets a value indicating whether this property store has uncommitted changes.</summary>
/// <value><c>true</c> if this instance is dirty; otherwise, <c>false</c>.</value>
public bool IsDirty { get ; protected set ; }
2017-11-27 13:11:20 -05:00
2018-01-20 20:19:18 -05:00
/// <summary>Gets a value indicating whether the <see cref="ICollection{T}"/> is read-only.</summary>
public virtual bool IsReadOnly = > false ;
/// <summary>Gets an <see cref="ICollection{T}"/> containing the keys of the <see cref="IDictionary{PROPERTYKEY, Object}"/>.</summary>
2019-05-08 21:01:44 -04:00
public ICollection < PROPERTYKEY > Keys = > Run ( ps = > GetKeyEnum ( ps ) . ToList ( ) ) ;
2017-11-27 13:11:20 -05:00
2018-01-20 20:19:18 -05:00
/// <summary>Gets an <see cref="ICollection{T}"/> containing the values in the <see cref="IDictionary{PROPERTYKEY, Object}"/>.</summary>
2019-05-08 21:01:44 -04:00
public ICollection < object > Values = > Run ( ps = > GetKeyEnum ( ps ) . Select ( k = > TryGetValue ( ps , k , out object v ) ? v : null ) . ToList ( ) ) ;
/// <summary>The IPropertyStore instance. This can be null.</summary>
protected virtual IPropertyStore IPropStoreInst { get ; }
2017-11-27 13:11:20 -05:00
2018-01-20 20:19:18 -05:00
/// <summary>Gets or sets the value of the property with the specified known key.</summary>
/// <value>The value.</value>
/// <param name="knownKey">The known key of the property (e.g. "System.Title"}.</param>
/// <returns>The value of the property.</returns>
public object this [ string knownKey ]
2017-11-27 13:11:20 -05:00
{
2018-01-20 20:19:18 -05:00
get = > this [ GetPropertyKeyFromName ( knownKey ) ] ;
set = > this [ GetPropertyKeyFromName ( knownKey ) ] = value ;
2017-11-27 13:11:20 -05:00
}
2018-01-20 20:19:18 -05:00
/// <summary>Gets or sets the value of the property with the specified PROPERTYKEY.</summary>
/// <value>The value.</value>
2018-04-03 20:35:18 -04:00
/// <param name="key">The PROPERTYKEY of the property.</param>
2018-01-20 20:19:18 -05:00
/// <returns>The value of the property.</returns>
2017-11-27 13:11:20 -05:00
public object this [ PROPERTYKEY key ]
{
get
{
2019-05-08 21:01:44 -04:00
if ( ! TryGetValue ( key , out var r ) )
2017-11-27 13:11:20 -05:00
throw new ArgumentOutOfRangeException ( nameof ( key ) ) ;
return r ;
}
set
{
2019-05-08 21:01:44 -04:00
Run ( ps = > {
if ( ps is null )
throw new InvalidOperationException ( "Property store does not exist." ) ;
if ( IsReadOnly )
throw new InvalidOperationException ( "Property store is read-only." ) ;
ps . SetValue ( key , new PROPVARIANT ( value ) ) ;
OnPropertyChanged ( key . ToString ( ) ) ;
} ) ;
2017-11-27 13:11:20 -05:00
}
}
2018-01-20 20:19:18 -05:00
/// <summary>Gets the property key for a canonical property name.</summary>
/// <param name="name">A property name.</param>
/// <returns>The requested property key.</returns>
public static PROPERTYKEY GetPropertyKeyFromName ( string name )
{
2019-05-08 16:59:52 -04:00
if ( name is null ) throw new ArgumentNullException ( nameof ( name ) ) ;
2018-01-22 20:23:01 -05:00
var hr = PSGetPropertyKeyFromName ( name , out var pk ) ;
if ( hr = = HRESULT . TYPE_E_ELEMENTNOTFOUND ) throw new ArgumentOutOfRangeException ( nameof ( name ) ) ;
hr . ThrowIfFailed ( ) ;
2018-01-20 20:19:18 -05:00
return pk ;
}
/// <summary>Adds a property with the provided key and value to the property store.</summary>
/// <param name="key">The PROPERTYKEY for the new property.</param>
/// <param name="value">The value of the new property.</param>
2017-11-27 13:11:20 -05:00
public void Add ( PROPERTYKEY key , object value )
{
2019-05-08 21:01:44 -04:00
Run ( ps = >
{
if ( ps is null )
throw new InvalidOperationException ( "Property store does not exist." ) ;
ps . SetValue ( key , new PROPVARIANT ( value ) ) ;
OnPropertyChanged ( key . ToString ( ) ) ;
} ) ;
2017-11-27 13:11:20 -05:00
}
2018-01-20 20:19:18 -05:00
/// <summary>Commits all changes to the property store.</summary>
2019-05-08 21:01:44 -04:00
public void Commit ( )
{
if ( ! IsDirty ) return ;
Run ( ps = > ps ? . Commit ( ) ) ;
IsDirty = false ;
}
2017-11-27 13:11:20 -05:00
2018-01-20 20:19:18 -05:00
/// <summary>Determines whether the <see cref="IDictionary{PROPERTYKEY, Object}"/> contains an element with the specified key.</summary>
/// <param name="key">The key to locate in the <see cref="IDictionary{PROPERTYKEY, Object}"/>.</param>
/// <returns>true if the <see cref="IDictionary{PROPERTYKEY, Object}"/> contains an element with the key; otherwise, false.</returns>
2017-11-27 13:11:20 -05:00
public bool ContainsKey ( PROPERTYKEY key ) = > Keys . Contains ( key ) ;
2018-01-20 20:19:18 -05:00
/// <summary>
/// Copies the elements of the <see cref="ICollection{T}"/> to an <see cref="T:System.Array"/>, starting at a particular
/// <see cref="T:System.Array"/> index.
/// </summary>
/// <param name="array">
/// The one-dimensional <see cref="T:System.Array"/> that is the destination of the elements copied from
/// <see cref="ICollection{T}"/>. The <see cref="T:System.Array"/> must have zero-based indexing.
/// </param>
/// <param name="arrayIndex">The zero-based index in <paramref name="array"/> at which copying begins.</param>
/// <exception cref="ArgumentOutOfRangeException">arrayIndex - The number of items exceeds the length of the supplied array.</exception>
/// <exception cref="ArgumentNullException">array</exception>
2017-11-27 13:11:20 -05:00
public void CopyTo ( KeyValuePair < PROPERTYKEY , object > [ ] array , int arrayIndex )
{
if ( array . Length < ( arrayIndex + Count ) )
2018-01-27 16:40:26 -05:00
throw new ArgumentOutOfRangeException ( nameof ( arrayIndex ) , "The number of items exceeds the length of the supplied array." ) ;
2019-05-08 16:59:52 -04:00
if ( array is null )
2017-11-27 13:11:20 -05:00
throw new ArgumentNullException ( nameof ( array ) ) ;
var i = arrayIndex ;
foreach ( var kv in this )
array [ i + + ] = kv ;
}
2018-01-20 20:19:18 -05:00
/// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
2019-05-08 21:01:44 -04:00
public virtual void Dispose ( ) = > Commit ( ) ;
2017-11-27 13:11:20 -05:00
2018-01-20 20:19:18 -05:00
/// <summary>Gets the property.</summary>
/// <typeparam name="TVal">The type of the value.</typeparam>
/// <param name="key">The key.</param>
/// <returns>The cast value of the property.</returns>
/// <exception cref="ArgumentOutOfRangeException">key</exception>
2019-05-08 16:59:52 -04:00
public TVal GetProperty < TVal > ( PROPERTYKEY key ) = > TryGetValue < TVal > ( key , out var ret ) ? ret : throw new ArgumentOutOfRangeException ( nameof ( key ) ) ;
2017-11-27 13:11:20 -05:00
2018-01-27 16:40:26 -05:00
/// <summary>Gets the string value of the property.</summary>
/// <param name="key">The key.</param>
/// <param name="flags">The formatting flags.</param>
/// <returns>The string value of the property.</returns>
/// <exception cref="ArgumentOutOfRangeException">key</exception>
2019-05-08 16:59:52 -04:00
public string GetPropertyString ( PROPERTYKEY key , PROPDESC_FORMAT_FLAGS flags = PROPDESC_FORMAT_FLAGS . PDFF_DEFAULT ) = >
TryGetValue ( key , out PROPVARIANT ret ) ? PropertyDescription . Create ( key ) ? . FormatForDisplay ( ret , flags ) : throw new ArgumentOutOfRangeException ( nameof ( key ) ) ;
2018-01-27 16:40:26 -05:00
/// <summary>Gets the PROPVARIANT value for a key.</summary>
/// <param name="key">The key.</param>
/// <returns>The PROPVARIANT value.</returns>
/// <exception cref="ArgumentOutOfRangeException">key</exception>
2019-05-08 16:59:52 -04:00
public PROPVARIANT GetPropVariant ( PROPERTYKEY key ) = > TryGetValue ( key , out PROPVARIANT ret ) ? ret : throw new ArgumentOutOfRangeException ( nameof ( key ) ) ;
2018-01-27 16:40:26 -05:00
2018-01-20 20:19:18 -05:00
/// <summary>Gets the value associated with the specified key.</summary>
/// <param name="key">The key whose value to get.</param>
/// <param name="value">
2019-05-08 16:59:52 -04:00
/// 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 <paramref name="value"/> parameter. This parameter is passed uninitialized.
2018-01-20 20:19:18 -05:00
/// </param>
/// <returns>
2019-05-08 16:59:52 -04:00
/// <see langword="true"/> if the object that implements <see cref="IDictionary{PROPERTYKEY, Object}"/> contains an element with the
/// specified key; otherwise, <see langword="false"/>.
2018-01-20 20:19:18 -05:00
/// </returns>
2019-05-08 16:59:52 -04:00
public bool TryGetValue ( PROPERTYKEY key , out object value ) = > TryGetValue < object > ( key , out value ) ;
2018-01-20 20:19:18 -05:00
/// <summary>Gets the value associated with the specified key.</summary>
/// <typeparam name="TVal">The type of the returned value.</typeparam>
/// <param name="key">The key whose value to get.</param>
/// <param name="value">
2019-05-08 16:59:52 -04:00
/// 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 <paramref name="value"/> parameter. This parameter is passed uninitialized.
2018-01-20 20:19:18 -05:00
/// </param>
/// <returns>
2019-05-08 16:59:52 -04:00
/// <see langword="true"/> if the object that implements <see cref="IDictionary{PROPERTYKEY, Object}"/> contains an element with the
/// specified key; otherwise, <see langword="false"/>.
2018-01-20 20:19:18 -05:00
/// </returns>
2019-05-08 16:59:52 -04:00
public virtual bool TryGetValue < TVal > ( PROPERTYKEY key , out TVal value )
2018-01-20 20:19:18 -05:00
{
2019-05-08 21:01:44 -04:00
var result = Run ( ps = >
{
var ret = TryGetValue < TVal > ( ps , key , out var val ) ;
return ( ret , ret ? val : default ) ;
} ) ;
value = result . Item2 ;
return result . ret ;
2018-01-20 20:19:18 -05:00
}
/// <summary>Adds an item to the <see cref="ICollection{T}"/>.</summary>
/// <param name="item">The object to add to the <see cref="ICollection{T}"/>.</param>
2019-05-08 16:59:52 -04:00
void ICollection < KeyValuePair < PROPERTYKEY , object > > . Add ( KeyValuePair < PROPERTYKEY , object > item ) = > Add ( item . Key , item . Value ) ;
2018-01-20 20:19:18 -05:00
/// <summary>Removes all items from the <see cref="ICollection{T}"/>.</summary>
/// <exception cref="InvalidOperationException"></exception>
2019-05-08 16:59:52 -04:00
void ICollection < KeyValuePair < PROPERTYKEY , object > > . Clear ( ) = > throw new InvalidOperationException ( ) ;
2018-01-20 20:19:18 -05:00
/// <summary>Determines whether the <see cref="ICollection{T}"/> contains a specific value.</summary>
/// <param name="item">The object to locate in the <see cref="ICollection{T}"/>.</param>
2019-05-08 16:59:52 -04:00
/// <returns><see langword="true"/> if <paramref name="item"/> is found in the <see cref="ICollection{T}"/>; otherwise, <see langword="false"/>.</returns>
2019-05-08 21:01:44 -04:00
bool ICollection < KeyValuePair < PROPERTYKEY , object > > . Contains ( KeyValuePair < PROPERTYKEY , object > item ) = > Run ( ps = > TryGetValue ( ps , item . Key , out object o ) & & Equals ( o , item . Value ) ) ;
2018-01-20 20:19:18 -05:00
2019-05-08 16:59:52 -04:00
/// <summary>Returns an enumerator that iterates through the collection.</summary>
/// <returns>A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to iterate through the collection.</returns>
2019-05-08 21:01:44 -04:00
IEnumerator < KeyValuePair < PROPERTYKEY , object > > IEnumerable < KeyValuePair < PROPERTYKEY , object > > . GetEnumerator ( ) = > Run ( ps = > GetKeyEnum ( ps ) . Select ( k = > new KeyValuePair < PROPERTYKEY , object > ( k , TryGetValue ( ps , k , out object pv ) ? pv : null ) ) . GetEnumerator ( ) ) ;
2019-05-08 16:59:52 -04:00
2018-01-20 20:19:18 -05:00
/// <summary>Returns an enumerator that iterates through a collection.</summary>
/// <returns>An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.</returns>
2019-05-08 16:59:52 -04:00
IEnumerator IEnumerable . GetEnumerator ( ) = > ( ( IEnumerable < KeyValuePair < PROPERTYKEY , object > > ) this ) . GetEnumerator ( ) ;
2018-01-20 20:19:18 -05:00
/// <summary>Removes the element with the specified key from the <see cref="IDictionary{PROPERTYKEY, Object}"/>.</summary>
/// <param name="key">The key of the element to remove.</param>
/// <returns>
2019-05-08 16:59:52 -04:00
/// <see langword="true"/> if the element is successfully removed; otherwise, <see langword="false"/>. This method also returns
/// <see langword="false"/> if <paramref name="key"/> was not found in the original <see cref="IDictionary{PROPERTYKEY, Object}"/>.
2018-01-20 20:19:18 -05:00
/// </returns>
/// <exception cref="InvalidOperationException"></exception>
2019-05-08 16:59:52 -04:00
bool IDictionary < PROPERTYKEY , object > . Remove ( PROPERTYKEY key ) = > throw new InvalidOperationException ( ) ;
2018-01-20 20:19:18 -05:00
/// <summary>Removes the first occurrence of a specific object from the <see cref="ICollection{T}"/>.</summary>
/// <param name="item">The object to remove from the <see cref="ICollection{T}"/>.</param>
/// <returns>
2019-05-08 16:59:52 -04:00
/// <see langword="true"/> if <paramref name="item"/> was successfully removed from the <see cref="ICollection{T}"/>; otherwise,
/// <see langword="false"/>. This method also returns <see langword="false"/> if <paramref name="item"/> is not found in the original <see cref="ICollection{T}"/>.
2018-01-20 20:19:18 -05:00
/// </returns>
/// <exception cref="InvalidOperationException"></exception>
2019-05-08 16:59:52 -04:00
bool ICollection < KeyValuePair < PROPERTYKEY , object > > . Remove ( KeyValuePair < PROPERTYKEY , object > item ) = > throw new InvalidOperationException ( ) ;
/// <summary>Gets an enumeration of the keys in the property store.</summary>
/// <returns>Keys in the property store.</returns>
2019-05-08 21:01:44 -04:00
protected virtual IEnumerable < PROPERTYKEY > GetKeyEnum ( IPropertyStore ps )
2019-05-08 16:59:52 -04:00
{
2019-05-08 21:01:44 -04:00
if ( ps is null ) yield break ;
2019-05-08 16:59:52 -04:00
for ( uint i = 0 ; i < Count ; i + + )
2019-05-08 21:01:44 -04:00
yield return ps . GetAt ( i ) ;
2019-05-08 16:59:52 -04:00
}
2018-01-20 20:19:18 -05:00
/// <summary>Called when a property has changed.</summary>
protected virtual void OnPropertyChanged ( string propertyName )
{
IsDirty = true ;
PropertyChanged ? . Invoke ( this , new PropertyChangedEventArgs ( propertyName ) ) ;
}
2018-01-27 16:40:26 -05:00
2019-05-08 21:01:44 -04:00
/// <summary>
/// Gets the value associated with the specified key.
/// </summary>
/// <param name="ps">The IPropertyStore instance.</param>
2019-05-08 16:59:52 -04:00
/// <param name="key">The key whose value to get.</param>
2019-05-08 21:01:44 -04:00
/// <param name="value">When this method returns, the value associated with the specified key, if the key is found; otherwise, an empty PROPVARIANT.</param>
/// <returns>
/// <see langword="true" /> if the property store contains an element with the specified key; otherwise, <see langword="false" />.
/// </returns>
protected virtual bool TryGetValue < T > ( IPropertyStore ps , PROPERTYKEY key , out T value )
2018-01-27 16:40:26 -05:00
{
2019-05-08 21:01:44 -04:00
value = default ;
if ( ps ! = null )
2018-01-27 16:40:26 -05:00
{
try
{
2019-05-08 21:01:44 -04:00
var pv = new PROPVARIANT ( ) ;
ps . GetValue ( key , pv ) ;
value = ( T ) pv . Value ;
pv . Clear ( ) ;
2018-01-27 16:40:26 -05:00
return true ;
}
catch { }
}
return false ;
}
2019-05-08 21:01:44 -04:00
private void Run ( Action < IPropertyStore > action )
{
var ps = IPropStoreInst ;
try { action ( ps ) ; }
finally { Marshal . FinalReleaseComObject ( ps ) ; }
}
private T Run < T > ( Func < IPropertyStore , T > action )
{
var ps = IPropStoreInst ;
try { return action ( ps ) ; }
finally { Marshal . FinalReleaseComObject ( ps ) ; }
}
2017-11-27 13:11:20 -05:00
}
}