//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
}
}