using System;
using System.ComponentModel;
using System.Linq.Expressions;
using System.Reflection;
namespace Inspectron.Settings
{
///
/// A specialization of System.ComponentModel.PropertyDescriptor that is bound
/// to a specific property of an object or type. If the property's setter is private,
/// this BoundPropertyDescriptor's IsReadOnly property is true.
/// Use this class to expose an object or type's property for property editing.
public class BoundPropertyDescriptor : PropertyDescriptor
{
public delegate object OnClickDelegate(object sender, EventArgs e);
public OnClickDelegate OnClick { get; set; }
///
/// Constructor for instance properties
/// Property owner
/// Lambda expression that accesses the property;
/// e.g., () => myObject.MyProperty
/// Property display name
/// Property category
/// Property description
public BoundPropertyDescriptor(
object owner,
Expression> expression,
string displayName,
string category,
string description)
: this(displayName, category, description)
{
PropertyInfo propertyInfo = GetPropertyInfo(expression);
Init(owner, null, null, propertyInfo, null, null);
}
///
/// Constructor for static properties
/// Type holding static property
/// Lambda expression that accesses the property;
/// e.g., () => MyClass.MyProperty
/// Property display name
/// Property category
/// Property description
public BoundPropertyDescriptor(
Type ownerType,
Expression> expression,
string displayName,
string category,
string description)
: this(displayName, category, description)
{
PropertyInfo propertyInfo = GetPropertyInfo(expression);
Init(null, ownerType, null, propertyInfo, null, null);
}
///
/// Constructor for instance properties
/// Property owner
/// Lambda expression that accesses the property;
/// e.g., () => myObject.MyProperty
/// Property display name
/// Property category
/// Property description
/// Editor for property
/// TypeConverter for property
public BoundPropertyDescriptor(
object owner,
Expression> expression,
string displayName,
string category,
string description,
object editor,
TypeConverter converter)
: this(displayName, category, description)
{
PropertyInfo propertyInfo = GetPropertyInfo(expression);
Init(owner, null, null, propertyInfo, editor, converter);
}
///
/// Constructor for static properties
/// Type holding static property
/// Lambda expression that accesses the property;
/// e.g., () => MyClass.MyProperty
/// Property display name
/// Property category
/// Property description
/// Editor for property
/// TypeConverter for property
public BoundPropertyDescriptor(
Type ownerType,
Expression> expression,
string displayName,
string category,
string description,
object editor,
TypeConverter converter)
: this(displayName, category, description)
{
PropertyInfo propertyInfo = GetPropertyInfo(expression);
Init(null, ownerType, null, propertyInfo, editor, converter);
}
///
/// Constructor for instance properties
/// Property owner
/// Property name
/// Property display name
/// Property category
/// Property description
public BoundPropertyDescriptor(
object owner,
string name,
string displayName,
string category,
string description)
: this(displayName, category, description)
{
Init(owner, null, name, null, null, null);
}
///
/// Constructor for instance properties
/// Property owner
/// Property name
/// Property display name
/// Property category
/// Property description
/// Editor for property
public BoundPropertyDescriptor(
object owner,
string name,
string displayName,
string category,
string description,
object editor)
: this(displayName, category, description)
{
Init(owner, null, name, null, editor, null);
}
///
/// Constructor for instance properties
/// Property owner
/// Property name
/// Property display name
/// Property category
/// Property description
/// Editor for property
/// TypeConverter for property
public BoundPropertyDescriptor(
object owner,
string name,
string displayName,
string category,
string description,
object editor,
TypeConverter converter)
: this(displayName, category, description)
{
Init(owner, null, name, null, editor, converter);
}
///
/// Constructor for static properties
/// Type holding static property
/// Property name
/// Property display name
/// Property category
/// Property description
public BoundPropertyDescriptor(
Type ownerType,
string name,
string displayName,
string category,
string description)
: this(displayName, category, description)
{
Init(null, ownerType, name, null, null, null);
}
///
/// Constructor for static properties
/// Type holding static property
/// Property name
/// Property display name
/// Property category
/// Property description
/// Editor for property
public BoundPropertyDescriptor(
Type ownerType,
string name,
string displayName,
string category,
string description,
object editor)
: this(displayName, category, description)
{
Init(null, ownerType, name, null, editor, null);
}
///
/// Constructor for static properties
/// Type holding static property
/// Property name
/// Property display name
/// Property category
/// Property description
/// Editor for property
/// TypeConverter for property
public BoundPropertyDescriptor(
Type ownerType,
string name,
string displayName,
string category,
string description,
object editor,
TypeConverter converter)
: this(displayName, category, description)
{
Init(null, ownerType, name, null, editor, converter);
}
private void Init(
object owner,
Type ownerType,
string name,
PropertyInfo propertyInfo,
object editor,
TypeConverter converter)
{
m_owner = owner;
// if given the property owner, ignore the ownerType parameter
if (owner != null)
ownerType = owner.GetType();
m_ownerType = ownerType;
if (string.IsNullOrEmpty(name))
{
if (propertyInfo == null)
throw new ArgumentException("either 'name' or 'propertyInfo' must be non-null");
name = propertyInfo.Name;
}
// if given the property info, don't use reflection to find it
_propertyInfo = propertyInfo;
if (_propertyInfo == null)
{
_propertyInfo = m_ownerType.GetProperty(
name,
BindingFlags.Public |
BindingFlags.Instance |
BindingFlags.Static);
if (_propertyInfo == null)
throw new ArgumentException(name + ": Property doesn't exist");
}
// look at "set_" method to determine if property is read-only.
// (PropertyInfo.CanWrite will return true if there is a set
// accessor, even if that accessor is made inaccessible using
// asymmetric accessor accessibility.)
MethodInfo setInfo = m_ownerType.GetMethod("set_" + name,
BindingFlags.Public |
BindingFlags.Instance |
BindingFlags.Static);
m_readOnly = (setInfo == null);
m_editor = editor;
m_typeConverter = converter;
}
private BoundPropertyDescriptor(
string displayName,
string category,
string description)
: base(displayName,
new Attribute[] { new CategoryAttribute(category),
new DescriptionAttribute(description), })
{
}
///
/// When overridden in a derived class, returns whether resetting an object changes its value
/// The component to test for reset capability
/// True iff resetting the component changes its value
public override bool CanResetValue(object component)
{
object defaultValue;
return GetDefaultValue(out defaultValue) && !Object.Equals(GetValue(null), defaultValue);
}
///
/// Gets the component this property is bound to. Is null, if bound to a static class's property.
public object Owner
{
get { return m_owner; }
}
///
/// Gets the type of the component this property is bound to
public override Type ComponentType
{
get { return m_ownerType; }
}
///
/// Gets the type of the property
public override Type PropertyType
{
get { return _propertyInfo.PropertyType; }
}
///
/// Gets whether this property is read-only
public override bool IsReadOnly
{
get { return m_readOnly; }
}
///
/// Resets the value for this property of the component to the default value
/// The component with the property value that is to be reset to the default value
public override void ResetValue(object component)
{
object defaultValue;
GetDefaultValue(out defaultValue);
SetValue(component, defaultValue);
}
///
/// Determines whether the value of this property needs to be persisted
/// The component with the property to be examined for persistence
/// True iff the property should be persisted
public override bool ShouldSerializeValue(object component)
{
object val = GetValue(component);
object defaultValue;
if (!GetDefaultValue(out defaultValue) && val == null)
return false;
else
return !val.Equals(defaultValue);
}
///
/// Returns the Owner's value (if the Owner is not null) or the component's value of the property
/// Component to examine
/// The value of a property
public override object GetValue(object component)
{
if (m_owner != null)
component = m_owner;
return _propertyInfo.GetValue(component, null);
}
///
/// Sets the value of the Owner (if not null) or component
/// Component
/// The new value
public override void SetValue(object component, object value)
{
if (m_owner != null)
component = m_owner;
_propertyInfo.SetValue(component, value, null);
}
///
/// Gets the default value
/// Is set to the default value or null, if it couldn't be determined
/// Whether or not the default value was determined
/// Uses reflection to look for a DefaultValueAttribute
public virtual bool GetDefaultValue(out object result)
{
bool foundDefault = false;
result = null;
object[] attributes = _propertyInfo.GetCustomAttributes(typeof(DefaultValueAttribute), false);
if (attributes.Length > 0)
{
foundDefault = true;
result = (attributes[0] as DefaultValueAttribute).Value;
if (result != null && result.GetType() != _propertyInfo.PropertyType)
{
// Default value type is not the same as the property type; convert it.
// This can happen if the property's type is not CLS-compliant (e.g. UInt32).
TypeConverter converter = TypeDescriptor.GetConverter(result);
if (converter.CanConvertTo(_propertyInfo.PropertyType))
{
result = converter.ConvertTo(result, _propertyInfo.PropertyType);
}
else
{
// Try using the converter associated with the source instead of the target
// (Not sure if this is useful for not, but can it hurt?)
converter = TypeDescriptor.GetConverter(_propertyInfo.PropertyType);
if (converter.CanConvertFrom(result.GetType()))
{
result = converter.ConvertFrom(result);
}
}
}
}
return foundDefault;
}
///
/// Returns an editor of the specified type
/// Base type of editor, which is used to differentiate between multiple
/// editors that a property supports
/// An instance of the requested editor type, or null if an editor cannot be found
public override object GetEditor(Type editorBaseType)
{
if (m_editor != null &&
editorBaseType.IsInstanceOfType(m_editor))
{
return m_editor;
}
if (editorBaseType.IsEnum)
{
}
return base.GetEditor(editorBaseType);
}
///
/// Gets the type converter for this property
public override TypeConverter Converter
{
get
{
if (m_typeConverter != null)
return m_typeConverter;
return base.Converter;
}
}
public PropertyInfo PropertyInfo => _propertyInfo;
// Does not return null. Will throw an exception if 'expression' is poorly formed.
private static PropertyInfo GetPropertyInfo(Expression> expression)
{
PropertyInfo propertyInfo = null;
MemberExpression memberExpression = expression.Body as MemberExpression;
if (memberExpression != null)
{
// this is the usual case for when a property has a public getter and setter
propertyInfo = memberExpression.Member as PropertyInfo;
}
else
{
// if the setter is private, the expression is a UnaryExpression type for some reason.
UnaryExpression unaryExpression = expression.Body as UnaryExpression;
if (unaryExpression != null)
{
memberExpression = unaryExpression.Operand as MemberExpression;
if (memberExpression != null)
propertyInfo = memberExpression.Member as PropertyInfo;
}
}
if (propertyInfo == null)
throw new ArgumentException(
"lambda expression was not properly formed." +
" Should be \" => myObject.MyProperty\" or" +
" \" => MyClass.MyProperty\"");
return propertyInfo;
}
object m_owner;
Type m_ownerType;
PropertyInfo _propertyInfo;
bool m_readOnly;
private object m_editor;
private TypeConverter m_typeConverter;
}
}