He hecho exactamente esto con una interfaz ICustomTypeDescriptor y un diccionario.
Implementación de ICustomTypeDescriptor para propiedades dinámicas:
Recientemente tuve el requisito de vincular una vista de cuadrícula a un objeto de registro que podría tener cualquier cantidad de propiedades que se pueden agregar y eliminar en tiempo de ejecución. Esto fue para permitir que un usuario agregue una nueva columna a un conjunto de resultados para ingresar un conjunto adicional de datos.
Esto se puede lograr teniendo cada 'fila' de datos como un diccionario con la clave como el nombre de la propiedad y el valor como una cadena o una clase que puede almacenar el valor de la propiedad para la fila especificada. Por supuesto, tener una lista de objetos de diccionario no podrá vincularse a una cuadrícula. Aquí es donde entra ICustomTypeDescriptor.
Al crear una clase contenedora para el diccionario y hacer que se adhiera a la interfaz ICustomTypeDescriptor, se puede anular el comportamiento para devolver propiedades para un objeto.
Eche un vistazo a la implementación de la clase de 'fila' de datos a continuación:
public class TestResultRowWrapper : Dictionary<string, TestResultValue>, ICustomTypeDescriptor
{
#region Methods
AttributeCollection ICustomTypeDescriptor.GetAttributes()
{
return new AttributeCollection(null);
}
string ICustomTypeDescriptor.GetClassName()
{
return null;
}
string ICustomTypeDescriptor.GetComponentName()
{
return null;
}
TypeConverter ICustomTypeDescriptor.GetConverter()
{
return null;
}
EventDescriptor ICustomTypeDescriptor.GetDefaultEvent()
{
return null;
}
PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty()
{
return null;
}
object ICustomTypeDescriptor.GetEditor(Type editorBaseType)
{
return null;
}
EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes)
{
return new EventDescriptorCollection(null);
}
EventDescriptorCollection ICustomTypeDescriptor.GetEvents()
{
return new EventDescriptorCollection(null);
}
PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes)
{
List<propertydescriptor> properties = new List<propertydescriptor>();
foreach (string key in this.Keys)
{
properties.Add(new TestResultPropertyDescriptor(key));
}
PropertyDescriptorCollection pdc = TypeDescriptor.GetProperties(this.GetType(), attributes);
foreach (PropertyDescriptor oPropertyDescriptor in pdc)
{
properties.Add(oPropertyDescriptor);
}
return new PropertyDescriptorCollection(properties.ToArray());
}
PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties()
{
return ((ICustomTypeDescriptor)this).GetProperties(null);
}
object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd)
{
return this;
}
#endregion Methods
}
Nota: En el método GetProperties, pude almacenar en caché los PropertyDescriptors una vez leídos para el rendimiento, pero como estoy agregando y eliminando columnas en tiempo de ejecución, siempre quiero que se reconstruyan
También notará en el método GetProperties que los descriptores de propiedad agregados para las entradas del diccionario son del tipo TestResultPropertyDescriptor. Se trata de una clase de descriptor de propiedades personalizada que gestiona cómo se establecen y recuperan las propiedades. Eche un vistazo a la implementación a continuación:
public class TestResultPropertyDescriptor : PropertyDescriptor
{
#region Properties
public override Type ComponentType
{
get { return typeof(Dictionary<string, TestResultValue>); }
}
public override bool IsReadOnly
{
get { return false; }
}
public override Type PropertyType
{
get { return typeof(string); }
}
#endregion Properties
#region Constructor
public TestResultPropertyDescriptor(string key)
: base(key, null)
{
}
#endregion Constructor
#region Methods
public override bool CanResetValue(object component)
{
return true;
}
public override object GetValue(object component)
{
return ((Dictionary<string, TestResultValue>)component)[base.Name].Value;
}
public override void ResetValue(object component)
{
((Dictionary<string, TestResultValue>)component)[base.Name].Value = string.Empty;
}
public override void SetValue(object component, object value)
{
((Dictionary<string, TestResultValue>)component)[base.Name].Value = value.ToString();
}
public override bool ShouldSerializeValue(object component)
{
return false;
}
#endregion Methods
}
Las principales propiedades a tener en cuenta en esta clase son GetValue y SetValue. Aquí puede ver el componente que se convierte como diccionario y el valor de la clave dentro de él se establece o se recupera. Es importante que el diccionario de esta clase sea del mismo tipo en la clase contenedora Row, de lo contrario, la conversión fallará. Cuando se crea el descriptor, se pasa la clave (nombre de propiedad) y se utiliza para consultar el diccionario para obtener el valor correcto.
Tomado de mi blog en:
Implementación de ICustomTypeDescriptor para propiedades dinámicas