Question

Je cherche à afficher les résultats d'une requête dans un DataGrid WPF. Le type ItemsSource Je se liant à IEnumerable<dynamic> est. Comme les champs renvoyés ne sont pas déterminés avant l'exécution, je ne sais pas le type de données jusqu'à ce que la requête est évaluée. Chaque « ligne » est retourné comme un ExpandoObject avec des propriétés dynamiques représentant les champs.

Il était mon espoir que AutoGenerateColumns (comme ci-dessous) serait en mesure de générer des colonnes d'un ExpandoObject comme il le fait avec un type statique, mais il ne semble pas.

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

Y at-il de toute façon de le faire déclarative ou dois-je accrocher à impérieusement avec une C #?

EDIT

Ok cela va me faire les bonnes colonnes:

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

Alors maintenant, juste besoin de comprendre comment lier les colonnes aux valeurs IDictionary.

Était-ce utile?

La solution

En fin de compte que je devais faire deux choses:

  1. Générer les colonnes manuellement à partir de la liste des propriétés de la requête:
  2. Configurer un objet DataBinding

Après que les données intégrée et liaison coups de pied a bien fonctionné et ne semblent avoir aucun problème pour trouver les valeurs de propriété de la ExpandoObject.

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

et

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

Autres conseils

Le problème ici est que le clr va créer des colonnes pour le ExpandoObject lui-même - mais il n'y a aucune garantie qu'un groupe de ExpandoObjects partagent les mêmes propriétés entre eux, aucune règle pour le moteur de savoir quelles colonnes doivent être créées.

Peut-être quelque chose comme Linq types anonymes fonctionnerait mieux pour vous. Je ne sais pas quel genre de DataGrid que vous utilisez, mais à caractère contraignant doit être identique pour tous. Voici un exemple simple pour le DataGrid telerik.
lien à Forums Telerik

Ce n'est pas en fait vraiment dynamique, les types doivent être connus au moment de la compilation - mais c'est un moyen facile de créer quelque chose comme ça lors de l'exécution.

Si vous avez vraiment aucune idée de ce genre de champs que vous exposerons le problème est un peu plus poilu. Les solutions possibles sont:

  • Création d'un mappage de type à l'exécution en utilisant Reflection.Emit, je pense qu'il est possible de créer un convertisseur de valeur générique qui accepte les résultats de la requête, créez un nouveau type (et maintenir une liste mise en cache) et retourner une liste d'objets . Création d'un nouveau type dynamique suivrait le même algorithme que vous utilisez déjà pour créer les ExpandoObjects
    MSDN sur Reflection.Emit
    Un article vieux mais utile sur CodeProject
  • Utilisation dynamique Linq - ce qui est probablement le plus simple moyen plus rapide de le faire
    Utilisation dynamique LINQ

    de liaison dynamique en XAML

    Je l'ai utilisé une approche qui suit le modèle de ce pseudo-code

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

    DynamicTypeHelper.GetDynamicType () génère un type ayant des propriétés simples. Voir ce poste les détails sur la façon de générer un tel type

    Ensuite, utiliser effectivement le type, faire quelque chose comme ceci

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

Bien qu'il y ait une réponse acceptée par l'OP, il utilise AutoGenerateColumns="False" qui n'est pas exactement ce que la question initiale a demandé. Heureusement, il peut être résolu avec des colonnes générées automatiquement ainsi. La clé de la solution est le DynamicObject qui peut avoir des propriétés statiques et dynamiques:

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

Pour la mise en œuvre de ICustomTypeDescriptor, vous pouvez utiliser la plupart des fonctions statiques de TypeDescriptor de manière triviale. GetProperties() est celui qui nécessite la mise en œuvre réelle:. la lecture des propriétés existantes et en ajoutant vos proches dynamiques

Comme PropertyDescriptor est abstraite, vous devez hériter:

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) {
    ...
  }
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top