diff --git a/UnitTests/Shell/ShellItemTests.cs b/UnitTests/Shell/ShellItemTests.cs index dea943be..63d76b2e 100644 --- a/UnitTests/Shell/ShellItemTests.cs +++ b/UnitTests/Shell/ShellItemTests.cs @@ -66,10 +66,10 @@ namespace Vanara.Windows.Forms.Tests } [Test] - public void GetAttrTest() - { - Assert.That(() => - { + public void GetAttrTest() + { + Assert.That(() => + { using (var i = new ShellItem(testDoc)) { Assert.That(i.Attributes, Is.Not.Zero); @@ -84,7 +84,7 @@ namespace Vanara.Windows.Forms.Tests Assert.That(i.ToolTipText, Is.Not.Null); } }, Throws.Nothing); - } + } [Test] public void GetDisplayNameTest() @@ -137,6 +137,16 @@ namespace Vanara.Windows.Forms.Tests Assert.That(i.Properties[PROPERTYKEY.System.Author], Has.Member("TestAuthor")); Assert.That(i.Properties[PROPERTYKEY.System.ItemTypeText], Does.StartWith("Microsoft Word")); Assert.That(i.Properties[PROPERTYKEY.System.DateAccessed], Is.TypeOf()); + Assert.That(() => i.Properties[new PROPERTYKEY()], Throws.Exception); + Assert.That(() => i.Properties[new PROPERTYKEY(Guid.NewGuid(), 2)], Throws.Exception); + + Assert.That(i.Properties["System.Author"], Has.Member("TestAuthor")); + Assert.That(i.Properties["DocAuthor"], Has.Member("TestAuthor")); + Assert.That(() => i.Properties[null], Throws.Exception); + Assert.That(() => i.Properties["Arthur"], Throws.Exception); + + Assert.That(i.Properties.GetProperty(PROPERTYKEY.System.Company), Is.InstanceOf().And.StartWith("Microsoft")); + Assert.That(() => i.Properties.GetProperty(PROPERTYKEY.System.Company), Throws.Exception); } } @@ -147,15 +157,14 @@ namespace Vanara.Windows.Forms.Tests { using (var i = new ShellItem(testDoc)) { - var pk = PROPERTYKEY.System.Category; - Assert.That(() => i.GetPropertyDescriptionList(ref pk), Throws.Exception); - pk = PROPERTYKEY.System.PropList.FullDetails; - using (var pdl = i.GetPropertyDescriptionList(ref pk)) + Assert.That(() => i.GetPropertyDescriptionList(PROPERTYKEY.System.Category), Throws.Exception); + using (var pdl = i.GetPropertyDescriptionList(PROPERTYKEY.System.PropList.FullDetails)) { Assert.That(pdl.Count, Is.GreaterThan(0)); foreach (var d in pdl) { Assert.That(d.TypeFlags, Is.Not.Zero); + Debug.WriteLine($"Property '{d.DisplayName}' is of type '{d.PropertyType}'"); } } } @@ -190,12 +199,12 @@ namespace Vanara.Windows.Forms.Tests Assert.That(ps, Is.Not.Null.And.InstanceOf()); System.Runtime.InteropServices.Marshal.ReleaseComObject(ps); Assert.That(() => i.GetHandler(), Throws.TypeOf()); - } - } + } + } - [Test] - public void GetImageTest() - { + [Test] + public void GetImageTest() + { using (var i = new ShellItem(testDoc)) { var sz = new System.Drawing.Size(32, 32); @@ -207,8 +216,8 @@ namespace Vanara.Windows.Forms.Tests { var sz = new System.Drawing.Size(1024, 1024); var bmp = i.GetImage(sz, ShellItemGetImageOptions.ThumbnailOnly | ShellItemGetImageOptions.ScaleUp); - Assert.That(bmp.Size, Is.EqualTo(sz)); - } - } - } + Assert.That(bmp.Size, Is.EqualTo(sz)); + } + } + } } \ No newline at end of file diff --git a/Windows.Shell/PropertyDescription.cs b/Windows.Shell/PropertyDescription.cs index 7fb8cb75..4cae6d26 100644 --- a/Windows.Shell/PropertyDescription.cs +++ b/Windows.Shell/PropertyDescription.cs @@ -11,12 +11,17 @@ namespace Vanara.Windows.Shell { /// The IPropertyDescription object. protected IPropertyDescription iDesc; + /// The IPropertyDescription2 object. + protected IPropertyDescription2 iDesc2; + + /// Gets the type list. + protected PropertyTypeList typeList; /// Initializes a new instance of the class. /// The property description. - internal PropertyDescription(IPropertyDescription propertyDescription) + internal protected PropertyDescription(IPropertyDescription propertyDescription) { - iDesc = propertyDescription; + iDesc = propertyDescription ?? throw new ArgumentNullException(nameof(propertyDescription)); } /// Gets a value that describes how the property values are displayed when multiple items are selected in the UI. @@ -53,15 +58,9 @@ namespace Vanara.Windows.Shell /// Gets a structure that acts as a property's unique identifier. public PROPERTYKEY PropertyKey => iDesc.GetPropertyKey(); - /// Gets the variant type of the property. - public Type PropertyType - { - get - { - throw new NotImplementedException(); - //TODO: return iDesc.GetPropertyType().GetType(); - } - } + /// Gets the variant type of the property. If the type cannot be determined, this property returns null. + public Type PropertyType => PROPVARIANT.GetType(iDesc.GetPropertyType()); + /// Gets the relative description type for a property description. public PROPDESC_RELATIVEDESCRIPTION_TYPE RelativeDescriptionType => iDesc.GetRelativeDescriptionType(); @@ -71,6 +70,9 @@ namespace Vanara.Windows.Shell /// Gets a set of flags that describe the uses and capabilities of the property. public PROPDESC_TYPE_FLAGS TypeFlags => iDesc.GetTypeFlags(PROPDESC_TYPE_FLAGS.PDTF_MASK_ALL); + /// Gets an instance of an PropertyTypeList, which can be used to enumerate the possible values for a property. + public PropertyTypeList TypeList => typeList ?? (typeList = new PropertyTypeList(iDesc.GetEnumTypeList() as IPropertyEnumTypeList)); + /// Gets the current set of flags governing the property's view. /// When this method returns, contains a pointer to a value that includes one or more of the following flags. See PROPDESC_VIEW_FLAGS for valid values. public PROPDESC_VIEW_FLAGS ViewFlags => iDesc.GetViewFlags(); @@ -82,27 +84,39 @@ namespace Vanara.Windows.Shell /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. public virtual void Dispose() { + typeList?.Dispose(); + if (iDesc2 != null) + { + Marshal.ReleaseComObject(iDesc2); + iDesc2 = null; + } if (iDesc != null) { Marshal.ReleaseComObject(iDesc); iDesc = null; } } - - // /// Gets an instance of an IPropertyEnumTypeList, which can be used to enumerate the possible values for a property. - // public IPropertyEnumTypeList EnumTypeList => iDesc.GetEnumTypeList() as IPropertyEnumTypeList; /// Gets a formatted string representation of a property value. - /// A reference to the requested property key, which identifies a property. See PROPERTYKEY. /// A PROPVARIANT that contains the type and value of the property. /// One or more of the PROPDESC_FORMAT_FLAGS flags, which are either bitwise or multiple values, that indicate the property string format. /// The formatted value. - public string FormatForDisplay(ref PROPERTYKEY key, PROPVARIANT propvar, PROPDESC_FORMAT_FLAGS pdfFlags) + public string FormatForDisplay(PROPVARIANT propvar, PROPDESC_FORMAT_FLAGS pdfFlags = PROPDESC_FORMAT_FLAGS.PDFF_DEFAULT) { var sb = new System.Text.StringBuilder(0x8000); + var key = PropertyKey; iDesc.FormatForDisplay(ref key, propvar, pdfFlags, sb, (uint)sb.Capacity); return sb.ToString(); } + /// Gets the image location for a value. + /// The value. + /// An IconLocation for the image associated with the property value. + public IconLocation GetImageLocationForValue(PROPVARIANT propvar) + { + if (iDesc2 == null) iDesc2 = iDesc as IPropertyDescription2; + return IconLocation.TryParse(iDesc2?.GetImageReferenceForValue(propvar), out var loc) ? loc : new IconLocation(); + } + /// Compares two property values in the manner specified by the property description. Returns two display strings that describe how the two properties compare. /// A reference to a PROPVARIANT structure that contains the type and value of the first property. /// A reference to a PROPVARIANT structure that contains the type and value of the second property. @@ -122,18 +136,30 @@ namespace Vanara.Windows.Shell public bool IsValueCanonical(PROPVARIANT propvar) => iDesc.IsValueCanonical(propvar).Succeeded; } + /// Exposes methods that extract information from a collection of property descriptions presented as a list. + /// + /// public class PropertyDescriptionList : IReadOnlyList, IDisposable { + /// The IPropertyDescriptionList instance. protected IPropertyDescriptionList iList; - internal PropertyDescriptionList(IPropertyDescriptionList list) + /// Initializes a new instance of the class. + /// The COM interface pointer. + internal protected PropertyDescriptionList(IPropertyDescriptionList list) { - iList = list; + iList = list ?? throw new ArgumentNullException(nameof(list)); } - public int Count => (int)iList.GetCount(); + /// Gets the number of elements in the collection. + /// The number of elements in the collection. + public virtual int Count => (int)iList.GetCount(); - public PropertyDescription this[int index] => new PropertyDescription(iList.GetAt((uint)index, typeof(IPropertyDescription).GUID)); + /// Gets the at the specified index. + /// The . + /// The index. + /// The at the specified index. + public virtual PropertyDescription this[int index] => new PropertyDescription(iList.GetAt((uint)index, typeof(IPropertyDescription).GUID)); /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. public virtual void Dispose() @@ -145,11 +171,17 @@ namespace Vanara.Windows.Shell } } + /// Returns an enumerator that iterates through the collection. + /// A that can be used to iterate through the collection. public IEnumerator GetEnumerator() => Enum().GetEnumerator(); + /// Returns an enumerator that iterates through a collection. + /// An object that can be used to iterate through the collection. IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - private IEnumerable Enum() + /// Enumerates through the items in this instance. + /// An for this list. + protected virtual IEnumerable Enum() { for (var i = 0; i < Count; i++) yield return this[i]; @@ -157,30 +189,56 @@ namespace Vanara.Windows.Shell } } + /// Exposes methods that extract data from enumeration information. + /// public class PropertyType : IDisposable { + /// The IPropertyEnumType instance. protected IPropertyEnumType iType; + /// The IPropertyEnumType2 instance. protected IPropertyEnumType2 iType2; - internal PropertyType(IPropertyEnumType type) + /// Initializes a new instance of the class. + /// The IPropertyEnumType object. + internal protected PropertyType(IPropertyEnumType type) { - iType = type; - iType2 = type as IPropertyEnumType2; + iType = type ?? throw new ArgumentNullException(nameof(type)); } + /// Gets the display text. + /// The display text. public string DisplayText => iType.GetDisplayText(); + /// Gets an enumeration type. + /// The enumeration type. public PROPENUMTYPE EnumType => iType.GetEnumType(); - public IconLocation ImageReference { get { if (IconLocation.TryParse(iType2?.GetImageReference(), out var loc)) return loc; return new IconLocation(); } } + /// Gets the image reference. + /// The image reference. + public IconLocation ImageReference + { + get + { + if (iType2 == null) iType2 = iType as IPropertyEnumType2; + return IconLocation.TryParse(iType2?.GetImageReference(), out var loc) ? loc : new IconLocation(); + } + } + /// Gets a minimum value. + /// The minimum value. public PROPVARIANT RangeMinValue => iType.GetRangeMinValue(); + /// Gets a set value. + /// The set value. public PROPVARIANT RangeSetValue => iType.GetRangeSetValue(); + /// Gets a value. + /// The value. public PROPVARIANT Value => iType.GetValue(); - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// public virtual void Dispose() { if (iType != null) @@ -196,20 +254,34 @@ namespace Vanara.Windows.Shell } } + /// Exposes methods that enumerate the possible values for a property. + /// + /// public class PropertyTypeList : IReadOnlyList, IDisposable { + /// The IPropertyEnumTypeList object. protected IPropertyEnumTypeList iList; - internal PropertyTypeList(IPropertyEnumTypeList list) + /// Initializes a new instance of the class. + /// The IPropertyEnumTypeList object. + internal protected PropertyTypeList(IPropertyEnumTypeList list) { - iList = list; + iList = list ?? throw new ArgumentNullException(nameof(list)); } - public int Count => (int)iList.GetCount(); + /// Gets the number of elements in the collection. + /// The number of elements in the collection. + public virtual int Count => (int)iList.GetCount(); - public PropertyType this[int index] => new PropertyType(iList.GetAt((uint)index, typeof(IPropertyDescription).GUID)); + /// Gets the at the specified index. + /// The . + /// The index. + /// The at the specified index. + public virtual PropertyType this[int index] => new PropertyType(iList.GetAt((uint)index, typeof(IPropertyDescription).GUID)); - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// public virtual void Dispose() { if (iList != null) @@ -219,13 +291,26 @@ namespace Vanara.Windows.Shell } } - public int FindMatchingIndex(PROPVARIANT pv) => (int)iList.FindMatchingIndex(pv); + /// Determines the index of a specific item in the list. + /// The object to locate in the list. + /// The index of item if found in the list; otherwise, -1. + public virtual int IndexOf(PROPVARIANT pv) => iList.FindMatchingIndex(pv, out var idx).Succeeded ? (int)idx : -1; + /// Returns an enumerator that iterates through the collection. + /// + /// A that can be used to iterate through the collection. + /// public IEnumerator GetEnumerator() => Enum().GetEnumerator(); + /// Returns an enumerator that iterates through a collection. + /// + /// An object that can be used to iterate through the collection. + /// IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - private IEnumerable Enum() + /// Enumerates through the items in this instance. + /// An for this list. + protected virtual IEnumerable Enum() { for (var i = 0; i < Count; i++) yield return this[i]; diff --git a/Windows.Shell/PropertyStore.cs b/Windows.Shell/PropertyStore.cs index 0a83464c..1b2ab32c 100644 --- a/Windows.Shell/PropertyStore.cs +++ b/Windows.Shell/PropertyStore.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Runtime.InteropServices; +using Vanara.PInvoke; using static Vanara.PInvoke.Ole32; using static Vanara.PInvoke.PropSys; @@ -97,7 +98,10 @@ namespace Vanara.Windows.Shell /// The requested property key. public static PROPERTYKEY GetPropertyKeyFromName(string name) { - PSGetPropertyKeyFromName(name, out var pk).ThrowIfFailed(); + 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; } @@ -187,8 +191,11 @@ namespace Vanara.Windows.Shell { var pv = new PROPVARIANT(); iprops.GetValue(ref key, pv); - value = pv.Value; - return true; + if (pv.VarType != VarEnum.VT_EMPTY) + { + value = pv.Value; + return true; + } } catch { } } @@ -208,19 +215,9 @@ namespace Vanara.Windows.Shell /// public bool TryGetValue(PROPERTYKEY key, out TVal value) { - if (iprops != null) - { - try - { - var pv = new PROPVARIANT(); - iprops.GetValue(ref key, pv); - value = (TVal)pv.Value; - return true; - } - catch { } - } - value = default(TVal); - return false; + var ret = TryGetValue(key, out var val); + value = ret ? (TVal)val : default(TVal); + return ret; } /// Adds an item to the . diff --git a/Windows.Shell/ShellItem.cs b/Windows.Shell/ShellItem.cs index 9eef8944..4d2346bd 100644 --- a/Windows.Shell/ShellItem.cs +++ b/Windows.Shell/ShellItem.cs @@ -353,8 +353,8 @@ namespace Vanara.Windows.Shell internal IShellItem iShellItem; internal IShellItem2 iShellItem2; private static Dictionary bhidLookup; - private IQueryInfo qi; - private ShellItemPropertyStore values; + private IQueryInfo qi; + private ShellItemPropertyStore values; /// Initializes a new instance of the class. /// The file system path of the item. @@ -423,8 +423,8 @@ namespace Vanara.Windows.Shell /// The attributes of the Shell item. public ShellItemAttribute Attributes => (ShellItemAttribute)iShellItem.GetAttributes((SFGAO)0xFFFFFFFF); - /// Gets the corresponding to this instance. - public ShellFileInfo FileInfo => IsFileSystem ? new ShellFileInfo(PIDL) : throw new InvalidOperationException("Not file system objects do not have associated ShellFileInfo objects"); + /// Gets the corresponding to this instance. + public ShellFileInfo FileInfo => IsFileSystem ? new ShellFileInfo(PIDL) : throw new InvalidOperationException("Not file system objects do not have associated ShellFileInfo objects"); /// Gets the file system path if this item is part of the file system. /// The file system path. @@ -558,16 +558,16 @@ namespace Vanara.Windows.Shell /// The requested interface. public TInterface GetHandler(BHID handler = 0) where TInterface : class { - if (handler == 0) - handler = GetBHIDForInterface(); - if (handler == 0) - throw new ArgumentOutOfRangeException(nameof(handler)); - return iShellItem.BindToHandler(BindContext, handler.Guid(), typeof(TInterface).GUID) as TInterface; + if (handler == 0) + handler = GetBHIDForInterface(); + if (handler == 0) + throw new ArgumentOutOfRangeException(nameof(handler)); + return iShellItem.BindToHandler(BindContext, handler.Guid(), typeof(TInterface).GUID) as TInterface; } - /// Returns a hash code for this instance. - /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. - public override int GetHashCode() => GetDisplayName(SIGDN.SIGDN_DESKTOPABSOLUTEPARSING).GetHashCode(); + /// Returns a hash code for this instance. + /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. + public override int GetHashCode() => GetDisplayName(SIGDN.SIGDN_DESKTOPABSOLUTEPARSING).GetHashCode(); /// /// Gets an image that represents this item. The default behavior is to load a thumbnail. If there is no thumbnail for the current item, it retrieves the @@ -595,10 +595,14 @@ namespace Vanara.Windows.Shell throw new InvalidOperationException("Unable to retrieve an image for this item."); } + /// Gets a property description list object containing descriptions of all properties. + /// A complete instance. + public PropertyDescriptionList GetPropertyDescriptionList() => GetPropertyDescriptionList(PROPERTYKEY.System.PropList.FullDetails); + /// Gets a property description list object given a reference to a property key. - /// A reference to a PROPERTYKEY structure. + /// A reference to a PROPERTYKEY structure. The values in are all valid. will return all properties. /// A instance for the supplied key. - public PropertyDescriptionList GetPropertyDescriptionList(ref PROPERTYKEY keyType) + public PropertyDescriptionList GetPropertyDescriptionList(PROPERTYKEY keyType) { ThrowIfNoShellItem2(); return new PropertyDescriptionList(iShellItem2.GetPropertyDescriptionList(ref keyType, typeof(IPropertyDescriptionList).GUID)); @@ -627,6 +631,7 @@ namespace Vanara.Windows.Shell /// Ensures that all cached information for this item is updated. public void Update() { + values?.Commit(); ThrowIfNoShellItem2(); iShellItem2.Update(BindContext); } @@ -777,24 +782,24 @@ namespace Vanara.Windows.Shell /// The width, in pixels, of the Bitmap. /// The resulting Bitmap, on success, or null on failure. private Image GetThumbnail(int width = 32) - { - IThumbnailProvider provider = null; - try - { - provider = GetHandler(BHID.BHID_ThumbnailHandler); - if (provider == null) return null; - provider.GetThumbnail((uint)width, out var hbmp, out var alpha); - return Image.FromHbitmap(hbmp); - } - catch - { - return null; - } - finally - { - if (provider != null) Marshal.ReleaseComObject(provider); - } - } + { + IThumbnailProvider provider = null; + try + { + provider = GetHandler(BHID.BHID_ThumbnailHandler); + if (provider == null) return null; + provider.GetThumbnail((uint)width, out var hbmp, out var alpha); + return Image.FromHbitmap(hbmp); + } + catch + { + return null; + } + finally + { + if (provider != null) Marshal.ReleaseComObject(provider); + } + } protected class ShellItemImpl : IDisposable, IShellItem {