diff --git a/UnitTests/PInvoke/PropSys/PropSysTests.cs b/UnitTests/PInvoke/PropSys/PropSysTests.cs index 64e37712..3dc725e7 100644 --- a/UnitTests/PInvoke/PropSys/PropSysTests.cs +++ b/UnitTests/PInvoke/PropSys/PropSysTests.cs @@ -39,7 +39,7 @@ namespace Vanara.PInvoke.Tests { var pv = new PROPVARIANT(); var g = Guid.NewGuid(); - InitPropVariantFromCLSID(ref g, pv); + InitPropVariantFromCLSID(g, pv); Assert.That(pv.VarType, Is.EqualTo(VarEnum.VT_CLSID)); Assert.That(pv.Value, Is.EqualTo(g)); pv.Dispose(); diff --git a/UnitTests/PInvoke/Shared/Collections/ComEnumeratorTests.cs b/UnitTests/PInvoke/Shared/Collections/ComEnumeratorTests.cs index 9787ee97..c5459d4d 100644 --- a/UnitTests/PInvoke/Shared/Collections/ComEnumeratorTests.cs +++ b/UnitTests/PInvoke/Shared/Collections/ComEnumeratorTests.cs @@ -152,7 +152,7 @@ namespace Vanara.Collections.Tests foreach (var pd in e) { Assert.IsInstanceOf(pd); - var s = pd.GetDisplayName(); + pd.GetDisplayName(out var s); l.Add(s); TestContext.WriteLine(s); c++; diff --git a/UnitTests/Shell/ShellFolderTests.cs b/UnitTests/Shell/ShellFolderTests.cs index 6ef31401..b5d4f17d 100644 --- a/UnitTests/Shell/ShellFolderTests.cs +++ b/UnitTests/Shell/ShellFolderTests.cs @@ -1,21 +1,16 @@ using System; using NUnit.Framework; -using System.Diagnostics; -using System.Runtime.InteropServices.ComTypes; -using Vanara.PInvoke; -using Vanara.Windows.Shell; -using static Vanara.PInvoke.Ole32; using static Vanara.PInvoke.Shell32; using System.IO; using System.Linq; -namespace Vanara.Windows.Forms.Tests +namespace Vanara.Windows.Shell.Tests { [TestFixture] public class ShellFolderTests { - private const string testFile = @"C:\Temp\Test.doc"; - private const string testFld = @"C:\Temp"; + private const string testFile = ShellItemTests.testDoc; + private static string testFld = Path.GetDirectoryName(testFile); [Test] public void ShellFolderTest1() @@ -35,9 +30,9 @@ namespace Vanara.Windows.Forms.Tests { Assert.That(() => { - var d = new ShellFolder(KNOWNFOLDERID.FOLDERID_Desktop); - Assert.That(d, Is.EqualTo(ShellFolder.Desktop)); - var i = new ShellFolder(KNOWNFOLDERID.FOLDERID_ProgramFiles); + var d = new ShellFolder(KNOWNFOLDERID.FOLDERID_Desktop); + Assert.That(d, Is.EqualTo(ShellFolder.Desktop)); + var i = new ShellFolder(KNOWNFOLDERID.FOLDERID_ProgramFiles); Assert.That(i.FileSystemPath, Is.EqualTo(Environment.GetEnvironmentVariable("ProgramFiles"))); }, Throws.Nothing); Assert.That(() => new ShellFolder((KNOWNFOLDERID)int.MaxValue), Throws.TypeOf()); @@ -66,7 +61,7 @@ namespace Vanara.Windows.Forms.Tests Assert.That(i.FileSystemPath, Is.EqualTo(testFld)); }, Throws.Nothing); Assert.That(() => new ShellFolder((ShellItem) null), Throws.Exception); - Assert.That(() => new ShellFolder(new ShellItem(@"C:\Temp\Test.doc")), Throws.Exception); + Assert.That(() => new ShellFolder(new ShellItem(testFile)), Throws.Exception); } [Test] @@ -113,7 +108,7 @@ namespace Vanara.Windows.Forms.Tests Assert.That(lnk, Is.Not.Null.And.InstanceOf()); } } - }, Throws.Nothing); + }, Throws.Nothing); Assert.That(() => new ShellFolder(KNOWNFOLDERID.FOLDERID_Windows).EnumerateChildren((FolderItemFilter)0x80000), Is.Empty); } @@ -131,6 +126,6 @@ namespace Vanara.Windows.Forms.Tests Assert.That(() => f.GetChildrenUIObjects(null, i), Throws.TypeOf()); Assert.That(() => f.GetViewObject(null), Throws.TypeOf()); } - } - } + } + } } \ No newline at end of file diff --git a/UnitTests/Shell/ShellItemPropStoreTests.cs b/UnitTests/Shell/ShellItemPropStoreTests.cs new file mode 100644 index 00000000..5dafd0b3 --- /dev/null +++ b/UnitTests/Shell/ShellItemPropStoreTests.cs @@ -0,0 +1,94 @@ +using System; +using NUnit.Framework; +using System.Diagnostics; +using System.Runtime.InteropServices.ComTypes; +using Vanara.PInvoke; +using Vanara.Windows.Shell; +using static Vanara.PInvoke.Ole32; +using static Vanara.PInvoke.Shell32; +using System.Runtime.InteropServices; + +namespace Vanara.Windows.Shell.Tests +{ + [TestFixture] + public class ShellItemPropStoreTests + { + private const string testDoc = ShellItemTests.testDoc; + + [Test] + public void ShellItemPropertyStoreTest1() + { + using (var i = new ShellItem(testDoc)) + { + Assert.That(i.Properties, Is.Not.Null.And.Count.GreaterThan(0)); + Assert.That(i.Properties, Is.EqualTo(i.Properties)); + Assert.That(i.Properties.IncludeSlow, Is.False); + Assert.That(i.Properties.IsDirty, Is.False); + Assert.That(i.Properties.NoInheritedProperties, Is.False); + Assert.That(i.Properties.ReadOnly, Is.True); + Assert.That(i.Properties.Temporary, Is.False); + } + } + + [Test] + public void EnumTest() + { + using (var i = new ShellItem(testDoc)) + { + var c = 0; + foreach (var key in i.Properties.Keys) + { + c++; + using (var d = i.Properties.Descriptions[key]) + { + Assert.That(d, Is.Not.Null); + Assert.That(() => { + var val = i.Properties[key]; + TestContext.WriteLine($"({c}) {key} = {val}"); + TestContext.WriteLine($" {d.FormatForDisplay(val)}"); + TestContext.WriteLine($" DispN:{d.DisplayName}; DispT:{d.DisplayType}"); + }, Throws.Nothing); + } + TestContext.WriteLine(""); + } + Assert.That(c, Is.EqualTo(i.Properties.Count)); + } + } + + [Test] + public void DescriptionTest() + { + using (var i = new ShellItem(testDoc)) + { + var c = 0; + foreach (var d in i.Properties.Descriptions) + { + c++; + Assert.That(d, Is.Not.Null); + Assert.That(() => + { + TestContext.WriteLine($"Agg:{d.AggregationType}; Can:{d.CanonicalName}; ColSt:{d.ColumnState}"); + TestContext.WriteLine($"Cond:{d.ConditionType}; ColW:{d.DefaultColumnWidth}; DispN:{d.DisplayName}"); + TestContext.WriteLine($"DispT:{d.DisplayType}; EditInv:{d.EditInvitation}; SortDesc:{d.GetSortDescriptionLabel()}"); + TestContext.WriteLine($"GrpRng:{d.GroupingRange}; PropType:{d.PropertyType}; RelDescType:{d.RelativeDescriptionType}"); + TestContext.WriteLine($"SortDesc:{d.SortDescription}; TypeFlags:{d.TypeFlags}; ViewFlags:{d.ViewFlags}"); + using (var pv = i.Properties.GetPropVariant(d.PropertyKey)) + { + Assert.That(pv, Is.Not.Null); + TestContext.WriteLine($" IsCan:{d.IsValueCanonical(pv)}; ImgLoc:{d.GetImageLocationForValue(pv)}"); + } + }, Throws.Nothing); + if (d.TypeList.Count > 0) Debug.WriteLine(" Prop Types:"); + foreach (var t in d.TypeList) + { + Debug.WriteLine($" DispTx:{t.DisplayText}; EnumT:{t.EnumType}"); + Debug.WriteLine($" ImgLoc:{t.ImageReference}; RngMin:{t.RangeMinValue}"); + Debug.WriteLine($" RngSet:{t.RangeSetValue}; Val:{t.Value}"); + } + } + TestContext.WriteLine(""); + Assert.That(c, Is.EqualTo(i.Properties.Descriptions.Count)); + } + } + } +} \ No newline at end of file diff --git a/UnitTests/Shell/ShellItemTests.cs b/UnitTests/Shell/ShellItemTests.cs index 63d76b2e..4f863807 100644 --- a/UnitTests/Shell/ShellItemTests.cs +++ b/UnitTests/Shell/ShellItemTests.cs @@ -3,18 +3,17 @@ using NUnit.Framework; using System.Diagnostics; using System.Runtime.InteropServices.ComTypes; using Vanara.PInvoke; -using Vanara.Windows.Shell; using static Vanara.PInvoke.Ole32; using static Vanara.PInvoke.Shell32; -namespace Vanara.Windows.Forms.Tests +namespace Vanara.Windows.Shell.Tests { [TestFixture] public class ShellItemTests { - private const string badTestDoc = @"C:\Temp\BadTest.doc"; - private const string testDoc = @"C:\Temp\Test.doc"; - private const string testLinkDoc = @"C:\Temp\Test.lnk"; + internal const string badTestDoc = @"C:\Temp\BadTest.doc"; + internal const string testDoc = @"C:\Temp\Test.docx"; + internal const string testLinkDoc = @"C:\Temp\Test.lnk"; [Test] public void ShellItemTest1() diff --git a/UnitTests/UnitTests.csproj b/UnitTests/UnitTests.csproj index 94ef76e5..dfd75d45 100644 --- a/UnitTests/UnitTests.csproj +++ b/UnitTests/UnitTests.csproj @@ -117,6 +117,7 @@ + diff --git a/Windows.Shell/PropertyDescription.cs b/Windows.Shell/PropertyDescription.cs index 4cae6d26..873e1eb8 100644 --- a/Windows.Shell/PropertyDescription.cs +++ b/Windows.Shell/PropertyDescription.cs @@ -13,78 +13,96 @@ namespace Vanara.Windows.Shell protected IPropertyDescription iDesc; /// The IPropertyDescription2 object. protected IPropertyDescription2 iDesc2; - + /// The property key for this property. + protected PROPERTYKEY key; /// Gets the type list. protected PropertyTypeList typeList; + /// Initializes a new instance of the class. + /// A valid . + public PropertyDescription(PROPERTYKEY propkey) + { + key = propkey; + if (PSGetPropertyDescription(ref propkey, typeof(IPropertyDescription).GUID, out var ppv).Succeeded) + iDesc = (IPropertyDescription)ppv; + } + /// Initializes a new instance of the class. /// The property description. internal protected PropertyDescription(IPropertyDescription propertyDescription) { - iDesc = propertyDescription ?? throw new ArgumentNullException(nameof(propertyDescription)); + iDesc = propertyDescription; + key = iDesc.GetPropertyKey(); } /// Gets a value that describes how the property values are displayed when multiple items are selected in the UI. - public PROPDESC_AGGREGATION_TYPE AggregationType => iDesc.GetAggregationType(); + public PROPDESC_AGGREGATION_TYPE AggregationType => iDesc?.GetAggregationType() ?? 0; /// Gets the case-sensitive name by which a property is known to the system, regardless of its localized name. - public string CanonicalName => iDesc.GetCanonicalName(); + public string CanonicalName => iDesc?.GetCanonicalName(); /// Gets the column state flag, which describes how the property should be treated by interfaces or APIs that use this flag. - public SHCOLSTATE ColumnState => iDesc.GetColumnState(); + public SHCOLSTATE ColumnState => iDesc?.GetColumnState() ?? 0; /// Gets the condition type and default condition operation to use when displaying the property in the query builder UI. This influences the list of predicate conditions (for example, equals, less than, and contains) that are shown for this property. public Tuple ConditionType { - get { iDesc.GetConditionType(out var ct, out var co); return new Tuple(ct, co); } + get + { + PROPDESC_CONDITION_TYPE ct = 0; + CONDITION_OPERATION co = 0; + if (iDesc != null) + iDesc.GetConditionType(out ct, out co); + return new Tuple(ct, co); + } } /// Gets the default column width of the property in a list view. /// A pointer to the column width value, in characters. - public uint DefaultColumnWidth => iDesc.GetDefaultColumnWidth(); + public uint DefaultColumnWidth => iDesc?.GetDefaultColumnWidth() ?? 0; /// Gets the display name of the property as it is shown in any UI. - public string DisplayName => iDesc.GetDisplayName(); + public string DisplayName => iDesc != null && iDesc.GetDisplayName(out var s).Succeeded ? s : null; /// Gets the current data type used to display the property. - public PROPDESC_DISPLAYTYPE DisplayType => iDesc.GetDisplayType(); + public PROPDESC_DISPLAYTYPE DisplayType { get { try { return iDesc?.GetDisplayType() ?? 0; } catch { return 0; } } } /// Gets the text used in edit controls hosted in various dialog boxes. - public string EditInvitation => iDesc.GetEditInvitation(); + public string EditInvitation => iDesc?.GetEditInvitation(); /// Gets the grouping method to be used when a view is grouped by a property, and retrieves the grouping type. - public PROPDESC_GROUPING_RANGE GroupingRange => iDesc.GetGroupingRange(); + public PROPDESC_GROUPING_RANGE GroupingRange => iDesc?.GetGroupingRange() ?? 0; /// Gets a structure that acts as a property's unique identifier. - public PROPERTYKEY PropertyKey => iDesc.GetPropertyKey(); + public PROPERTYKEY PropertyKey => key; /// Gets the variant type of the property. If the type cannot be determined, this property returns null. - public Type PropertyType => PROPVARIANT.GetType(iDesc.GetPropertyType()); + public Type PropertyType => PROPVARIANT.GetType(iDesc?.GetPropertyType() ?? VARTYPE.VT_EMPTY); /// Gets the relative description type for a property description. - public PROPDESC_RELATIVEDESCRIPTION_TYPE RelativeDescriptionType => iDesc.GetRelativeDescriptionType(); + public PROPDESC_RELATIVEDESCRIPTION_TYPE RelativeDescriptionType => iDesc?.GetRelativeDescriptionType() ?? 0; /// Gets the current sort description flags for the property, which indicate the particular wordings of sort offerings. - public PROPDESC_SORTDESCRIPTION SortDescription => iDesc.GetSortDescription(); + public PROPDESC_SORTDESCRIPTION SortDescription => iDesc?.GetSortDescription() ?? 0; /// 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); + public PROPDESC_TYPE_FLAGS TypeFlags => iDesc?.GetTypeFlags(PROPDESC_TYPE_FLAGS.PDTF_MASK_ALL) ?? 0; - /// 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 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(typeof(IPropertyEnumTypeList).GUID))); /// 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(); + public PROPDESC_VIEW_FLAGS ViewFlags => iDesc?.GetViewFlags() ?? 0; /// Coerces the value to the canonical value, according to the property description. /// On entry, contains a PROPVARIANT that contains the original value. When this method returns, contains the canonical value. - public bool CoerceToCanonicalValue(PROPVARIANT propvar) => iDesc.CoerceToCanonicalValue(propvar).Succeeded; + public bool CoerceToCanonicalValue(PROPVARIANT propvar) => iDesc?.CoerceToCanonicalValue(propvar).Succeeded ?? false; /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. public virtual void Dispose() { - typeList?.Dispose(); + // typeList?.Dispose(); if (iDesc2 != null) { Marshal.ReleaseComObject(iDesc2); @@ -96,44 +114,52 @@ namespace Vanara.Windows.Shell iDesc = null; } } + /// Gets a formatted string representation of a property value. - /// A PROPVARIANT that contains the type and value of the property. + /// A object 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(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(); - } + public string FormatForDisplay(object obj, PROPDESC_FORMAT_FLAGS pdfFlags = PROPDESC_FORMAT_FLAGS.PDFF_DEFAULT) => iDesc?.FormatForDisplay(new PROPVARIANT(obj), pdfFlags) ?? obj?.ToString(); + + /// Gets a formatted string representation of a property value. + /// A object 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. + internal string FormatForDisplay(PROPVARIANT pv, PROPDESC_FORMAT_FLAGS pdfFlags = PROPDESC_FORMAT_FLAGS.PDFF_DEFAULT) => iDesc?.FormatForDisplay(pv, pdfFlags) ?? pv?.ToString(); /// Gets the image location for a value. - /// The value. + /// The value. /// An IconLocation for the image associated with the property value. - public IconLocation GetImageLocationForValue(PROPVARIANT propvar) + public IconLocation GetImageLocationForValue(object obj) { if (iDesc2 == null) iDesc2 = iDesc as IPropertyDescription2; - return IconLocation.TryParse(iDesc2?.GetImageReferenceForValue(propvar), out var loc) ? loc : new IconLocation(); + return iDesc2 != null && IconLocation.TryParse(iDesc2.GetImageReferenceForValue(new PROPVARIANT(obj), out var img).Succeeded ? img : null, 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. - public Tuple GetRelativeDescription(PROPVARIANT propvar1, PROPVARIANT propvar2) + /// An object for the first property. + /// An object for the second property. + public Tuple GetRelativeDescription(object obj1, object obj2) { - iDesc.GetRelativeDescription(propvar1, propvar2, out var d1, out var d2); + string d1 = null, d2 = null; + iDesc?.GetRelativeDescription(new PROPVARIANT(obj1), new PROPVARIANT(obj2), out d1, out d2); return new Tuple(d1, d2); } /// Gets the localized display string that describes the current sort order. /// TRUE if ppszDescription should reference the string "Z on top"; FALSE to reference the string "A on top". /// When this method returns, contains the address of a pointer to the sort description as a null-terminated Unicode string. - public string GetSortDescriptionLabel(bool descending = false) => iDesc.GetSortDescriptionLabel(descending); + public string GetSortDescriptionLabel(bool descending = false) => iDesc?.GetSortDescriptionLabel(descending); /// Gets a value that indicates whether a property is canonical according to the definition of the property description. /// A PROPVARIANT that contains the type and value of the property. - public bool IsValueCanonical(PROPVARIANT propvar) => iDesc.IsValueCanonical(propvar).Succeeded; + public bool IsValueCanonical(PROPVARIANT propvar) => iDesc?.IsValueCanonical(propvar).Succeeded ?? false; + + /// Returns a that represents this instance. + /// A that represents this instance. + public override string ToString() => CanonicalName; + + public IPropertyDescription Raw => iDesc; } /// Exposes methods that extract information from a collection of property descriptions presented as a list. @@ -148,18 +174,25 @@ namespace Vanara.Windows.Shell /// The COM interface pointer. internal protected PropertyDescriptionList(IPropertyDescriptionList list) { - iList = list ?? throw new ArgumentNullException(nameof(list)); + iList = list; } /// Gets the number of elements in the collection. /// The number of elements in the collection. - public virtual int Count => (int)iList.GetCount(); + public virtual int Count => (int)(iList?.GetCount() ?? 0); /// 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)); + public virtual PropertyDescription this[int index] => + new PropertyDescription(iList?.GetAt((uint)index, typeof(IPropertyDescription).GUID)); + + /// Gets the for the specified key. + /// The . + /// The PROPERTYKEY. + /// The for the specified key. + public virtual PropertyDescription this[PROPERTYKEY propkey] => new PropertyDescription(propkey); /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. public virtual void Dispose() @@ -202,16 +235,16 @@ namespace Vanara.Windows.Shell /// The IPropertyEnumType object. internal protected PropertyType(IPropertyEnumType type) { - iType = type ?? throw new ArgumentNullException(nameof(type)); + iType = type; } /// Gets the display text. /// The display text. - public string DisplayText => iType.GetDisplayText(); + public string DisplayText { get { try { iType.GetDisplayText(out var s); return s; } catch { return null; } } } /// Gets an enumeration type. /// The enumeration type. - public PROPENUMTYPE EnumType => iType.GetEnumType(); + public PROPENUMTYPE EnumType => iType?.GetEnumType() ?? 0; /// Gets the image reference. /// The image reference. @@ -220,38 +253,41 @@ namespace Vanara.Windows.Shell get { if (iType2 == null) iType2 = iType as IPropertyEnumType2; - return IconLocation.TryParse(iType2?.GetImageReference(), out var loc) ? loc : new IconLocation(); + string img = null; + return IconLocation.TryParse(iType2?.GetImageReference(out img).Succeeded ?? false ? img : null, out var loc) ? loc : new IconLocation(); } } /// Gets a minimum value. /// The minimum value. - public PROPVARIANT RangeMinValue => iType.GetRangeMinValue(); + public object RangeMinValue { get { try { var t = new PROPVARIANT(); iType.GetRangeMinValue(t); return t.Value; } catch { return null; } } } /// Gets a set value. /// The set value. - public PROPVARIANT RangeSetValue => iType.GetRangeSetValue(); + public object RangeSetValue { get { try { var t = new PROPVARIANT(); iType.GetRangeSetValue(t); return t.Value; } catch { return null; } } } /// Gets a value. - /// The value. - public PROPVARIANT Value => iType.GetValue(); + /// The value.EnumType != PROPENUMTYPE.PET_DEFAULTVALUE ? + public object Value { get { try { var t = new PROPVARIANT(); iType.GetValue(t); return t.Value; } catch { return null; } } } - /// - /// 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) - { - Marshal.ReleaseComObject(iType); - iType = null; - } if (iType2 != null) { Marshal.ReleaseComObject(iType2); iType2 = null; } + if (iType != null) + { + Marshal.ReleaseComObject(iType); + iType = null; + } } + + /// Returns a that represents this instance. + /// A that represents this instance. + public override string ToString() => DisplayText ?? ""; } /// Exposes methods that enumerate the possible values for a property. @@ -266,22 +302,20 @@ namespace Vanara.Windows.Shell /// The IPropertyEnumTypeList object. internal protected PropertyTypeList(IPropertyEnumTypeList list) { - iList = list ?? throw new ArgumentNullException(nameof(list)); + iList = list; } /// Gets the number of elements in the collection. /// The number of elements in the collection. - public virtual int Count => (int)iList.GetCount(); + public virtual int Count => (int)(iList?.GetCount() ?? 0); /// 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)); + public virtual PropertyType this[int index] => new PropertyType(iList?.GetAt((uint)index, typeof(IPropertyEnumType).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) @@ -292,9 +326,9 @@ namespace Vanara.Windows.Shell } /// Determines the index of a specific item in the list. - /// The object to locate 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; + public virtual int IndexOf(object obj) => iList == null ? -1 : (iList.FindMatchingIndex(new PROPVARIANT(obj), out var idx).Succeeded ? (int)idx : -1); /// Returns an enumerator that iterates through the collection. /// diff --git a/Windows.Shell/PropertyStore.cs b/Windows.Shell/PropertyStore.cs index 1b2ab32c..56fb0c31 100644 --- a/Windows.Shell/PropertyStore.cs +++ b/Windows.Shell/PropertyStore.cs @@ -2,7 +2,6 @@ using System.Collections; using System.Collections.Generic; using System.ComponentModel; -using System.Linq; using System.Runtime.InteropServices; using Vanara.PInvoke; using static Vanara.PInvoke.Ole32; @@ -88,6 +87,8 @@ namespace Vanara.Windows.Shell { 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()); } @@ -138,8 +139,7 @@ namespace Vanara.Windows.Shell 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."); + 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; @@ -160,7 +160,7 @@ 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() => Keys.Select(k => new KeyValuePair(k, this[k])).GetEnumerator(); + public IEnumerator> GetEnumerator() => Enum().GetEnumerator(); /// Gets the property. /// The type of the value. @@ -174,6 +174,29 @@ namespace Vanara.Windows.Shell 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 new PropertyDescription(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. /// @@ -185,22 +208,9 @@ namespace Vanara.Windows.Shell /// public bool TryGetValue(PROPERTYKEY key, out object value) { - if (iprops != null) - { - try - { - var pv = new PROPVARIANT(); - iprops.GetValue(ref key, pv); - if (pv.VarType != VarEnum.VT_EMPTY) - { - value = pv.Value; - return true; - } - } - catch { } - } - value = null; - return false; + var ret = TryGetValue(key, out PROPVARIANT pv); + value = ret ? pv.Value : null; + return ret; } /// Gets the value associated with the specified key. @@ -215,8 +225,8 @@ namespace Vanara.Windows.Shell /// public bool TryGetValue(PROPERTYKEY key, out TVal value) { - var ret = TryGetValue(key, out var val); - value = ret ? (TVal)val : default(TVal); + var ret = TryGetValue(key, out PROPVARIANT val); + value = ret ? (TVal)val.Value : default(TVal); return ret; } @@ -260,5 +270,32 @@ namespace Vanara.Windows.Shell 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) + { + if (iprops != null) + { + try + { + var pv = new PROPVARIANT(); + iprops.GetValue(ref key, pv); + value = pv; + return true; + } + catch { } + } + value = null; + return false; + } } } \ No newline at end of file diff --git a/Windows.Shell/ShellItem.cs b/Windows.Shell/ShellItem.cs index 4d2346bd..0789534a 100644 --- a/Windows.Shell/ShellItem.cs +++ b/Windows.Shell/ShellItem.cs @@ -337,7 +337,6 @@ namespace Vanara.Windows.Shell SingleLine = 0x00000010, } - // TODO: object GetPropertyDescriptionList(IntPtr keyType, [In, MarshalAs(UnmanagedType.LPStruct)] Guid riid); // TODO: object GetPropertyStoreForKeys(IntPtr rgKeys, uint cKeys, GPS flags, [In, MarshalAs(UnmanagedType.LPStruct)] Guid riid); // TODO: object GetPropertyStoreWithCreateObject(GPS flags, object punkCreateObject, [In, MarshalAs(UnmanagedType.LPStruct)] Guid riid); /// Encapsulates an item in the Windows Shell. @@ -354,7 +353,8 @@ namespace Vanara.Windows.Shell internal IShellItem2 iShellItem2; private static Dictionary bhidLookup; private IQueryInfo qi; - private ShellItemPropertyStore values; + private ShellItemPropertyStore props; + private PropertyDescriptionList propDescList; /// Initializes a new instance of the class. /// The file system path of the item. @@ -408,15 +408,8 @@ namespace Vanara.Windows.Shell /// Occurs when a property value changes. public event PropertyChangedEventHandler PropertyChanged { - add - { - ((INotifyPropertyChanged)Properties).PropertyChanged += value; - } - - remove - { - ((INotifyPropertyChanged)Properties).PropertyChanged -= value; - } + add { ((INotifyPropertyChanged)Properties).PropertyChanged += value; } + remove { ((INotifyPropertyChanged)Properties).PropertyChanged -= value; } } /// Gets the attributes for the Shell item. @@ -481,7 +474,11 @@ namespace Vanara.Windows.Shell /// Gets the property store for the item. /// The dictionary of properties. - public ShellItemPropertyStore Properties => values ?? (values = new ShellItemPropertyStore(this)); + public ShellItemPropertyStore Properties => props ?? (props = new ShellItemPropertyStore(this)); + + /// Gets a property description list object containing descriptions of all properties. + /// A complete instance. + public PropertyDescriptionList PropertyDescriptions => propDescList ?? (propDescList = GetPropertyDescriptionList(PROPERTYKEY.System.PropList.FullDetails)); /// Gets the normal tool tip text associated with this item. /// The tool tip text. @@ -526,7 +523,8 @@ namespace Vanara.Windows.Shell /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. public virtual void Dispose() { - values?.Dispose(); + if (props != null) { props?.Dispose(); props = null; } + if (propDescList != null) { propDescList?.Dispose(); propDescList = null; } if (qi != null) { Marshal.ReleaseComObject(qi); qi = null; } if (iShellItem2 != null) { Marshal.ReleaseComObject(iShellItem2); iShellItem2 = null; } if (iShellItem != null) { Marshal.ReleaseComObject(iShellItem); iShellItem = null; } @@ -595,10 +593,6 @@ 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. The values in are all valid. will return all properties. /// A instance for the supplied key. @@ -631,7 +625,7 @@ namespace Vanara.Windows.Shell /// Ensures that all cached information for this item is updated. public void Update() { - values?.Commit(); + props?.Commit(); ThrowIfNoShellItem2(); iShellItem2.Update(BindContext); } diff --git a/Windows.Shell/ShellItemPropertyStore.cs b/Windows.Shell/ShellItemPropertyStore.cs index 26426477..d3f57d1c 100644 --- a/Windows.Shell/ShellItemPropertyStore.cs +++ b/Windows.Shell/ShellItemPropertyStore.cs @@ -7,11 +7,18 @@ using static Vanara.PInvoke.PropSys; namespace Vanara.Windows.Shell { + /// A property store for a . + /// public sealed class ShellItemPropertyStore : PropertyStore { + /// The shell item private readonly ShellItem shellItem; + /// The flags. private GETPROPERTYSTOREFLAGS flags = GETPROPERTYSTOREFLAGS.GPS_DEFAULT; + /// Initializes a new instance of the class. + /// The ShellItem instance. + /// The optional property changed handler. internal ShellItemPropertyStore(ShellItem item, PropertyChangedEventHandler propChangedHandler = null) { shellItem = item; @@ -20,17 +27,9 @@ namespace Vanara.Windows.Shell PropertyChanged += propChangedHandler; } - [DefaultValue(false)] - public bool NoInheritedProperties - { - get => flags.IsFlagSet(GETPROPERTYSTOREFLAGS.GPS_HANDLERPROPERTIESONLY); - set - { - if (NoInheritedProperties == value) return; - flags = flags.SetFlags(GETPROPERTYSTOREFLAGS.GPS_HANDLERPROPERTIESONLY, value); - Refresh(); - } - } + /// Gets a property description list object containing descriptions of all properties. + /// A complete instance. + public PropertyDescriptionList Descriptions => shellItem.PropertyDescriptions; /// Gets or sets a value indicating whether to include slow properties. /// true if including slow properties; otherwise, false. @@ -42,12 +41,31 @@ namespace Vanara.Windows.Shell { if (IncludeSlow == value) return; flags = flags.SetFlags(GETPROPERTYSTOREFLAGS.GPS_OPENSLOWITEM, value); + if (value) + flags = flags.SetFlags(GETPROPERTYSTOREFLAGS.GPS_TEMPORARY | GETPROPERTYSTOREFLAGS.GPS_FASTPROPERTIESONLY, false); Refresh(); } } + /// 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); + Refresh(); + } + } + /// Gets or sets a value indicating whether properties can be read and written. /// true if properties are read/write; otherwise, false. [DefaultValue(true)] @@ -59,11 +77,18 @@ namespace Vanara.Windows.Shell if (ReadOnly == value) return; flags = flags.SetFlags(GETPROPERTYSTOREFLAGS.GPS_READWRITE, !value); if (!value) - flags = flags.SetFlags(GETPROPERTYSTOREFLAGS.GPS_DELAYCREATION | GETPROPERTYSTOREFLAGS.GPS_TEMPORARY, false); + flags = flags.SetFlags(GETPROPERTYSTOREFLAGS.GPS_DELAYCREATION | GETPROPERTYSTOREFLAGS.GPS_TEMPORARY | GETPROPERTYSTOREFLAGS.GPS_BESTEFFORT | GETPROPERTYSTOREFLAGS.GPS_FASTPROPERTIESONLY, false); + else + flags = flags.SetFlags(GETPROPERTYSTOREFLAGS.GPS_HANDLERPROPERTIESONLY); Refresh(); } } + /// + /// 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 { @@ -74,10 +99,14 @@ namespace Vanara.Windows.Shell flags = flags.SetFlags(GETPROPERTYSTOREFLAGS.GPS_TEMPORARY, value); if (value) { - flags = flags.SetFlags(GETPROPERTYSTOREFLAGS.GPS_READWRITE, true); - flags = flags.SetFlags(GETPROPERTYSTOREFLAGS.GPS_DELAYCREATION | GETPROPERTYSTOREFLAGS.GPS_OPENSLOWITEM, false); + flags = GETPROPERTYSTOREFLAGS.GPS_TEMPORARY; + ReadOnly = false; + } + else + { + flags = flags.SetFlags(GETPROPERTYSTOREFLAGS.GPS_TEMPORARY, false); + Refresh(); } - Refresh(); } } @@ -90,6 +119,7 @@ namespace Vanara.Windows.Shell return shellItem.iShellItem2.GetCLSID(ref propertyKey); } + /// Refreshes this instance. This call is intended for internal use only and should not need to be called. public void Refresh() { shellItem.ThrowIfNoShellItem2();