Exposed ReadOnlyPropertyStore and PropertyStore as public non-abstract classes with constructors to get properties directly for a file system item. Adjusted properties and methods from ShellItemPropertyStore to lower-level classes.

master
David Hall 2024-06-06 11:17:18 -06:00
parent eff69a6fe8
commit fea9ff85c4
4 changed files with 156 additions and 106 deletions

View File

@ -29,11 +29,34 @@ public class PropertyDescription : IDisposable
key = pkey ?? iDesc.GetPropertyKey();
}
/// <summary>Initializes a new instance of the <see cref="PropertyDescription"/> class from a specified property key.</summary>
/// <param name="propkey">The property key.</param>
public PropertyDescription(PROPERTYKEY propkey)
{
PSGetPropertyDescription(propkey, typeof(IPropertyDescription).GUID, out var ppv).ThrowIfFailed();
iDesc = (IPropertyDescription)ppv;
key = propkey;
}
/// <summary>Initializes a new instance of the <see cref="PropertyDescription"/> class from a property name.</summary>
/// <param name="name">A string that identifies the property.</param>
public PropertyDescription(string name)
{
PSGetPropertyDescriptionByName(name, typeof(IPropertyDescription).GUID, out var ppv).ThrowIfFailed();
iDesc = (IPropertyDescription)ppv;
key = iDesc.GetPropertyKey();
}
/// <summary>Creates a <see cref="PropertyDescription"/> instance from a specified property key.</summary>
/// <param name="propkey">The property key.</param>
/// <returns>An associated instance of <see cref="PropertyDescription"/> or <see langword="null"/> if the PROPERTYKEY does not exist in the schema subsystem cache.</returns>
public static PropertyDescription? Create(PROPERTYKEY propkey) => PSGetPropertyDescription(propkey, typeof(IPropertyDescription).GUID, out var ppv).Succeeded ? new PropertyDescription((IPropertyDescription)ppv, propkey) : null;
/// <summary>Creates a <see cref="PropertyDescription"/> instance from a property key name.</summary>
/// <param name="name">A string that identifies the property.</param>
/// <returns>An associated instance of <see cref="PropertyDescription"/> or <see langword="null"/> if the PROPERTYKEY does not exist in the schema subsystem cache.</returns>
public static PropertyDescription? Create(string name) => PSGetPropertyDescriptionByName(name, typeof(IPropertyDescription).GUID, out var ppv).Succeeded ? new PropertyDescription((IPropertyDescription)ppv) : null;
/// <summary>Tries to create a <see cref="PropertyDescription"/> instance from a specified property key.</summary>
/// <param name="propkey">The property key.</param>
/// <param name="desc">
@ -152,7 +175,7 @@ public class PropertyDescription : IDisposable
/// <returns>An IconLocation for the image associated with the property value.</returns>
public IconLocation GetImageLocationForValue(object obj)
{
if (iDesc2 == null) iDesc2 = iDesc as IPropertyDescription2;
iDesc2 ??= iDesc as IPropertyDescription2;
return iDesc2 != null && IconLocation.TryParse(iDesc2.GetImageReferenceForValue(new PROPVARIANT(obj), out var img).Succeeded ? img : null, out var loc) ? loc : new IconLocation();
}

View File

