web-dev-qa-db-ja.com

WPF DataGridで列を動的に生成するにはどうすればよいですか?

WPFデータグリッドでクエリの結果を表示しようとしています。バインドするItemsSourceタイプはIEnumerable<dynamic>。返されるフィールドは実行時まで決定されないため、クエリが評価されるまでデータのタイプがわかりません。各「行」は、フィールドを表す動的プロパティを持つExpandoObjectとして返されます。

AutoGenerateColumns(以下のような)がExpandoObjectから列を生成できるようになるのが私の希望でしたが、静的型の場合と同じように見えます。

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

とにかく宣言的にこれを行うにはありますか、またはいくつかのC#で命令的にフックする必要がありますか?

[〜#〜] edit [〜#〜]

これで正しい列が表示されます:

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

そのため、列をIDictionary値にバインドする方法を理解する必要があります。

30
dkackman

最終的に、2つのことを行う必要がありました。

  1. クエリによって返されたプロパティのリストから列を手動で生成します
  2. DataBindingオブジェクトを設定します

その後、組み込みのデータバインディングが開始されて正常に動作し、ExpandoObjectからプロパティ値を取得する際に問題は発生しなかったようです。

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

そして

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

ここでの問題は、clrがExpandoObject自体の列を作成することです。ただし、ExpandoObjectsのグループが相互に同じプロパティを共有するという保証はなく、エンジンが作成する必要がある列を認識するルールはありません。

おそらく、Linqの匿名型のようなものがより適切に機能するでしょう。どんな種類のデータグリッドを使用しているのかわかりませんが、すべてのバインディングが同一でなければなりません。次に、telerikデータグリッドの簡単な例を示します。
telerikフォーラムへのリンク

これは実際には動的ではありません。コンパイル時に型を知る必要がありますが、これは実行時にこのようなものを設定する簡単な方法です。

問題を表示するフィールドの種類が本当にわからない場合は、もう少し難しくなります。可能な解決策は次のとおりです。

動的linqを使用すると、実行時に文字列を使用して匿名型を作成できます-クエリの結果から組み立てることができます。 2番目のリンクの使用例:

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

いずれにせよ、基本的な考え方は、itemgridを何らかの方法でオブジェクトのコレクションに設定することです 共有した パブリックプロパティはリフレクションによって見つけることができます。

5
Egor

Xamlの動的列バインディング からの私の答え

私はこの擬似コードのパターンに従うアプローチを使用しました

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

DynamicTypeHelper.GetDynamicType()は、単純なプロパティを持つ型を生成します。そのようなタイプを生成する方法の詳細については、 this post を参照してください

次に、実際にタイプを使用するには、次のようにします

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
kenwarner

OPによって受け入れられた回答がありますが、_AutoGenerateColumns="False"_を使用しますが、これは元の質問が正確に要求したものではありません。幸いなことに、自動生成された列でも解決できます。ソリューションの鍵は、静的プロパティと動的プロパティの両方を持つことができるDynamicObjectです:

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

ICustomTypeDescriptor実装の場合、ほとんどの場合、TypeDescriptorの静的関数を簡単に使用できます。 GetProperties()は、実際の実装が必要なものです。既存のプロパティを読み取り、動的なプロパティを追加します。

PropertyDescriptorは抽象であるため、継承する必要があります。

_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
Gábor