using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Drawing.Design; using System.Linq; namespace DelftTools.Utils.PropertyBag { /// /// Represents the method that will handle the GetValue and SetValue events of the /// PropertyBag class. /// public delegate void PropertySpecEventHandler(object sender, PropertySpecEventArgs e); /// /// Represents a collection of custom properties that can be selected into a /// PropertyGrid to provide functionality beyond that of the simple reflection /// normally used to query an object's properties. /// public class PropertyBag : ICustomTypeDescriptor { #region PropertySpecCollection class definition /// /// Encapsulates a collection of PropertySpec objects. /// [Serializable] public class PropertySpecCollection : IList { private ArrayList innerArray; /// /// Initializes a new instance of the PropertySpecCollection class. /// public PropertySpecCollection() { innerArray = new ArrayList(); } /// /// Gets the number of elements in the PropertySpecCollection. /// /// /// The number of elements contained in the PropertySpecCollection. /// public int Count { get { return innerArray.Count; } } /// /// Gets a value indicating whether the PropertySpecCollection has a fixed size. /// /// /// true if the PropertySpecCollection has a fixed size; otherwise, false. /// public bool IsFixedSize { get { return false; } } /// /// Gets a value indicating whether the PropertySpecCollection is read-only. /// public bool IsReadOnly { get { return false; } } /// /// Gets a value indicating whether access to the collection is synchronized (thread-safe). /// /// /// true if access to the PropertySpecCollection is synchronized (thread-safe); otherwise, false. /// public bool IsSynchronized { get { return false; } } /// /// Gets an object that can be used to synchronize access to the collection. /// /// /// An object that can be used to synchronize access to the collection. /// object ICollection.SyncRoot { get { return null; } } /// /// Gets or sets the element at the specified index. /// In C#, this property is the indexer for the PropertySpecCollection class. /// /// The zero-based index of the element to get or set. /// /// The element at the specified index. /// public PropertySpec this[int index] { get { return (PropertySpec)innerArray[index]; } set { innerArray[index] = value; } } /// /// Adds a PropertySpec to the end of the PropertySpecCollection. /// /// The PropertySpec to be added to the end of the PropertySpecCollection. /// The PropertySpecCollection index at which the value has been added. public int Add(PropertySpec value) { int index = innerArray.Add(value); return index; } /// /// Adds the elements of an array of PropertySpec objects to the end of the PropertySpecCollection. /// /// The PropertySpec array whose elements should be added to the end of the /// PropertySpecCollection. public void AddRange(PropertySpec[] array) { innerArray.AddRange(array); } /// /// Removes all elements from the PropertySpecCollection. /// public void Clear() { innerArray.Clear(); } /// /// Determines whether a PropertySpec is in the PropertySpecCollection. /// /// The PropertySpec to locate in the PropertySpecCollection. The element to locate /// can be a null reference (Nothing in Visual Basic). /// true if item is found in the PropertySpecCollection; otherwise, false. public bool Contains(PropertySpec item) { return innerArray.Contains(item); } /// /// Determines whether a PropertySpec with the specified name is in the PropertySpecCollection. /// /// The name of the PropertySpec to locate in the PropertySpecCollection. /// true if item is found in the PropertySpecCollection; otherwise, false. public bool Contains(string name) { foreach (PropertySpec spec in innerArray) if (spec.Name == name) return true; return false; } /// /// Copies the entire PropertySpecCollection to a compatible one-dimensional Array, starting at the /// beginning of the target array. /// /// The one-dimensional Array that is the destination of the elements copied /// fromPropertySpecCollection. The Array must have zero-based indexing. public void CopyTo(PropertySpec[] array) { innerArray.CopyTo(array); } /// /// Copies the PropertySpecCollection or a portion of it to a one-dimensional array. /// /// The one-dimensional Array that is the destination of the elements copied /// from the collection. /// The zero-based index in array at which copying begins. public void CopyTo(PropertySpec[] array, int index) { innerArray.CopyTo(array, index); } /// /// Returns an enumerator that can iterate through the PropertySpecCollection. /// /// An IEnumerator for the entire PropertySpecCollection. public IEnumerator GetEnumerator() { return innerArray.GetEnumerator(); } /// /// Searches for the specified PropertySpec and returns the zero-based index of the first /// occurrence within the entire PropertySpecCollection. /// /// The PropertySpec to locate in the PropertySpecCollection. /// The zero-based index of the first occurrence of value within the entire PropertySpecCollection, /// if found; otherwise, -1. public int IndexOf(PropertySpec value) { return innerArray.IndexOf(value); } /// /// Searches for the PropertySpec with the specified name and returns the zero-based index of /// the first occurrence within the entire PropertySpecCollection. /// /// The name of the PropertySpec to locate in the PropertySpecCollection. /// The zero-based index of the first occurrence of value within the entire PropertySpecCollection, /// if found; otherwise, -1. public int IndexOf(string name) { int i = 0; foreach (PropertySpec spec in innerArray) { if (spec.Name == name) return i; i++; } return -1; } /// /// Inserts a PropertySpec object into the PropertySpecCollection at the specified index. /// /// The zero-based index at which value should be inserted. /// The PropertySpec to insert. public void Insert(int index, PropertySpec value) { innerArray.Insert(index, value); } /// /// Removes the first occurrence of a specific object from the PropertySpecCollection. /// /// The PropertySpec to remove from the PropertySpecCollection. public void Remove(PropertySpec obj) { innerArray.Remove(obj); } /// /// Removes the property with the specified name from the PropertySpecCollection. /// /// The name of the PropertySpec to remove from the PropertySpecCollection. public void Remove(string name) { int index = IndexOf(name); RemoveAt(index); } /// /// Removes the object at the specified index of the PropertySpecCollection. /// /// The zero-based index of the element to remove. public void RemoveAt(int index) { innerArray.RemoveAt(index); } /// /// Copies the elements of the PropertySpecCollection to a new PropertySpec array. /// /// A PropertySpec array containing copies of the elements of the PropertySpecCollection. public PropertySpec[] ToArray() { return (PropertySpec[])innerArray.ToArray(typeof(PropertySpec)); } #region Explicit interface implementations for ICollection and IList /// /// This member supports the .NET Framework infrastructure and is not intended to be used directly from your code. /// void ICollection.CopyTo(Array array, int index) { CopyTo((PropertySpec[])array, index); } /// /// This member supports the .NET Framework infrastructure and is not intended to be used directly from your code. /// int IList.Add(object value) { return Add((PropertySpec)value); } /// /// This member supports the .NET Framework infrastructure and is not intended to be used directly from your code. /// bool IList.Contains(object obj) { return Contains((PropertySpec)obj); } /// /// This member supports the .NET Framework infrastructure and is not intended to be used directly from your code. /// object IList.this[int index] { get { return ((PropertySpecCollection)this)[index]; } set { ((PropertySpecCollection)this)[index] = (PropertySpec)value; } } /// /// This member supports the .NET Framework infrastructure and is not intended to be used directly from your code. /// int IList.IndexOf(object obj) { return IndexOf((PropertySpec)obj); } /// /// This member supports the .NET Framework infrastructure and is not intended to be used directly from your code. /// void IList.Insert(int index, object value) { Insert(index, (PropertySpec)value); } /// /// This member supports the .NET Framework infrastructure and is not intended to be used directly from your code. /// void IList.Remove(object value) { Remove((PropertySpec)value); } #endregion } #endregion #region PropertySpecDescriptor class definition private class PropertySpecDescriptor : PropertyDescriptor { private PropertyBag bag; private PropertySpec item; public PropertySpecDescriptor(PropertySpec item, PropertyBag bag, string name, Attribute[] attrs) : base(name, attrs) { this.bag = bag; this.item = item; } public override Type ComponentType { get { return item.GetType(); } } public override bool IsReadOnly { get { return (Attributes.Matches(ReadOnlyAttribute.Yes)); } } public override bool IsBrowsable { get { ReEvaluateAttributes(); return base.IsBrowsable; } } public override Type PropertyType { get { return Type.GetType(item.TypeName); } } public override bool CanResetValue(object component) { if (item.DefaultValue == null) return false; else return !this.GetValue(component).Equals(item.DefaultValue); } public override object GetValue(object component) { return ReEvaluateAttributes().Value; } public override void ResetValue(object component) { SetValue(component, item.DefaultValue); } public override void SetValue(object component, object value) { // Have the property bag raise an event to set the current value // of the property. PropertySpecEventArgs e = new PropertySpecEventArgs(item, value); bag.OnSetValue(e); } public override bool ShouldSerializeValue(object component) { object val = this.GetValue(component); if (item.DefaultValue == null && val == null) return false; else return !val.Equals(item.DefaultValue); } private PropertySpecEventArgs ReEvaluateAttributes() { // Have the property bag raise an event to get the current value // of the property and evaluate the dynamic attributes var e = new PropertySpecEventArgs(item, null); bag.OnGetValue(e); base.AttributeArray = e.Property.Attributes; return e; } } #endregion private string defaultProperty; private PropertySpecCollection properties; /// /// Initializes a new instance of the PropertyBag class. /// public PropertyBag() { defaultProperty = null; properties = new PropertySpecCollection(); } /// /// Gets or sets the name of the default property in the collection. /// public string DefaultProperty { get { return defaultProperty; } set { defaultProperty = value; } } /// /// Gets the collection of properties contained within this PropertyBag. /// public PropertySpecCollection Properties { get { return properties; } } /// /// Occurs when a PropertyGrid requests the value of a property. /// public event PropertySpecEventHandler GetValue; /// /// Occurs when the user changes the value of a property in a PropertyGrid. /// public event PropertySpecEventHandler SetValue; /// /// Raises the GetValue event. /// /// A PropertySpecEventArgs that contains the event data. protected virtual void OnGetValue(PropertySpecEventArgs e) { if (GetValue != null) GetValue(this, e); } /// /// Raises the SetValue event. /// /// A PropertySpecEventArgs that contains the event data. protected virtual void OnSetValue(PropertySpecEventArgs e) { if (SetValue != null) SetValue(this, e); } #region ICustomTypeDescriptor explicit interface definitions // Most of the functions required by the ICustomTypeDescriptor are // merely pssed on to the default TypeDescriptor for this type, // which will do something appropriate. The exceptions are noted // below. AttributeCollection ICustomTypeDescriptor.GetAttributes() { return TypeDescriptor.GetAttributes(this, true); } string ICustomTypeDescriptor.GetClassName() { return TypeDescriptor.GetClassName(this, true); } string ICustomTypeDescriptor.GetComponentName() { return TypeDescriptor.GetComponentName(this, true); } System.ComponentModel.TypeConverter ICustomTypeDescriptor.GetConverter() { return TypeDescriptor.GetConverter(this, true); } EventDescriptor ICustomTypeDescriptor.GetDefaultEvent() { return TypeDescriptor.GetDefaultEvent(this, true); } PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty() { // This function searches the property list for the property // with the same name as the DefaultProperty specified, and // returns a property descriptor for it. If no property is // found that matches DefaultProperty, a null reference is // returned instead. if(defaultProperty == null && properties.Count != 0) { defaultProperty = properties[0].Name; } PropertySpec propertySpec = null; if (defaultProperty != null) { int index = properties.IndexOf(defaultProperty); propertySpec = properties[index]; } if (propertySpec != null) return new PropertySpecDescriptor(propertySpec, this, propertySpec.Name, null); else return null; } object ICustomTypeDescriptor.GetEditor(Type editorBaseType) { return TypeDescriptor.GetEditor(this, editorBaseType, true); } EventDescriptorCollection ICustomTypeDescriptor.GetEvents() { return TypeDescriptor.GetEvents(this, true); } EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes) { return TypeDescriptor.GetEvents(this, attributes, true); } PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties() { return ((ICustomTypeDescriptor)this).GetProperties(new Attribute[0]); } PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes) { // Rather than passing this function on to the default TypeDescriptor, // which would return the actual properties of PropertyBag, I construct // a list here that contains property descriptors for the elements of the // Properties list in the bag. var props = new List(); var propsToOrder = new List>(); foreach (PropertySpec property in properties) { var attrs = new ArrayList(); // If a category, description, editor, or type converter are specified // in the PropertySpec, create attributes to define that relationship. if (property.Category != null) attrs.Add(new CategoryAttribute(property.Category)); if (property.Description != null) attrs.Add(new DescriptionAttribute(property.Description)); if (property.EditorTypeName != null) attrs.Add(new EditorAttribute(property.EditorTypeName, typeof(UITypeEditor))); if (property.ConverterTypeName != null) attrs.Add(new TypeConverterAttribute(property.ConverterTypeName)); // Additionally, append the custom attributes associated with the // PropertySpec, if any. if (property.Attributes != null) attrs.AddRange(property.Attributes); Attribute[] attrArray = (Attribute[])attrs.ToArray(typeof(Attribute)); // Create a new property descriptor for the property item, and add // it to the list. var pd = new PropertySpecDescriptor(property,this, property.Name, attrArray); var propertyOrderAttribute = property.Attributes != null ? property.Attributes.OfType().FirstOrDefault() : null; if (propertyOrderAttribute != null) { propsToOrder.Add(new Tuple(propertyOrderAttribute.Order, pd)); } else { props.Add(pd); } } var orderedProperties = propsToOrder.OrderBy(p => p.First).Select(p => p.Second).ToList(); // Convert the list of PropertyDescriptors to a collection that the // ICustomTypeDescriptor can use, and return it. var browsableAttribute = attributes.OfType().FirstOrDefault(); var propertySpecDescriptors = (browsableAttribute != null) ? orderedProperties.Concat(props).Where(p => p.IsBrowsable == browsableAttribute.Browsable) : orderedProperties.Concat(props); return new PropertyDescriptorCollection(propertySpecDescriptors.ToArray()); } object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd) { return this; } #endregion } }