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