diff --git a/Windows.Shell.Common/ShellProperties/PropertyDescription.cs b/Windows.Shell.Common/ShellProperties/PropertyDescription.cs index 9d46e9ad..0aa08354 100644 --- a/Windows.Shell.Common/ShellProperties/PropertyDescription.cs +++ b/Windows.Shell.Common/ShellProperties/PropertyDescription.cs @@ -29,11 +29,34 @@ public class PropertyDescription : IDisposable key = pkey ?? iDesc.GetPropertyKey(); } + /// Initializes a new instance of the class from a specified property key. + /// The property key. + public PropertyDescription(PROPERTYKEY propkey) + { + PSGetPropertyDescription(propkey, typeof(IPropertyDescription).GUID, out var ppv).ThrowIfFailed(); + iDesc = (IPropertyDescription)ppv; + key = propkey; + } + + /// Initializes a new instance of the class from a property name. + /// A string that identifies the property. + public PropertyDescription(string name) + { + PSGetPropertyDescriptionByName(name, typeof(IPropertyDescription).GUID, out var ppv).ThrowIfFailed(); + iDesc = (IPropertyDescription)ppv; + key = iDesc.GetPropertyKey(); + } + /// Creates a instance from a specified property key. /// The property key. /// An associated instance of or if the PROPERTYKEY does not exist in the schema subsystem cache. public static PropertyDescription? Create(PROPERTYKEY propkey) => PSGetPropertyDescription(propkey, typeof(IPropertyDescription).GUID, out var ppv).Succeeded ? new PropertyDescription((IPropertyDescription)ppv, propkey) : null; + /// Creates a instance from a property key name. + /// A string that identifies the property. + /// An associated instance of or if the PROPERTYKEY does not exist in the schema subsystem cache. + public static PropertyDescription? Create(string name) => PSGetPropertyDescriptionByName(name, typeof(IPropertyDescription).GUID, out var ppv).Succeeded ? new PropertyDescription((IPropertyDescription)ppv) : null; + /// Tries to create a instance from a specified property key. /// The property key. /// @@ -152,7 +175,7 @@ public class PropertyDescription : IDisposable /// An IconLocation for the image associated with the property value. 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(); } diff --git a/Windows.Shell.Common/ShellProperties/PropertyStore.cs b/Windows.Shell.Common/ShellProperties/PropertyStore.cs index 8e66aaff..a2018a7d 100644 --- a/Windows.Shell.Common/ShellProperties/PropertyStore.cs +++ b/Windows.Shell.Common/ShellProperties/PropertyStore.cs @@ -10,13 +10,23 @@ namespace Vanara.Windows.Shell; /// Encapsulates the IPropertyStore object. /// /// -public abstract class PropertyStore : ReadOnlyPropertyStore, IDictionary, IDisposable, INotifyPropertyChanged +public class PropertyStore : ReadOnlyPropertyStore, IDictionary, IDisposable, INotifyPropertyChanged { private bool noICapabilities = false; private IPropertyStoreCapabilities? pCapabilities; /// Initializes a new instance of the class. - protected PropertyStore() { } + protected PropertyStore() => flags |= GETPROPERTYSTOREFLAGS.GPS_READWRITE; + + /// Initializes a new instance of the class from a file system path. + /// A string that specifies the item path. + /// The optional property changed handler. + public PropertyStore(string path, PropertyChangedEventHandler? propChangedHandler = null) : base(path) + { + flags |= GETPROPERTYSTOREFLAGS.GPS_READWRITE; + if (propChangedHandler != null) + PropertyChanged += propChangedHandler; + } /// Occurs when a property value changes. public event PropertyChangedEventHandler? PropertyChanged; @@ -25,15 +35,14 @@ public abstract class PropertyStore : ReadOnlyPropertyStore, IDictionarytrue if this instance is dirty; otherwise, false. public bool IsDirty { get; protected set; } - /// - public override bool IsReadOnly => false; - /// Gets an containing the keys of the . public new ICollection Keys => base.Keys.ToList(); /// Gets an containing the values in the . public new ICollection Values => base.Values.ToList(); + bool ICollection>.IsReadOnly => ReadOnly; + /// 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"}. @@ -61,7 +70,7 @@ public abstract class PropertyStore : ReadOnlyPropertyStore, IDictionary 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) diff --git a/Windows.Shell.Common/ShellProperties/ReadOnlyPropertyStore.cs b/Windows.Shell.Common/ShellProperties/ReadOnlyPropertyStore.cs index 29087e59..e7985ba6 100644 --- a/Windows.Shell.Common/ShellProperties/ReadOnlyPropertyStore.cs +++ b/Windows.Shell.Common/ShellProperties/ReadOnlyPropertyStore.cs @@ -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; /// Encapsulates the IPropertyStore object. /// /// -public abstract class ReadOnlyPropertyStore : IReadOnlyDictionary, IDisposable +public class ReadOnlyPropertyStore : IReadOnlyDictionary, IDisposable { + /// The flags. + protected GETPROPERTYSTOREFLAGS flags = GETPROPERTYSTOREFLAGS.GPS_BESTEFFORT; + + /// An optional IBindCtx object, which provides access to a bind context. + protected System.Runtime.InteropServices.ComTypes.IBindCtx? iBindCtx = null; + /// The IPropertyStore instance. protected IPropertyStore? iPropertyStore; + /// If specified, the path to the file system item. + protected string? itemPath = null; + private PropertyDescriptionDictionary? descriptions; - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. protected ReadOnlyPropertyStore() { } + /// Returns a property store for an item, given a path or parsing name. + /// A string that specifies the item path. + /// One or more values from the GETPROPERTYSTOREFLAGS constants. + /// An optional IBindCtx object, which provides access to a bind context. + 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; + } + + /// Initializes a new instance of the class from a file system path. + /// A string that specifies the item path. + public ReadOnlyPropertyStore(string path) : this(path, GETPROPERTYSTOREFLAGS.GPS_DEFAULT) { } + /// Gets the number of properties in the current property store. public int Count => Run(ps => (int)(ps?.GetCount() ?? 0)); @@ -28,8 +55,77 @@ public abstract class ReadOnlyPropertyStore : IReadOnlyDictionaryThe 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 or sets a value indicating whether to include slow properties. + /// true if including slow properties; otherwise, false. + [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); + } + } + + /// Gets or sets a value indicating whether to include only properties directly from the property handler. + /// true if no inherited properties; otherwise, false. + [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); + } + } + + /// Gets or sets a value indicating whether properties can be read and written. + /// true if properties are read/write; otherwise, false. + [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); + } + } + + /// + /// Gets or sets a value indicating whether this 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. + /// + /// true if temporary; otherwise, false. + [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); + } + } + } /// Gets an containing the keys of the . public IEnumerable Keys => Run(ps => GetKeyEnum(ps).ToList())!; @@ -170,7 +266,7 @@ public abstract class ReadOnlyPropertyStore : IReadOnlyDictionaryReturns 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(); + (Run(ps => GetKeyEnum(ps).Select(k => new KeyValuePair(k, TryGetValue(ps, k, out object? pv) ? pv : null))) ?? []).GetEnumerator(); /// Returns an enumerator that iterates through a collection. /// An object that can be used to iterate through the collection. @@ -191,7 +287,12 @@ public abstract class ReadOnlyPropertyStore : IReadOnlyDictionaryThe IPropertyStore instance. This can be null. - 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; + } /// Gets an enumeration of the keys in the property store. /// Keys in the property store. @@ -221,24 +322,20 @@ public abstract class ReadOnlyPropertyStore : IReadOnlyDictionary + private class PropertyDescriptionDictionary(ReadOnlyPropertyStore ps) : IReadOnlyDictionary { - private readonly ReadOnlyPropertyStore store; + public IEnumerable Values => ps.Keys.Select(GetPropertyDescription).ToList(); - public PropertyDescriptionDictionary(ReadOnlyPropertyStore ps) => store = ps; + public int Count => ps.Count; - public IEnumerable Values => store.Keys.Select(GetPropertyDescription).ToList(); - - public int Count => store.Count; - - public IEnumerable Keys => store.Keys; + public IEnumerable 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> GetEnumerator() => - store.Keys.Select(k => new KeyValuePair(k, GetPropertyDescription(k))).ToList().GetEnumerator(); + ps.Keys.Select(k => new KeyValuePair(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 : IReadOnlyDictionaryThe shell item - private ShellItem shellItem; - - /// The flags. - private GETPROPERTYSTOREFLAGS flags = GETPROPERTYSTOREFLAGS.GPS_BESTEFFORT; + private readonly ShellItem shellItem; /// Initializes a new instance of the class. /// The ShellItem instance. @@ -35,39 +31,6 @@ public sealed class ShellItemPropertyStore : PropertyStore [DefaultValue(null)] public ICreateObject? Creator { get; set; } - /// Gets or sets a value indicating whether to include slow properties. - /// true if including slow properties; otherwise, false. - [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); - } - } - - /// Gets a value indicating whether the is read-only. - public override bool IsReadOnly => ReadOnly; - - /// Gets or sets a value indicating whether to include only properties directly from the property handler. - /// true if no inherited properties; otherwise, false. - [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); - } - } - /// /// Gets or sets a set of properties used to filter the property store. This value can be . This value will be /// ignored if is also set. @@ -76,48 +39,6 @@ public sealed class ShellItemPropertyStore : PropertyStore [DefaultValue(null)] public PROPERTYKEY[]? PropertyFilter { get; set; } - /// Gets or sets a value indicating whether properties can be read and written. - /// true if properties are read/write; otherwise, false. - [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); - } - } - - /// - /// Gets or sets a value indicating whether this 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. - /// - /// true if temporary; otherwise, false. - [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); - } - } - } - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. public override void Dispose() {