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.
4 réponses
en fin de compte, je devais faire deux choses:
- générer les colonnes manuellement à partir de la liste des propriétés retournées par la requête
- 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);
}
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:
- création d'une cartographie de type à l'exécution en utilisant la réflexion.Émettre, je pense qu'il est possible de créer une valeur générique convertisseur qui accepterait les résultats de votre requête, créer un nouveau type (et de maintenir une mise en cache de la liste), et renvoie une liste d'objets. Créer un nouveau type dynamique suivrait le même algorithme que vous utilisez déjà pour créer les objets Expandoob
MSDN on Reflection.Emit
un vieux mais utile article sur projet codeprojet
- utilisant Dynamic Linq - c'est probablement le moyen le plus simple et le plus rapide de le faire.
Dynamiques À L'Aide De Linq
Se déplacer anonyme de type maux de tête dynamique avec linq
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.
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
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) {
...
}