Type descriptors for extensibility...

The TypeDescriptor architecture is used widely within the .NET framework and allows for greater extensibility than reflection.  The msdn article Type Descriptor Overview gives a good introduction to the model.  Whilst most often used in the design time environment, there are notable examples where used at runtime (such as the PropertyGrid), and having the capability to affect the TypeDescriptor at runtime opens up many possibilities.

In this example I will walk through a simple example to demonstrate how easy it is to override the TypeDescriptor at runtime to affect display and edit capability with the PropertyGrid.  The example will be to add a TypeDescriptor to a simple data type to demonstrate how the ReadOnly attribute can be changed based on runtime state.

In this example I use a simple data item type within the application:

    public partial class DataItem
    {
        [Description("Name of the data item")]
        [ReadOnly(true)]
        public string Name { get; set;}
        [Description("Data item description")]
        public string Description { get; set; }
        [Browsable(false)]
        public bool ReadOnly { get; set; }
    }

The Name property is decorated with a ReadOnly attribute and Description is editable, for simplicity I have included a ReadOnly property within the aim being that when set to ReadOnly the Description property will also be made ReadOnly.

The code below defines the TypeDescriptor infrastructure required.

    public class DataItemDescriptionProvider : TypeDescriptionProvider
    {
        public DataItemDescriptionProvider() : this(TypeDescriptor.GetProvider(typeof(DataItem)))
        {
        }
        public DataItemDescriptionProvider(TypeDescriptionProvider parent): base(parent)
        {
        }
        public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance)
        {
            ICustomTypeDescriptor td = base.GetTypeDescriptor(objectType, instance);
            td = new DataItemTypeDescriptor(td, instance as DataItem);
            return td;
        }
    }
    public class DataItemTypeDescriptor : CustomTypeDescriptor
    {
        private DataItem m_instance;
        public DataItemTypeDescriptor(ICustomTypeDescriptor parent, DataItem instance) : base(parent)
        {
            m_instance = instance;
        }
        public override PropertyDescriptorCollection GetProperties(Attribute[] attributes)
        {
            PropertyDescriptorCollection properties = base.GetProperties(attributes);
            if (m_instance.ReadOnly)
            {
                var p = from property in properties.OfType<PropertyDescriptor>()
                        select new DataItemReadOnlyPropertyDescriptor(true, property);
                return new PropertyDescriptorCollection(p.ToArray());
            }
            return properties;
        }
    }
    public class DataItemReadOnlyPropertyDescriptor: PropertyDescriptor
    {
        private PropertyDescriptor m_descriptor;
        private bool m_readOnly;
        
        public DataItemReadOnlyPropertyDescriptor(bool readOnly, PropertyDescriptor descriptor) : base(descriptor)
        {
            m_descriptor = descriptor;
            m_readOnly = readOnly;
        }
        #region abstract PropertyDescriptor implementation
        public override bool CanResetValue(object component)
        {
            return m_descriptor.CanResetValue(component);
        }
        public override Type ComponentType
        {
            get { return m_descriptor.ComponentType; }
        }
        public override object GetValue(object component)
        {
            return m_descriptor.GetValue(component);
        }
        public override bool IsReadOnly
        {
            get { return m_readOnly; }
        }
        public override Type PropertyType
        {
            get { return m_descriptor.PropertyType; }
        }
        public override void ResetValue(object component)
        {
            m_descriptor.ResetValue(component);
        }
        public override void SetValue(object component, object value)
        {
            m_descriptor.SetValue(component, value);
        }
        public override bool ShouldSerializeValue(object component)
        {
            return m_descriptor.ShouldSerializeValue(component);
        }
        #endregion
    }

The DataItemDescriptionProvider implements TypeDescriptionProvider overriding the GetTypeDescriptor method to return the specialized TypeProvider, later in our calling code we will hand this to TypeProvider for our Type.

The DataItemTypeDescriptor is where the work happens, implementing the CustomTypeDescriptor which is a simple default implementation of the ICustomTypeProvider (minimising the amount of typing required).  We are overriding the GetProperties method and extracting the properties from the base class, then (if the instance ReadOnly  is set) creating overridden PropertyDescriptors (which simply allows the setting of the ReadOnly attribute and includes lots of boiler plate to hand off to the base instance for all other requests). 

In usage code we need to hand the TypeDescriptionProvider to TypeProvider:

            TypeDescriptor.AddProvider(new DataItemDescriptionProvider(), typeof(DataItem));

To demonstrate I used this within a simple windows forms app with a property grid, show below:

 read EDITABLE

You can download the sample app here.

Comments are closed