Comment puis-je générer dynamiquement des colonnes dans un DataGrid WPF?

j'essaie d'afficher les résultats d'une requête dans un WPF datagrid. Le type de source Itemss auquel je suis lié est IEnumerable<dynamic> . Comme les champs retournés ne sont pas déterminés avant l'exécution, Je ne connais pas le type de données tant que la requête n'est pas évaluée. Chaque " rangée "est retournée comme une ExpandoObject avec des propriétés dynamiques représentant les champs.

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

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

y a-t-il quoi qu'il en soit pour faire cette Déclaration ou dois-je me raccrocher impérativement à un certain C#?

MODIFIER

Ok cela me donnera 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, il suffit de comprendre comment lier les colonnes aux valeurs Idictionnaires.

27
demandé sur dkackman 2009-12-31 02:19:04

4 réponses

en fin de compte, je devais faire deux choses:

  1. générer les colonnes manuellement à partir de la liste des propriétés retournées par la requête
  2. mettre en place une liaison de données de l'objet

après cela, la liaison de données intégrée a fonctionné très bien et ne semblait pas avoir de problème à obtenir les valeurs de la propriété du 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);
}
24
répondu dkackman 2011-08-28 18:40:40

le problème ici est que le clr va créer des colonnes pour L'ExpandoObject lui - même-mais il n'y a aucune garantie qu'un groupe D'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 que quelque chose comme les types de Linq anonymous fonctionnerait mieux pour vous. Je ne sais pas quel type de datagrid vous utilisez, mais la reliure devrait être identique pour tous. Voici un exemple simple pour le datagrid telerik.

lien de telerik forums

ce n'est pas vraiment dynamique, les types doivent être connus au moment de la compilation - mais c'est une façon facile de configurer quelque chose comme ça à l'exécution.

si vous n'avez vraiment aucune idée de quel type de champs vous allez afficher le problème devient un peu plus Poilu. Les solutions possibles sont:

avec dynamic linq vous pouvez créer des types anonymes en utilisant une chaîne de caractères à l'exécution - que vous pouvez assembler à partir des résultats de votre requête. Exemple d'utilisation du deuxième lien:

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

dans tous les cas, l'idée de base est de mettre d'une manière ou d'une autre Le grid item à une collection d'objets dont partagé propriétés publiques peuvent être trouvées par réflexion.

5
répondu Egor 2010-01-01 00:06:30

ma réponse Dynamique de la colonne de liaison dans le code Xaml

j'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 avec des propriétés simples. Voir cet article pour les détails sur la façon de générer un tel type

alors pour réellement utiliser le type, faire quelque chose comme ça

Dim rows as List(Of DynamicItem)
Dim row As DynamicItem = CType(Activator.CreateInstance(dynamicType), DynamicItem)
row("Name") = "Foo"
rows.Add(row)
dataGrid.DataContext = rows
4
répondu kenwarner 2017-05-23 10:29:35

bien qu'il y ait une réponse acceptée par L'OP, elle utilise AutoGenerateColumns="False" qui n'est pas exactement ce que la question initiale demandait. Heureusement, il peut être résolu avec l'auto-générés colonnes. 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 l'implémentation de ICustomTypeDescriptor , vous pouvez principalement utiliser les fonctions statiques de TypeDescriptor d'une manière triviale. GetProperties() est celui qui nécessite une mise en œuvre réelle: lire les propriétés existantes et ajouter vos propriétés dynamiques.

comme PropertyDescriptor est Abstrait, vous devez en 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) {
    ...
  }
1
répondu Gábor 2017-10-01 23:16:29