//Copyright (c) Microsoft Corporation. All rights reserved.
using System;
using System.Runtime.InteropServices;
using MS.WindowsAPICodePack.Internal;
namespace Microsoft.WindowsAPICodePack.Shell.PropertySystem
{
/// <summary>
/// Creates a property writer capable of setting multiple properties for a given ShellObject.
/// </summary>
public class ShellPropertyWriter : IDisposable
{
private ShellObject parentShellObject = null;
// Reference to our writable PropertyStore
internal IPropertyStore writablePropStore = null;
/// <summary>
/// The value was set but truncated in a string value or rounded if a numeric value.
/// </summary>
protected const int InPlaceStringTruncated = 0x00401A0;
internal ShellPropertyWriter(ShellObject parent)
{
ParentShellObject = parent;
// Open the property store for this shell object...
Guid guid = new Guid(ShellIIDGuid.IPropertyStore);
try
{
int hr = ParentShellObject.NativeShellItem2.GetPropertyStore(
ShellNativeMethods.GETPROPERTYSTOREFLAGS.GPS_READWRITE,
ref guid,
out writablePropStore);
if (!CoreErrorHelper.Succeeded(hr))
{
throw new ExternalException("Unable to get writable property store for this property.",
Marshal.GetExceptionForHR(hr));
}
else
{
// If we succeed in creating a valid property store for this ShellObject,
// then set it on the parent shell object for others to use.
// Once this writer is closed/commited, we will set the
if (ParentShellObject.NativePropertyStore == null)
ParentShellObject.NativePropertyStore = writablePropStore;
}
}
catch (InvalidComObjectException e)
{
throw new ExternalException("Unable to get writable property store for this property.", e);
}
catch (InvalidCastException)
{
throw new ExternalException("Unable to get writable property store for this property.");
}
}
/// <summary>
/// Reference to parent ShellObject (associated with this writer)
/// </summary>
protected ShellObject ParentShellObject
{
get { return parentShellObject; }
private set { parentShellObject = value; }
}
/// <summary>
/// Writes the given property key and value.
/// </summary>
/// <param name="key">The property key.</param>
/// <param name="value">The value associated with the key.</param>
public void WriteProperty(PropertyKey key, object value)
{
WriteProperty(key, value, true);
}
/// <summary>
/// Writes the given property key and value. To allow truncation of the given value, set allowTruncatedValue
/// to true.
/// </summary>
/// <param name="key">The property key.</param>
/// <param name="value">The value associated with the key.</param>
/// <param name="allowTruncatedValue">True to allow truncation (default); otherwise False.</param>
/// <exception cref="System.InvalidOperationException">If the writable property store is already
/// closed.</exception>
/// <exception cref="System.ArgumentOutOfRangeException">If AllowTruncatedValue is set to false
/// and while setting the value on the property it had to be truncated in a string or rounded in
/// a numeric value.</exception>
public void WriteProperty(PropertyKey key, object value, bool allowTruncatedValue)
{
if (writablePropStore == null)
throw new InvalidOperationException("Writeable store has been closed.");
PropVariant propVar = PropVariant.FromObject(value);
int result = writablePropStore.SetValue(ref key, ref propVar);
if (!allowTruncatedValue && (result == InPlaceStringTruncated))
{
// At this point we can't revert back the commit
// so don't commit, close the property store and throw an exception
// to let the user know.
Marshal.ReleaseComObject(writablePropStore);
writablePropStore = null;
throw new ArgumentOutOfRangeException("value", "A value had to be truncated in a string or rounded if a numeric value. Set AllowTruncatedValue to true to prevent this exception.");
}
if (!CoreErrorHelper.Succeeded(result))
{
throw new ExternalException("Unable to set property.", Marshal.GetExceptionForHR(result));
}
}
/// <summary>
/// Writes the specified property given the canonical name and a value.
/// </summary>
/// <param name="canonicalName">The canonical name.</param>
/// <param name="value">The property value.</param>
public void WriteProperty(string canonicalName, object value)
{
WriteProperty(canonicalName, value, true);
}
/// <summary>
/// Writes the specified property given the canonical name and a value. To allow truncation of the given value, set allowTruncatedValue
/// to true.
/// </summary>
/// <param name="canonicalName">The canonical name.</param>
/// <param name="value">The property value.</param>
/// <param name="allowTruncatedValue">True to allow truncation (default); otherwise False.</param>
/// <exception cref="System.ArgumentException">If the given canonical name is not valid.</exception>
public void WriteProperty(string canonicalName, object value, bool allowTruncatedValue)
{
// Get the PropertyKey using the canonicalName passed in
PropertyKey propKey;
int result = PropertySystemNativeMethods.PSGetPropertyKeyFromName(canonicalName, out propKey);
if (!CoreErrorHelper.Succeeded(result))
{
throw new ArgumentException(
"The given CanonicalName is not valid.",
Marshal.GetExceptionForHR(result));
}
WriteProperty(propKey, value, allowTruncatedValue);
}
/// <summary>
/// Writes the specified property using an IShellProperty and a value.
/// </summary>
/// <param name="shellProperty">The property name.</param>
/// <param name="value">The property value.</param>
public void WriteProperty(IShellProperty shellProperty, object value)
{
WriteProperty(shellProperty, value, true);
}
/// <summary>
/// Writes the specified property given an IShellProperty and a value. To allow truncation of the given value, set allowTruncatedValue
/// to true.
/// </summary>
/// <param name="shellProperty">The property name.</param>
/// <param name="value">The property value.</param>
/// <param name="allowTruncatedValue">True to allow truncation (default); otherwise False.</param>
public void WriteProperty(IShellProperty shellProperty, object value, bool allowTruncatedValue)
{
WriteProperty(shellProperty.PropertyKey, value, allowTruncatedValue);
}
/// <summary>
/// Writes the specified property using a strongly-typed ShellProperty and a value.
/// </summary>
/// <typeparam name="T">The type of the property name.</typeparam>
/// <param name="shellProperty">The property name.</param>
/// <param name="value">The property value.</param>
public void WriteProperty<T>(ShellProperty<T> shellProperty, T value)
{
WriteProperty<T>(shellProperty, value, true);
}
/// <summary>
/// Writes the specified property given a strongly-typed ShellProperty and a value. To allow truncation of the given value, set allowTruncatedValue
/// to true.
/// </summary>
/// <typeparam name="T">The type of the property name.</typeparam>
/// <param name="shellProperty">The property name.</param>
/// <param name="value">The property value.</param>
/// <param name="allowTruncatedValue">True to allow truncation (default); otherwise False.</param>
public void WriteProperty<T>(ShellProperty<T> shellProperty, T value, bool allowTruncatedValue)
{
WriteProperty(shellProperty.PropertyKey, value, allowTruncatedValue);
}
#region IDisposable Members
/// <summary>
/// Release the native objects.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
///
/// </summary>
~ShellPropertyWriter()
{
Dispose(false);
}
/// <summary>
/// Release the native and managed objects.
/// </summary>
/// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.
/// </param>
protected virtual void Dispose(bool disposing)
{
Close();
}
/// <summary>
/// Call this method to commit the writes (calls to WriteProperty method)
/// and dispose off the writer.
/// </summary>
public void Close()
{
// Close the property writer (commit, etc)
if (writablePropStore != null)
{
HRESULT hr = writablePropStore.Commit();
Marshal.ReleaseComObject(writablePropStore);
writablePropStore = null;
}
ParentShellObject.NativePropertyStore = null;
}
#endregion
}
}