Я сделал именно это с помощью интерфейса ICustomTypeDescriptor и словаря.
Реализация ICustomTypeDescriptor для динамических свойств:
У меня недавно было требование привязать представление сетки к объекту записи, который может иметь любое количество свойств, которые можно добавлять и удалять во время выполнения. Это должно было позволить пользователю добавить новый столбец в набор результатов для ввода дополнительного набора данных.
Это может быть достигнуто за счет того, что каждая строка данных представляет собой словарь, ключом которого является имя свойства, а значением - строка или класс, который может хранить значение свойства для указанной строки. Конечно, наличие списка объектов словаря не может быть привязано к сетке. Вот здесь и вступает ICustomTypeDescriptor.
Путем создания класса-оболочки для словаря и его привязки к интерфейсу ICustomTypeDescriptor поведение для возврата свойств объекта может быть переопределено.
Взгляните на реализацию класса данных строки ниже:
/// <summary>
/// Class to manage test result row data functions
/// </summary>
public class TestResultRowWrapper : Dictionary<string, TestResultValue>, ICustomTypeDescriptor
{
//- METHODS -----------------------------------------------------------------------------------------------------------------
#region Methods
/// <summary>
/// Gets the Attributes for the object
/// </summary>
AttributeCollection ICustomTypeDescriptor.GetAttributes()
{
return new AttributeCollection(null);
}
/// <summary>
/// Gets the Class name
/// </summary>
string ICustomTypeDescriptor.GetClassName()
{
return null;
}
/// <summary>
/// Gets the component Name
/// </summary>
string ICustomTypeDescriptor.GetComponentName()
{
return null;
}
/// <summary>
/// Gets the Type Converter
/// </summary>
TypeConverter ICustomTypeDescriptor.GetConverter()
{
return null;
}
/// <summary>
/// Gets the Default Event
/// </summary>
/// <returns></returns>
EventDescriptor ICustomTypeDescriptor.GetDefaultEvent()
{
return null;
}
/// <summary>
/// Gets the Default Property
/// </summary>
PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty()
{
return null;
}
/// <summary>
/// Gets the Editor
/// </summary>
object ICustomTypeDescriptor.GetEditor(Type editorBaseType)
{
return null;
}
/// <summary>
/// Gets the Events
/// </summary>
EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes)
{
return new EventDescriptorCollection(null);
}
/// <summary>
/// Gets the events
/// </summary>
EventDescriptorCollection ICustomTypeDescriptor.GetEvents()
{
return new EventDescriptorCollection(null);
}
/// <summary>
/// Gets the properties
/// </summary>
PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes)
{
List<propertydescriptor> properties = new List<propertydescriptor>();
//Add property descriptors for each entry in the dictionary
foreach (string key in this.Keys)
{
properties.Add(new TestResultPropertyDescriptor(key));
}
//Get properties also belonging to this class also
PropertyDescriptorCollection pdc = TypeDescriptor.GetProperties(this.GetType(), attributes);
foreach (PropertyDescriptor oPropertyDescriptor in pdc)
{
properties.Add(oPropertyDescriptor);
}
return new PropertyDescriptorCollection(properties.ToArray());
}
/// <summary>
/// gets the Properties
/// </summary>
PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties()
{
return ((ICustomTypeDescriptor)this).GetProperties(null);
}
/// <summary>
/// Gets the property owner
/// </summary>
object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd)
{
return this;
}
#endregion Methods
//---------------------------------------------------------------------------------------------------------------------------
}
Примечание. В методе GetProperties я могу кэшировать PropertyDescriptors, когда-то считываемые для повышения производительности, но, поскольку я добавляю и удаляю столбцы во время выполнения, я всегда хочу их перестроить
В методе GetProperties вы также заметите, что дескрипторы свойств, добавленные для словарных статей, имеют тип TestResultPropertyDescriptor. Это пользовательский класс дескриптора свойства, который управляет настройкой и получением свойств. Посмотрите на реализацию ниже:
/// <summary>
/// Property Descriptor for Test Result Row Wrapper
/// </summary>
public class TestResultPropertyDescriptor : PropertyDescriptor
{
//- PROPERTIES --------------------------------------------------------------------------------------------------------------
#region Properties
/// <summary>
/// Component Type
/// </summary>
public override Type ComponentType
{
get { return typeof(Dictionary<string, TestResultValue>); }
}
/// <summary>
/// Gets whether its read only
/// </summary>
public override bool IsReadOnly
{
get { return false; }
}
/// <summary>
/// Gets the Property Type
/// </summary>
public override Type PropertyType
{
get { return typeof(string); }
}
#endregion Properties
//- CONSTRUCTOR -------------------------------------------------------------------------------------------------------------
#region Constructor
/// <summary>
/// Constructor
/// </summary>
public TestResultPropertyDescriptor(string key)
: base(key, null)
{
}
#endregion Constructor
//- METHODS -----------------------------------------------------------------------------------------------------------------
#region Methods
/// <summary>
/// Can Reset Value
/// </summary>
public override bool CanResetValue(object component)
{
return true;
}
/// <summary>
/// Gets the Value
/// </summary>
public override object GetValue(object component)
{
return ((Dictionary<string, TestResultValue>)component)[base.Name].Value;
}
/// <summary>
/// Resets the Value
/// </summary>
public override void ResetValue(object component)
{
((Dictionary<string, TestResultValue>)component)[base.Name].Value = string.Empty;
}
/// <summary>
/// Sets the value
/// </summary>
public override void SetValue(object component, object value)
{
((Dictionary<string, TestResultValue>)component)[base.Name].Value = value.ToString();
}
/// <summary>
/// Gets whether the value should be serialized
/// </summary>
public override bool ShouldSerializeValue(object component)
{
return false;
}
#endregion Methods
//---------------------------------------------------------------------------------------------------------------------------
}
Основные свойства этого класса - это GetValue и SetValue. Здесь вы видите компонент, который преобразуется в словарь, а значение ключа внутри него устанавливается или восстанавливается. Важно, чтобы словарь в этом классе был того же типа, что и класс-оболочка Row, иначе приведение не удастся. Когда дескриптор создан, ключ (имя свойства) передается и используется для запроса словаря для получения правильного значения.
Взято из моего блога по адресу:
ICustomTypeDescriptor Реализация динамических свойств