Pergunta

Estou tentando exibir os resultados de uma consulta em um datagrid WPF. O tipo de itens que eu estou vinculando é IEnumerable<dynamic>. Como os campos retornados não são determinados até o tempo de execução, não sei o tipo de dados até que a consulta seja avaliada. Cada "linha" é devolvida como um ExpandoObject com propriedades dinâmicas representando os campos.

Era minha esperança que AutoGenerateColumns (como abaixo) seria capaz de gerar colunas de um ExpandoObject Como acontece com um tipo estático, mas não parece.

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

Existe alguma maneira de fazer isso declarativamente ou eu tenho que prender imperativamente com algum C#?

EDITAR

Ok, isso vai me dar as colunas corretas:

// 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 });

Portanto, agora só precisa descobrir como vincular as colunas aos valores de idicicionário.

Foi útil?

Solução

Em última análise, eu precisava fazer duas coisas:

  1. Gerar as colunas manualmente a partir da lista de propriedades retornadas pela consulta
  2. Configure um objeto de banco de dados

Depois disso, a ligação de dados interna entrou em ação e funcionou bem e não parecia ter nenhum problema para tirar os valores da propriedade do ExpandoObject.

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

e

// 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);
}

Outras dicas

O problema aqui é que o CLR criará colunas para o próprio ExpandoObject - mas não há garantia de que um grupo de expandos compartilhe as mesmas propriedades entre si, nenhuma regra para o mecanismo saber quais colunas precisam ser criadas.

Talvez algo como o Linq Anonymous Tipos funcionasse melhor para você. Não sei que tipo de datagrid você está usando, mas a ligação deve ser idêntica para todos eles. Aqui está um exemplo simples para o Telerik DataGrid.
Link para fóruns Telerik

Na verdade, isso não é realmente dinâmico, os tipos precisam ser conhecidos em tempo de compilação - mas essa é uma maneira fácil de definir algo assim em tempo de execução.

Se você realmente não tem idéia de que tipo de campos estará exibindo o problema fica um pouco mais peludo. As soluções possíveis são:

Com o dinâmico LINQ, você pode criar tipos anônimos usando uma string no tempo de execução - que você pode montar a partir dos resultados da sua consulta. Exemplo de uso do segundo link:

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

De qualquer forma, a idéia básica é de alguma forma definir o itemGrid como uma coleção de objetos cujos compartilhado As propriedades públicas podem ser encontradas por reflexão.

minha resposta de Cordação dinâmica da coluna em xaml

Eu usei uma abordagem que segue o padrão deste pseudocódigo

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

DynamictypeHelper.getDynamicty () gera um tipo com propriedades simples. Ver esta postagem Para os detalhes sobre como gerar esse tipo

Então, para realmente usar o tipo, faça algo assim

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

Embora exista uma resposta aceita pelo OP, ele usa AutoGenerateColumns="False" O que não é exatamente o que a pergunta original fez. Felizmente, ele também pode ser resolvido com colunas geradas automaticamente. A chave para a solução é o DynamicObject Isso pode ter propriedades estáticas e 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 o ICustomTypeDescriptor implementação, você pode usar principalmente as funções estáticas de TypeDescriptor de maneira trivial. GetProperties() é o que requer implementação real: ler as propriedades existentes e adicionar suas dinâmicas.

Como PropertyDescriptor é abstrato, você tem que herdá -lo:

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 em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top