Pregunta

Estoy intentando mostrar los resultados de una consulta en una cuadrícula de datos de WPF.El tipo de ItemsSource al que me estoy vinculando es IEnumerable<dynamic>.Como los campos devueltos no se determinan hasta el tiempo de ejecución, no sé el tipo de datos hasta que se evalúa la consulta.Cada "fila" se devuelve como un ExpandoObject con propiedades dinámicas que representan los campos.

Era mi esperanza que AutoGenerateColumns (como a continuación) podría generar columnas a partir de un ExpandoObject como ocurre con un tipo estático pero no lo parece.

<DataGrid AutoGenerateColumns="True" ItemsSource="{Binding Results}"/>

¿Hay alguna forma de hacer esto de forma declarativa o tengo que conectarme imperativamente con algo de C#?

EDITAR

Ok, esto me dará las columnas correctas:

// ExpandoObject implements IDictionary<string,object> 
IEnumerable<IDictionary<string, object>> rows = dataGrid1.ItemsSource.OfType<IDictionary<string, object>>();
IEnumerable<string> columns = rows.SelectMany(d => d.Keys).Distinct(StringComparer.OrdinalIgnoreCase);
foreach (string s in columns)
    dataGrid1.Columns.Add(new DataGridTextColumn { Header = s });

Ahora sólo hay que descubrir cómo vincular las columnas a los valores del IDictionary.

¿Fue útil?

Solución

Al final necesitaba hacer dos cosas:

  1. Genere las columnas manualmente a partir de la lista de propiedades devueltas por la consulta
  2. Configurar un objeto DataBinding

Después de eso, el enlace de datos incorporado se activó y funcionó bien y no pareció tener ningún problema para sacar los valores de propiedad del ExpandoObject.

<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Results}" />

y

// Since there is no guarantee that all the ExpandoObjects have the 
// same set of properties, get the complete list of distinct property names
// - this represents the list of columns
var rows = dataGrid1.ItemsSource.OfType<IDictionary<string, object>>();
var columns = rows.SelectMany(d => d.Keys).Distinct(StringComparer.OrdinalIgnoreCase);

foreach (string text in columns)
{
    // now set up a column and binding for each property
    var column = new DataGridTextColumn 
    {
        Header = text,
        Binding = new Binding(text)
    };

    dataGrid1.Columns.Add(column);
}

Otros consejos

El problema aquí es que el CLR creará columnas para la propia ExpandoObject - pero no hay ninguna garantía de que un grupo de ExpandoObjects comparten las mismas propiedades entre sí, hay una regla para que el motor sé a qué se deben crear las columnas.

Tal vez algo así como tipos anónimos LINQ funcionaría mejor para usted. No sé qué clase de cuadrícula de datos que está utilizando, pero la unión debería debería ser idéntico para todos ellos. Aquí está un ejemplo sencillo para la cuadrícula de datos telerik.
enlace a Los foros de Telerik

Esto no es en realidad verdaderamente dinámica, los tipos tienen que ser conocido en tiempo de compilación - pero esto es una manera fácil de crear algo como esto en tiempo de ejecución.

Si realmente no tiene idea de qué tipo de campos que se expondrá el problema se vuelve un poco más peludo. Las posibles soluciones son:

Con LINQ dinámica puede crear tipos anónimos utilizando una cadena en tiempo de ejecución - que se puede ensamblar a partir de los resultados de la consulta. Ejemplo de uso de la segunda enlace:

var orders = db.Orders.Where("OrderDate > @0", DateTime.Now.AddDays(-30)).Select("new(OrderID, OrderDate)");

En cualquier caso, la idea básica es fijar de alguna manera el itemgrid a una colección de objetos cuyo compartida propiedades públicas se pueden encontrar por la reflexión.

Enlace dinámico en Xaml

He utilizado un enfoque que sigue el patrón de este pseudocódigo

columns = New DynamicTypeColumnList()
columns.Add(New DynamicTypeColumn("Name", GetType(String)))
dynamicType = DynamicTypeHelper.GetDynamicType(columns)

DynamicTypeHelper.GetDynamicType () genera un tipo con propiedades simples. Ver este post para los detalles sobre cómo generar este tipo a

A continuación, a utilizar el tipo de realidad, hacer algo como esto

