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