@ -10,13 +10,23 @@ namespace Vanara.Windows.Shell;
/// <summary>Encapsulates the IPropertyStore object.</summary>
/// <seealso cref="IDictionary{PROPERTYKEY, Object}"/>
/// <seealso cref="IDisposable"/>
public abstract class PropertyStore : ReadOnlyPropertyStore, IDictionary<PROPERTYKEY, object?>, IDisposable, INotifyPropertyChanged
public class PropertyStore : ReadOnlyPropertyStore, IDictionary<PROPERTYKEY, object?>, IDisposable, INotifyPropertyChanged
{
private bool noICapabilities = false;
private IPropertyStoreCapabilities? pCapabilities;
/// <summary>Initializes a new instance of the <see cref="PropertyStore"/> class.</summary>
protected PropertyStore() { }
protected PropertyStore() => flags |= GETPROPERTYSTOREFLAGS.GPS_READWRITE;
/// <summary>Initializes a new instance of the <see cref="PropertyStore"/> class from a file system path.</summary>
/// <param name="path">A string that specifies the item path.</param>
/// <param name="propChangedHandler">The optional property changed handler.</param>
public PropertyStore(string path, PropertyChangedEventHandler? propChangedHandler = null) : base(path)
{
flags |= GETPROPERTYSTOREFLAGS.GPS_READWRITE;
if (propChangedHandler != null)
PropertyChanged += propChangedHandler;
}
/// <summary>Occurs when a property value changes.</summary>
public event PropertyChangedEventHandler? PropertyChanged;
@ -25,15 +35,14 @@ public abstract class PropertyStore : ReadOnlyPropertyStore, IDictionary<PROPERT
/// <value><c>true</c> if this instance is dirty; otherwise, <c>false</c>.</value>
public bool IsDirty { get; protected set; }
/// <inheritdoc/>
public override bool IsReadOnly => false;
/// <summary>Gets an <see cref="ICollection{T}"/> containing the keys of the <see cref="IDictionary{PROPERTYKEY, Object}"/>.</summary>
public new ICollection<PROPERTYKEY> Keys => base.Keys.ToList();
/// <summary>Gets an <see cref="ICollection{T}"/> containing the values in the <see cref="IDictionary{PROPERTYKEY, Object}"/>.</summary>
public new ICollection<object?> Values => base.Values.ToList();
bool ICollection<KeyValuePair<PROPERTYKEY, object?>>.IsReadOnly => ReadOnly;
/// <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>
@ -61,7 +70,7 @@ public abstract class PropertyStore : ReadOnlyPropertyStore, IDictionary<PROPERT
{
if (ps is null)
throw new InvalidOperationException("Property store does not exist.");
if (IsReadOnly)
if (ReadOnly)
throw new InvalidOperationException("Property store is read-only.");
ps.SetValue(key, value, false);
OnPropertyChanged(key.ToString());
@ -94,7 +103,7 @@ public abstract class PropertyStore : ReadOnlyPropertyStore, IDictionary<PROPERT
/// </remarks>
public bool IsPropertyWritable(in PROPERTYKEY key)
{
if (IsReadOnly) return false;
if (ReadOnly) return false;
// Check for an IPropertyStoreCapabilities if possible
if (pCapabilities is null && !noICapabilities)

View File

@ -1,26 +1,53 @@
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using Vanara.PInvoke;
using static Vanara.PInvoke.Ole32;
using static Vanara.PInvoke.PropSys;
using static Vanara.PInvoke.Shell32;
namespace Vanara.Windows.Shell;
/// <summary>Encapsulates the IPropertyStore object.</summary>
/// <seealso cref="IDictionary{PROPERTYKEY, Object}"/>
/// <seealso cref="IDisposable"/>
public abstract class ReadOnlyPropertyStore : IReadOnlyDictionary<PROPERTYKEY, object?>, IDisposable
public class ReadOnlyPropertyStore : IReadOnlyDictionary<PROPERTYKEY, object?>, IDisposable
{
/// <summary>The flags.</summary>
protected GETPROPERTYSTOREFLAGS flags = GETPROPERTYSTOREFLAGS.GPS_BESTEFFORT;
/// <summary>An optional IBindCtx object, which provides access to a bind context.</summary>
protected System.Runtime.InteropServices.ComTypes.IBindCtx? iBindCtx = null;
/// <summary>The IPropertyStore instance.</summary>
protected IPropertyStore? iPropertyStore;
/// <summary>If specified, the path to the file system item.</summary>
protected string? itemPath = null;
private PropertyDescriptionDictionary? descriptions;
/// <summary>Initializes a new instance of the <see cref="PropertyStore"/> class.</summary>
/// <summary>Initializes a new instance of the <see cref="ReadOnlyPropertyStore"/> class.</summary>
protected ReadOnlyPropertyStore() { }
/// <summary>Returns a property store for an item, given a path or parsing name.</summary>
/// <param name="path">A string that specifies the item path.</param>
/// <param name="flags">One or more values from the GETPROPERTYSTOREFLAGS constants.</param>
/// <param name="pbc">An optional IBindCtx object, which provides access to a bind context.</param>
protected ReadOnlyPropertyStore(string path, GETPROPERTYSTOREFLAGS? flags = null, System.Runtime.InteropServices.ComTypes.IBindCtx? pbc = null)
{
itemPath = Path.GetFullPath(Environment.ExpandEnvironmentVariables(path));
if (flags.HasValue) this.flags = flags.Value;
iBindCtx = pbc;
}
/// <summary>Initializes a new instance of the <see cref="ReadOnlyPropertyStore"/> class from a file system path.</summary>
/// <param name="path">A string that specifies the item path.</param>
public ReadOnlyPropertyStore(string path) : this(path, GETPROPERTYSTOREFLAGS.GPS_DEFAULT) { }
/// <summary>Gets the number of properties in the current property store.</summary>
public int Count => Run(ps => (int)(ps?.GetCount() ?? 0));
@ -28,8 +55,77 @@ public abstract class ReadOnlyPropertyStore : IReadOnlyDictionary<PROPERTYKEY, o
/// <value>The property descriptions.</value>
public virtual IReadOnlyDictionary<PROPERTYKEY, PropertyDescription> Descriptions => descriptions ??= new PropertyDescriptionDictionary(this);
/// <summary>Gets a value indicating whether the <see cref="ICollection{T}"/> is read-only.</summary>
public virtual bool IsReadOnly => true;
/// <summary>Gets or sets a value indicating whether to include slow properties.</summary>
/// <value><c>true</c> if including slow properties; otherwise, <c>false</c>.</value>
[DefaultValue(false)]
public bool IncludeSlow
{
get => flags.IsFlagSet(GETPROPERTYSTOREFLAGS.GPS_OPENSLOWITEM);
set
{
if (IncludeSlow == value) return;
flags = flags.SetFlags(GETPROPERTYSTOREFLAGS.GPS_OPENSLOWITEM, value);
if (value)
flags = flags.SetFlags(GETPROPERTYSTOREFLAGS.GPS_TEMPORARY | GETPROPERTYSTOREFLAGS.GPS_FASTPROPERTIESONLY, false);
}
}
/// <summary>Gets or sets a value indicating whether to include only properties directly from the property handler.</summary>
/// <value><c>true</c> if no inherited properties; otherwise, <c>false</c>.</value>
[DefaultValue(false)]
public bool NoInheritedProperties
{
get => flags.IsFlagSet(GETPROPERTYSTOREFLAGS.GPS_HANDLERPROPERTIESONLY);
set
{
if (NoInheritedProperties == value) return;
flags = flags.SetFlags(GETPROPERTYSTOREFLAGS.GPS_HANDLERPROPERTIESONLY, value);
if (value)
flags = flags.SetFlags(GETPROPERTYSTOREFLAGS.GPS_TEMPORARY | GETPROPERTYSTOREFLAGS.GPS_BESTEFFORT | GETPROPERTYSTOREFLAGS.GPS_FASTPROPERTIESONLY, false);
}
}
/// <summary>Gets or sets a value indicating whether properties can be read and written.</summary>
/// <value><c>true</c> if properties are read/write; otherwise, <c>false</c>.</value>
[DefaultValue(true)]
public bool ReadOnly
{
get => !flags.IsFlagSet(GETPROPERTYSTOREFLAGS.GPS_READWRITE);
set
{
if (ReadOnly == value) return;
flags = flags.SetFlags(GETPROPERTYSTOREFLAGS.GPS_READWRITE, !value);
if (!value)
flags = flags.SetFlags(GETPROPERTYSTOREFLAGS.GPS_DELAYCREATION | GETPROPERTYSTOREFLAGS.GPS_TEMPORARY | GETPROPERTYSTOREFLAGS.GPS_BESTEFFORT | GETPROPERTYSTOREFLAGS.GPS_FASTPROPERTIESONLY, false);
else
flags = flags.SetFlags(GETPROPERTYSTOREFLAGS.GPS_HANDLERPROPERTIESONLY);
}
}
/// <summary>
/// Gets or sets a value indicating whether this <see cref="ShellItemPropertyStore"/> provides a writable store, with no initial
/// properties, that exists for the lifetime of the Shell item instance; basically, a property bag attached to the item instance.
/// </summary>
/// <value><c>true</c> if temporary; otherwise, <c>false</c>.</value>
[DefaultValue(false)]
public bool Temporary
{
get => flags.IsFlagSet(GETPROPERTYSTOREFLAGS.GPS_TEMPORARY);
set
{
if (Temporary == value) return;
flags = flags.SetFlags(GETPROPERTYSTOREFLAGS.GPS_TEMPORARY, value);
if (value)
{
flags = GETPROPERTYSTOREFLAGS.GPS_TEMPORARY;
ReadOnly = false;
}
else
{
flags = flags.SetFlags(GETPROPERTYSTOREFLAGS.GPS_TEMPORARY, false);
}
}
}
/// <summary>Gets an <see cref="IEnumerable{T}"/> containing the keys of the <see cref="IReadOnlyDictionary{PROPERTYKEY, Object}"/>.</summary>
public IEnumerable<PROPERTYKEY> Keys => Run(ps => GetKeyEnum(ps).ToList())!;
@ -170,7 +266,7 @@ public abstract class ReadOnlyPropertyStore : IReadOnlyDictionary<PROPERTYKEY, o
/// <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>
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))) ?? new KeyValuePair<PROPERTYKEY, object?>[0]).GetEnumerator();
(Run(ps => GetKeyEnum(ps).Select(k => new KeyValuePair<PROPERTYKEY, object?>(k, TryGetValue(ps, k, out object? pv) ? pv : null))) ?? []).GetEnumerator();
/// <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>
@ -191,7 +287,12 @@ public abstract class ReadOnlyPropertyStore : IReadOnlyDictionary<PROPERTYKEY, o
}
/// <summary>The IPropertyStore instance. This can be null.</summary>
protected virtual IPropertyStore? GetIPropertyStore() => iPropertyStore;
protected virtual IPropertyStore? GetIPropertyStore()
{
if (iPropertyStore is null && itemPath is not null)
SHGetPropertyStoreFromParsingName(itemPath, iBindCtx, flags, typeof(IPropertyStore).GUID, out iPropertyStore).ThrowIfFailed();
return iPropertyStore;
}
/// <summary>Gets an enumeration of the keys in the property store.</summary>
/// <returns>Keys in the property store.</returns>
@ -221,24 +322,20 @@ public abstract class ReadOnlyPropertyStore : IReadOnlyDictionary<PROPERTYKEY, o
return iPropertyStore is null ? default : action.Invoke(iPropertyStore);
}
private class PropertyDescriptionDictionary : IReadOnlyDictionary<PROPERTYKEY, PropertyDescription>
private class PropertyDescriptionDictionary(ReadOnlyPropertyStore ps) : IReadOnlyDictionary<PROPERTYKEY, PropertyDescription>
{
private readonly ReadOnlyPropertyStore store;
public IEnumerable<PropertyDescription> Values => ps.Keys.Select(GetPropertyDescription).ToList();
public PropertyDescriptionDictionary(ReadOnlyPropertyStore ps) => store = ps;
public int Count => ps.Count;
public IEnumerable<PropertyDescription> Values => store.Keys.Select(GetPropertyDescription).ToList();
public int Count => store.Count;
public IEnumerable<PROPERTYKEY> Keys => store.Keys;
public IEnumerable<PROPERTYKEY> Keys => ps.Keys;
public PropertyDescription this[PROPERTYKEY key] => GetPropertyDescription(key);
public bool ContainsKey(PROPERTYKEY key) => store.ContainsKey(key);
public bool ContainsKey(PROPERTYKEY key) => ps.ContainsKey(key);
public IEnumerator<KeyValuePair<PROPERTYKEY, PropertyDescription>> GetEnumerator() =>
store.Keys.Select(k => new KeyValuePair<PROPERTYKEY, PropertyDescription>(k, GetPropertyDescription(k))).ToList().GetEnumerator();
ps.Keys.Select(k => new KeyValuePair<PROPERTYKEY, PropertyDescription>(k, GetPropertyDescription(k))).ToList().GetEnumerator();
#if NET40_OR_GREATER || NETSTANDARD2_0_OR_GREATER && !NET5_0_OR_GREATER
#nullable disable
@ -247,7 +344,7 @@ public abstract class ReadOnlyPropertyStore : IReadOnlyDictionary<PROPERTYKEY, o
public bool TryGetValue(PROPERTYKEY key, [MaybeNullWhen(false)] out PropertyDescription value)
#endif
{
if (store.ContainsKey(key))
if (ps.ContainsKey(key))
{
value = this[key];
return true;

View File

@ -1,5 +1,4 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel;
using Vanara.PInvoke;
using static Vanara.PInvoke.Ole32;
using static Vanara.PInvoke.PropSys;
@ -11,10 +10,7 @@ namespace Vanara.Windows.Shell;
public sealed class ShellItemPropertyStore : PropertyStore
{
/// <summary>The shell item</summary>
private ShellItem shellItem;
/// <summary>The flags.</summary>
private GETPROPERTYSTOREFLAGS flags = GETPROPERTYSTOREFLAGS.GPS_BESTEFFORT;
private readonly ShellItem shellItem;
/// <summary>Initializes a new instance of the <see cref="ShellItemPropertyStore"/> class.</summary>
/// <param name="item">The ShellItem instance.</param>
@ -35,39 +31,6 @@ public sealed class ShellItemPropertyStore : PropertyStore
[DefaultValue(null)]
public ICreateObject? Creator { get; set; }
/// <summary>Gets or sets a value indicating whether to include slow properties.</summary>
/// <value><c>true</c> if including slow properties; otherwise, <c>false</c>.</value>
[DefaultValue(false)]
public bool IncludeSlow
{
get => flags.IsFlagSet(GETPROPERTYSTOREFLAGS.GPS_OPENSLOWITEM);
set
{
if (IncludeSlow == value) return;
flags = flags.SetFlags(GETPROPERTYSTOREFLAGS.GPS_OPENSLOWITEM, value);
if (value)
flags = flags.SetFlags(GETPROPERTYSTOREFLAGS.GPS_TEMPORARY | GETPROPERTYSTOREFLAGS.GPS_FASTPROPERTIESONLY, false);
}
}
/// <summary>Gets a value indicating whether the <see cref="ICollection{T}"/> is read-only.</summary>
public override bool IsReadOnly => ReadOnly;
/// <summary>Gets or sets a value indicating whether to include only properties directly from the property handler.</summary>
/// <value><c>true</c> if no inherited properties; otherwise, <c>false</c>.</value>
[DefaultValue(false)]
public bool NoInheritedProperties
{
get => flags.IsFlagSet(GETPROPERTYSTOREFLAGS.GPS_HANDLERPROPERTIESONLY);
set
{
if (NoInheritedProperties == value) return;
flags = flags.SetFlags(GETPROPERTYSTOREFLAGS.GPS_HANDLERPROPERTIESONLY, value);
if (value)
flags = flags.SetFlags(GETPROPERTYSTOREFLAGS.GPS_TEMPORARY | GETPROPERTYSTOREFLAGS.GPS_BESTEFFORT | GETPROPERTYSTOREFLAGS.GPS_FASTPROPERTIESONLY, false);
}
}
/// <summary>
/// Gets or sets a set of properties used to filter the property store. This value can be <see langword="null"/>. This value will be
/// ignored if <see cref="Creator"/> is also set.
@ -76,48 +39,6 @@ public sealed class ShellItemPropertyStore : PropertyStore
[DefaultValue(null)]
public PROPERTYKEY[]? PropertyFilter { get; set; }
/// <summary>Gets or sets a value indicating whether properties can be read and written.</summary>
/// <value><c>true</c> if properties are read/write; otherwise, <c>false</c>.</value>
[DefaultValue(true)]
public bool ReadOnly
{
get => !flags.IsFlagSet(GETPROPERTYSTOREFLAGS.GPS_READWRITE);
set
{
if (ReadOnly == value) return;
flags = flags.SetFlags(GETPROPERTYSTOREFLAGS.GPS_READWRITE, !value);
if (!value)
flags = flags.SetFlags(GETPROPERTYSTOREFLAGS.GPS_DELAYCREATION | GETPROPERTYSTOREFLAGS.GPS_TEMPORARY | GETPROPERTYSTOREFLAGS.GPS_BESTEFFORT | GETPROPERTYSTOREFLAGS.GPS_FASTPROPERTIESONLY, false);
else
flags = flags.SetFlags(GETPROPERTYSTOREFLAGS.GPS_HANDLERPROPERTIESONLY);
}
}
/// <summary>
/// Gets or sets a value indicating whether this <see cref="ShellItemPropertyStore"/> provides a writable store, with no initial
/// properties, that exists for the lifetime of the Shell item instance; basically, a property bag attached to the item instance.
/// </summary>
/// <value><c>true</c> if temporary; otherwise, <c>false</c>.</value>
[DefaultValue(false)]
public bool Temporary
{
get => flags.IsFlagSet(GETPROPERTYSTOREFLAGS.GPS_TEMPORARY);
set
{
if (Temporary == value) return;
flags = flags.SetFlags(GETPROPERTYSTOREFLAGS.GPS_TEMPORARY, value);
if (value)
{
flags = GETPROPERTYSTOREFLAGS.GPS_TEMPORARY;
ReadOnly = false;
}
else
{
flags = flags.SetFlags(GETPROPERTYSTOREFLAGS.GPS_TEMPORARY, false);
}
}
}
/// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
public override void Dispose()
{