Dim rows as List(Of DynamicItem)
Dim row As DynamicItem = CType(Activator.CreateInstance(dynamicType), DynamicItem)
row("Name") = "Foo"
rows.Add(row)
dataGrid.DataContext = rows

A pesar de que hay una respuesta aceptada por la OP, utiliza AutoGenerateColumns="False" que no es exactamente lo que la pregunta original pidió. Afortunadamente, se puede resolver con columnas generadas automáticamente también. La clave para la solución es el DynamicObject que puede tener tanto estática y propiedades dinámicas:

public class MyObject : DynamicObject, ICustomTypeDescriptor {
  // The object can have "normal", usual properties if you need them:
  public string Property1 { get; set; }
  public int Property2 { get; set; }

  public MyObject() {
  }

  public override IEnumerable<string> GetDynamicMemberNames() {
    // in addition to the "normal" properties above,
    // the object can have some dynamically generated properties
    // whose list we return here:
    return list_of_dynamic_property_names;
  }

  public override bool TryGetMember(GetMemberBinder binder, out object result) {
    // for each dynamic property, we need to look up the actual value when asked:
    if (<binder.Name is a correct name for your dynamic property>) {
      result = <whatever data binder.Name means>
      return true;
    }
    else {
      result = null;
      return false;
    }
  }

  public override bool TrySetMember(SetMemberBinder binder, object value) {
    // for each dynamic property, we need to store the actual value when asked:
    if (<binder.Name is a correct name for your dynamic property>) {
      <whatever storage binder.Name means> = value;
      return true;
    }
    else
      return false;
  }

  public PropertyDescriptorCollection GetProperties() {
    // This is where we assemble *all* properties:
    var collection = new List<PropertyDescriptor>();
    // here, we list all "standard" properties first:
    foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(this, true))
      collection.Add(property);
    // and dynamic ones second:
    foreach (string name in GetDynamicMemberNames())
      collection.Add(new CustomPropertyDescriptor(name, typeof(property_type), typeof(MyObject)));
    return new PropertyDescriptorCollection(collection.ToArray());
  }

  public PropertyDescriptorCollection GetProperties(Attribute[] attributes) => TypeDescriptor.GetProperties(this, attributes, true);
  public AttributeCollection GetAttributes() => TypeDescriptor.GetAttributes(this, true);
  public string GetClassName() => TypeDescriptor.GetClassName(this, true);
  public string GetComponentName() => TypeDescriptor.GetComponentName(this, true);
  public TypeConverter GetConverter() => TypeDescriptor.GetConverter(this, true);
  public EventDescriptor GetDefaultEvent() => TypeDescriptor.GetDefaultEvent(this, true);
  public PropertyDescriptor GetDefaultProperty() => TypeDescriptor.GetDefaultProperty(this, true);
  public object GetEditor(Type editorBaseType) => TypeDescriptor.GetEditor(this, editorBaseType, true);
  public EventDescriptorCollection GetEvents() => TypeDescriptor.GetEvents(this, true);
  public EventDescriptorCollection GetEvents(Attribute[] attributes) => TypeDescriptor.GetEvents(this, attributes, true);
  public object GetPropertyOwner(PropertyDescriptor pd) => this;
}

Para la ejecución ICustomTypeDescriptor, se puede utilizar la mayoría de las funciones estáticas de TypeDescriptor de una manera trivial. GetProperties() es el que requiere la implementación real:. la lectura de las propiedades existentes y añadiendo sus dinámicas

Como PropertyDescriptor es abstracto, tiene que heredarlo:

public class CustomPropertyDescriptor : PropertyDescriptor {
  private Type componentType;

  public CustomPropertyDescriptor(string propertyName, Type componentType)
    : base(propertyName, new Attribute[] { }) {
    this.componentType = componentType;
  }

  public CustomPropertyDescriptor(string propertyName, Type componentType, Attribute[] attrs)
    : base(propertyName, attrs) {
    this.componentType = componentType;
  }

  public override bool IsReadOnly => false;

  public override Type ComponentType => componentType;
  public override Type PropertyType => typeof(property_type);

  public override bool CanResetValue(object component) => true;
  public override void ResetValue(object component) => SetValue(component, null);

  public override bool ShouldSerializeValue(object component) => true;

  public override object GetValue(object component) {
    return ...;
  }

  public override void SetValue(object component, object value) {
    ...
  }
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top