WPFデータグリッドで列を動的に生成するにはどうすればよいですか?
-
22-09-2019 - |
質問
WPF Datagridにクエリの結果を表示しようとしています。 ISに拘束されているItemsSourceタイプ IEnumerable<dynamic>
. 。返されたフィールドは、実行時まで決定されないため、クエリが評価されるまでデータのタイプがわかりません。それぞれの「行」は、として返されます ExpandoObject
フィールドを表す動的特性を備えています。
それは私の希望でした AutoGenerateColumns
(以下のように)からから列を生成できます ExpandoObject
静的タイプで行うように、しかしそれは見えません。
<DataGrid AutoGenerateColumns="True" ItemsSource="{Binding Results}"/>
とにかくこれを宣言的に行うか、あるいはいくつかのC#で命令的にフックする必要がありますか?
編集
わかりました、これは私に正しい列を取得します:
// 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値にバインドする方法を把握する必要があります。
解決
最終的に、私は2つのことをする必要がありました:
- クエリによって返されるプロパティのリストから手動で列を生成します
- データバインディングオブジェクトを設定します
その後、組み込みのデータバインディングが始まり、正常に動作し、プロパティ値を取得する問題はないようです。 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);
}
他のヒント
ここでの問題は、CLRがExpandOObject自体の列を作成することですが、拡張オブジェクトのグループが互いに同じプロパティを共有するという保証はありません。エンジンがどの列を作成する必要があるかを知るためのルールはありません。
おそらく、linqの匿名タイプのようなものはあなたにとってよりうまく機能するでしょう。どんな種類のデータグリッドを使用しているのかわかりませんが、バインディングはそれらすべてについて同一でなければなりません。 Telerik Datagridの簡単な例を次に示します。
Telerikフォーラムへのリンク
これは実際には本当に動的ではありません。タイプはコンパイル時に知られる必要がありますが、これは実行時にこのようなものを簡単に設定する簡単な方法です。
どのような分野を表示するか本当にわからない場合は、問題を表示することがもう少し毛むくじゃらになります。可能な解決策は次のとおりです。
- Reflection.emitを使用して実行時にタイプマッピングを作成すると、クエリ結果を受け入れ、新しいタイプを作成(およびキャッシュリストを維持する)、オブジェクトのリストを返す一般的な値コンバーターを作成することが可能だと思います。新しいダイナミックタイプを作成すると、拡張オブジェクトの作成に既に使用しているのと同じアルゴリズムに従います
Reflection.emitのmsdn
CodeProjectに関する古いが便利な記事 - ダイナミックLINQを使用する - これはおそらくそれを行うためのよりシンプルなより速い方法です。
動的linqを使用します
動的なlinqを使用して匿名タイプの頭痛を回避します
動的LINQを使用すると、実行時に文字列を使用して匿名タイプを作成できます。これは、クエリの結果から組み立てることができます。 2番目のリンクからの使用例:
var orders = db.Orders.Where("OrderDate > @0", DateTime.Now.AddDays(-30)).Select("new(OrderID, OrderDate)");
いずれにせよ、基本的なアイデアは、どうにかしてアイテムグリッドをオブジェクトのコレクションに設定することです。 共有 パブリックプロパティは、反射によって見つけることができます。
からの私の答え XAMLの動的カラム結合
この擬似コードのパターンに従うアプローチを使用しました
columns = New DynamicTypeColumnList()
columns.Add(New DynamicTypeColumn("Name", GetType(String)))
dynamicType = DynamicTypeHelper.GetDynamicType(columns)
dynamictypehelper.getDynamictype()は、単純な特性を持つタイプを生成します。見る この郵便受け そのようなタイプを生成する方法の詳細については
その後、実際にタイプを使用するには、このようなことをします
Dim rows as List(Of DynamicItem)
Dim row As DynamicItem = CType(Activator.CreateInstance(dynamicType), DynamicItem)
row("Name") = "Foo"
rows.Add(row)
dataGrid.DataContext = rows
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) {
...
}