Frage

Ich versuche, die Ergebnisse einer Abfrage in einem WPF -DataGrid anzuzeigen. Der Artikel -Tour -Typ, an den ich bindet, ist IS IEnumerable<dynamic>. Da die zurückgegebenen Felder erst dann erst ermittelt werden, kenne ich den Typ der Daten erst, wenn die Abfrage bewertet wird. Jede "Reihe" wird als zurückgegeben ExpandoObject mit dynamischen Eigenschaften, die die Felder darstellen.

Es war meine Hoffnung, dass AutoGenerateColumns (wie unten) könnten in der Lage sein, Spalten von einem zu generieren ExpandoObject Wie es mit einem statischen Typ tut, erscheint es aber nicht.

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

Gibt es sowieso deklarativ, oder muss ich mit etwas C#imperativ einbinden?

BEARBEITEN

Ok, das bekommt mir die richtigen Spalten:

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

Machen Sie nun nur noch heraus, wie Sie die Spalten an die Idictionary -Werte binden können.

War es hilfreich?

Lösung

Letztendlich musste ich zwei Dinge tun:

  1. Generieren Sie die Spalten manuell aus der Liste der von der Abfrage zurückgegebenen Eigenschaften
  2. Richten Sie ein Datenbankobjekt ein

Danach begann die integrierte Datenbindung einwandfrei und schien kein Problem zu haben ExpandoObject.

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

und

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

Andere Tipps

Das Problem hierfür ist, dass die CLR Spalten für das ExplosionoObject selbst erstellt. Es gibt jedoch keine Garantie dafür, dass eine Gruppe von ExplosionsObjects die gleichen Eigenschaften untereinander hat. Keine Regel für die Engine, um zu wissen, welche Spalten erstellt werden müssen.

Vielleicht würde so etwas wie die anonymen Linq -Typen besser für Sie funktionieren. Ich weiß nicht, welche Art von DataGrid Sie verwenden, aber die Bindung sollte für alle identisch sein. Hier ist ein einfaches Beispiel für das Telerik -DataGrid.
Link zu Telerik -Foren

Dies ist nicht wirklich dynamisch, die Typen müssen zur Kompilierzeit bekannt sein - aber dies ist eine einfache Möglichkeit, so etwas zur Laufzeit zu setzen.

Wenn Sie wirklich keine Ahnung haben, welche Art von Feldern Sie zeigen, wird das Problem etwas haariger. Mögliche Lösungen sind:

Mit Dynamic linq können Sie anonyme Typen mit einer String zur Laufzeit erstellen - die Sie aus den Ergebnissen Ihrer Abfrage zusammenstellen können. Beispiel Verwendung aus dem zweiten Link:

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

In jedem Fall besteht die Grundidee darin, das ElementGrid irgendwie auf eine Sammlung von Objekten zu setzen, deren geteilt Öffentliche Eigenschaften können durch Reflexion gefunden werden.

meine Antwort von Dynamische Säulenbindung in XAML

Ich habe einen Ansatz verwendet, der dem Muster dieses Pseudocode folgt

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

Dynamictypehelper.getDynamictype () generiert einen Typ mit einfachen Eigenschaften. Sehen dieser Beitrag Für die Details zum Generieren eines solchen Typs

Um den Typ dann tatsächlich zu verwenden, machen Sie so etwas

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

Obwohl es eine akzeptierte Antwort des OP gibt, verwendet sie AutoGenerateColumns="False" Welches ist nicht genau das, wonach die ursprüngliche Frage gestellt wurde. Glücklicherweise kann es auch mit automatisch generierten Säulen gelöst werden. Der Schlüssel zur Lösung ist der DynamicObject Das kann sowohl statische als auch dynamische Eigenschaften haben:

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

Für die ICustomTypeDescriptor Implementierung können Sie meistens die statischen Funktionen von nutzen TypeDescriptor auf triviale Weise. GetProperties() ist diejenige, die eine echte Implementierung erfordert: Lesen der vorhandenen Eigenschaften und Hinzufügen Ihrer dynamischen.

Wie PropertyDescriptor ist abstrakt, müssen Sie es erben:

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) {
    ...
  }
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top