Domanda

Sto tentando di visualizzare i risultati di una query in un DataGrid WPF. Il tipo ItemsSource sto vincolanti per è IEnumerable<dynamic>. Come i campi restituiti non sono determinati fino al runtime non so il tipo di dati fino a quando la query viene valutata. Ogni "riga" viene restituito come ExpandoObject con proprietà dinamiche rappresentano i campi.

E 'stata la mia speranza che AutoGenerateColumns (come di seguito) sarebbe in grado di generare le colonne da un ExpandoObject come fa con un tipo statico, ma non sembra.

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

Esiste un modo per fare questo in modo dichiarativo o devo agganciare in imperativamente con un po 'C #?

Modifica

Ok questo otterrà me le colonne corrette:

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

Quindi, ora solo bisogno di capire come associare le colonne con i valori IDictionary.

È stato utile?

Soluzione

In definitiva avevo bisogno di fare due cose:

  1. Genera le colonne manualmente dalla lista delle proprietà restituito dalla query
  2. Configurare un oggetto DataBinding

Dopo che i dati incorporati nel legare calci e funzionava bene e non sembra avere alcun problema di ottenere i valori delle proprietà fuori dal 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);
}

Altri suggerimenti

Il problema qui è che il CLR creerà colonne per l'ExpandoObject in sé - ma non v'è alcuna garanzia che un gruppo di ExpandoObjects condividono le stesse proprietà tra l'altro, nessuna regola per il motore per sapere quali colonne devono essere creati.

Forse qualcosa di simile Linq tipi anonimi avrebbe funzionato meglio per voi. Non so che tipo di un DataGrid che si sta utilizzando, ma vincolante dovrebbe dovrebbero essere identiche per tutti loro. Ecco un semplice esempio per il datagrid Telerik.
link forum Telerik

Questo non è in realtà veramente dinamico, i tipi devono essere noti al momento della compilazione - ma questo è un modo facile di impostare qualcosa di simile in fase di esecuzione.

Se avete veramente idea di che tipo di campi che si esporrà il problema diventa un po 'più peloso. Le soluzioni possibili sono:

Con LINQ dinamica è possibile creare tipi anonimi utilizzando una stringa in fase di esecuzione - che è possibile assemblare dai risultati della query. Esempio di utilizzo dal secondo link:

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

In ogni caso, l'idea di base è quella di impostare in qualche modo l'itemgrid a una collezione di oggetti la cui condiviso proprietà pubbliche può essere trovato dalla riflessione.

binding dinamico in XAML

Ho usato un approccio che segue il modello di questo pseudocodice

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

DynamicTypeHelper.GetDynamicType () genera un tipo di proprietà semplici. Vedere questo post i dettagli su come generare un tale tipo

Quindi utilizzare effettivamente il tipo, fare qualcosa di simile

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

Anche se non c'è una risposta accettata dal PO, utilizza AutoGenerateColumns="False" che non è esattamente quello che la domanda iniziale chiesto. Per fortuna, si può essere risolto con le colonne generate automaticamente pure. La chiave per la soluzione è la DynamicObject che può avere proprietà statiche e dinamiche:

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

Per l'attuazione ICustomTypeDescriptor, è possibile per lo più utilizzare le funzioni statiche di TypeDescriptor in modo banale. GetProperties() è quella che richiede implementazione reale:. la lettura delle proprietà esistenti e aggiungendo i vostri dinamici

Come PropertyDescriptor è astratta, si ha in possesso di questo:

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) {
    ...
  }
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top