//Copyright (c) Microsoft Corporation. All rights reserved. using System; using System.Diagnostics; using System.Reflection; using System.Runtime.InteropServices; using Microsoft.WindowsAPICodePack.Shell.Resources; using MS.WindowsAPICodePack.Internal; namespace Microsoft.WindowsAPICodePack.Shell.PropertySystem { /// /// Defines a strongly-typed property object. /// All writable property objects must be of this type /// to be able to call the value setter. /// /// The type of this property's value. /// Because a property value can be empty, only nullable types /// are allowed. public class ShellProperty : IShellProperty { #region Private Fields private PropertyKey propertyKey; string imageReferencePath = null; int? imageReferenceIconIndex; private ShellPropertyDescription description = null; #endregion #region Private Methods private ShellObject ParentShellObject { get; set; } private IPropertyStore NativePropertyStore { get; set; } private void GetImageReference() { IPropertyStore store = ShellPropertyCollection.CreateDefaultPropertyStore(ParentShellObject); using (PropVariant propVar = new PropVariant()) { store.GetValue(ref propertyKey, propVar); Marshal.ReleaseComObject(store); store = null; string refPath; ((IPropertyDescription2)Description.NativePropertyDescription).GetImageReferenceForValue( propVar, out refPath); if (refPath == null) { return; } int index = ShellNativeMethods.PathParseIconLocation(ref refPath); if (refPath != null) { imageReferencePath = refPath; imageReferenceIconIndex = index; } } } private void StorePropVariantValue(PropVariant propVar) { Guid guid = new Guid(ShellIIDGuid.IPropertyStore); IPropertyStore writablePropStore = null; try { int hr = ParentShellObject.NativeShellItem2.GetPropertyStore( ShellNativeMethods.GetPropertyStoreOptions.ReadWrite, ref guid, out writablePropStore); if (!CoreErrorHelper.Succeeded(hr)) { throw new PropertySystemException(LocalizedMessages.ShellPropertyUnableToGetWritableProperty, Marshal.GetExceptionForHR(hr)); } HResult result = writablePropStore.SetValue(ref propertyKey, propVar); if (!AllowSetTruncatedValue && (int)result == ShellNativeMethods.InPlaceStringTruncated) { throw new ArgumentOutOfRangeException("propVar", LocalizedMessages.ShellPropertyValueTruncated); } if (!CoreErrorHelper.Succeeded(result)) { throw new PropertySystemException(LocalizedMessages.ShellPropertySetValue, Marshal.GetExceptionForHR((int)result)); } writablePropStore.Commit(); } catch (InvalidComObjectException e) { throw new PropertySystemException(LocalizedMessages.ShellPropertyUnableToGetWritableProperty, e); } catch (InvalidCastException) { throw new PropertySystemException(LocalizedMessages.ShellPropertyUnableToGetWritableProperty); } finally { if (writablePropStore != null) { Marshal.ReleaseComObject(writablePropStore); writablePropStore = null; } } } #endregion #region Internal Constructor /// /// Constructs a new Property object /// /// /// /// internal ShellProperty( PropertyKey propertyKey, ShellPropertyDescription description, ShellObject parent) { this.propertyKey = propertyKey; this.description = description; ParentShellObject = parent; AllowSetTruncatedValue = false; } /// /// Constructs a new Property object /// /// /// /// internal ShellProperty( PropertyKey propertyKey, ShellPropertyDescription description, IPropertyStore propertyStore) { this.propertyKey = propertyKey; this.description = description; NativePropertyStore = propertyStore; AllowSetTruncatedValue = false; } #endregion #region Public Properties /// /// Gets or sets the strongly-typed value of this property. /// The value of the property is cleared if the value is set to null. /// /// /// If the property value cannot be retrieved or updated in the Property System /// If the type of this property is not supported; e.g. writing a binary object. /// Thrown if is false, and either /// a string value was truncated or a numeric value was rounded. public T Value { get { // Make sure we load the correct type Debug.Assert(ValueType == ShellPropertyFactory.VarEnumToSystemType(Description.VarEnumType)); using (PropVariant propVar = new PropVariant()) { if (ParentShellObject.NativePropertyStore != null) { // If there is a valid property store for this shell object, then use it. ParentShellObject.NativePropertyStore.GetValue(ref propertyKey, propVar); } else if (ParentShellObject != null) { // Use IShellItem2.GetProperty instead of creating a new property store // The file might be locked. This is probably quicker, and sufficient for what we need ParentShellObject.NativeShellItem2.GetProperty(ref propertyKey, propVar); } else if (NativePropertyStore != null) { NativePropertyStore.GetValue(ref propertyKey, propVar); } //Get the value return propVar.Value != null ? (T)propVar.Value : default(T); } } set { // Make sure we use the correct type Debug.Assert(ValueType == ShellPropertyFactory.VarEnumToSystemType(Description.VarEnumType)); if (typeof(T) != ValueType) { throw new NotSupportedException( string.Format(System.Globalization.CultureInfo.InvariantCulture, LocalizedMessages.ShellPropertyWrongType, ValueType.Name)); } if (value is Nullable) { Type t = typeof(T); PropertyInfo pi = t.GetProperty("HasValue"); if (pi != null) { bool hasValue = (bool)pi.GetValue(value, null); if (!hasValue) { ClearValue(); return; } } } else if (value == null) { ClearValue(); return; } if (ParentShellObject != null) { using (ShellPropertyWriter propertyWriter = ParentShellObject.Properties.GetPropertyWriter()) { propertyWriter.WriteProperty(this, value, AllowSetTruncatedValue); } } else if (NativePropertyStore != null) { throw new InvalidOperationException(LocalizedMessages.ShellPropertyCannotSetProperty); } } } #endregion #region IProperty Members /// /// Gets the property key identifying this property. /// public PropertyKey PropertyKey { get { return propertyKey; } } /// /// Returns a formatted, Unicode string representation of a property value. /// /// One or more of the PropertyDescriptionFormat flags /// that indicate the desired format. /// The formatted value as a string, or null if this property /// cannot be formatted for display. /// True if the method successfully locates the formatted string; otherwise /// False. public bool TryFormatForDisplay(PropertyDescriptionFormatOptions format, out string formattedString) { if (Description == null || Description.NativePropertyDescription == null) { // We cannot do anything without a property description formattedString = null; return false; } IPropertyStore store = ShellPropertyCollection.CreateDefaultPropertyStore(ParentShellObject); using (PropVariant propVar = new PropVariant()) { store.GetValue(ref propertyKey, propVar); // Release the Propertystore Marshal.ReleaseComObject(store); store = null; HResult hr = Description.NativePropertyDescription.FormatForDisplay(propVar, ref format, out formattedString); // Sometimes, the value cannot be displayed properly, such as for blobs // or if we get argument exception if (!CoreErrorHelper.Succeeded(hr)) { formattedString = null; return false; } return true; } } /// /// Returns a formatted, Unicode string representation of a property value. /// /// One or more of the PropertyDescriptionFormat flags /// that indicate the desired format. /// The formatted value as a string, or null if this property /// cannot be formatted for display. public string FormatForDisplay(PropertyDescriptionFormatOptions format) { string formattedString; if (Description == null || Description.NativePropertyDescription == null) { // We cannot do anything without a property description return null; } IPropertyStore store = ShellPropertyCollection.CreateDefaultPropertyStore(ParentShellObject); using (PropVariant propVar = new PropVariant()) { store.GetValue(ref propertyKey, propVar); // Release the Propertystore Marshal.ReleaseComObject(store); store = null; HResult hr = Description.NativePropertyDescription.FormatForDisplay(propVar, ref format, out formattedString); // Sometimes, the value cannot be displayed properly, such as for blobs // or if we get argument exception if (!CoreErrorHelper.Succeeded(hr)) throw new ShellException(hr); return formattedString; } } /// /// Get the property description object. /// public ShellPropertyDescription Description { get { return description; } } /// /// Gets the case-sensitive name of a property as it is known to the system, /// regardless of its localized name. /// public string CanonicalName { get { return Description.CanonicalName; } } /// /// Clears the value of the property. /// public void ClearValue() { using (PropVariant propVar = new PropVariant()) { StorePropVariantValue(propVar); } } /// /// Gets the value for this property using the generic Object type. /// To obtain a specific type for this value, use the more type strong /// Property<T> class. /// Also, you can only set a value for this type using Property<T> /// public object ValueAsObject { get { using (PropVariant propVar = new PropVariant()) { if (ParentShellObject != null) { IPropertyStore store = ShellPropertyCollection.CreateDefaultPropertyStore(ParentShellObject); store.GetValue(ref propertyKey, propVar); Marshal.ReleaseComObject(store); store = null; } else if (NativePropertyStore != null) { NativePropertyStore.GetValue(ref propertyKey, propVar); } return propVar != null ? propVar.Value : null; } } } /// /// Gets the associated runtime type. /// public Type ValueType { get { // The type for this object need to match that of the description Debug.Assert(Description.ValueType == typeof(T)); return Description.ValueType; } } /// /// Gets the image reference path and icon index associated with a property value (Windows 7 only). /// public IconReference IconReference { get { if (!CoreHelpers.RunningOnWin7) { throw new PlatformNotSupportedException(LocalizedMessages.ShellPropertyWindows7); } GetImageReference(); int index = (imageReferenceIconIndex.HasValue ? imageReferenceIconIndex.Value : -1); return new IconReference(imageReferencePath, index); } } /// /// Gets or sets a value that determines if a value can be truncated. The default for this property is false. /// /// /// An will be thrown if /// this property is not set to true, and a property value was set /// but later truncated. /// /// public bool AllowSetTruncatedValue { get; set; } #endregion } }