2009-12-30 84 views
26

我正在嘗試在WPF數據網格中顯示查詢的結果。我綁定的ItemsSource類型是IEnumerable<dynamic>。由於返回的字段直到運行時才確定,所以在查詢被評估之前我不知道數據的類型。每個「行」作爲ExpandoObject作爲表示字段的動態屬性返回。如何在WPF DataGrid中動態生成列?

這是我的希望,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的值。

回答

24

最終我需要做兩件事情:

  1. 生成從查詢返回的屬性列表中手動列
  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); 
} 
+1

這很好,但是你什麼時候執行這段代碼?當您在DataContextChanged上處理這個時,ItemsSource尚未設置 – Wouter 2011-09-28 09:57:30

+0

在我的實例中,ItemSource綁定到名爲Results的ViewModel屬性。我在視圖中有一個INotifyPrpertyChanged處理程序,對該屬性進行更改。 – dkackman 2011-09-28 23:15:29

+0

這是我的問題,但我偶然發現了一個問題。行驗證呢?您是否必須處理ExpandoObjects上的行驗證? – Ninglin 2015-11-19 15:25:15

5

這裏的問題是,clr會爲ExpandoObject本身創建列 - 但不能保證一組ExpandoObjects在彼此之間共享相同的屬性,沒有引擎知道需要創建哪些列的規則。

也許像Linq匿名類型的東西會更適合你。我不知道你正在使用什麼樣的數據網格,但綁定應該對所有的數據網格都是相同的。這是telerik datagrid的一個簡單例子。
link to telerik forums

這實際上並不是真正的動態,這些類型在編譯時需要知道 - 但這是在運行時設置類似的簡單方法。

如果你真的不知道你將顯示​​什麼樣的字段,問題會變得更多毛。可能的解決方案是:

使用動態LINQ可以創建使用字符串在運行時匿名類型 - 您可以從您的查詢的結果組合。從第二連桿實例:

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

在任何情況下,基本思想是將itemgrid某種方式設置爲對象,其共享公共性質可通過反射發現的集合。

+0

有問題的數據,所以設定確實不相符。事實上,沒有編譯時間知道它們會是什麼。我可以解決屬性一致性的問題,但令人遺憾的是,ExpandoObject對反射是不透明的(儘管我可以看到這是一個難以解決的問題)。 – dkackman 2009-12-31 20:04:52

+0

在這種情況下,動態linq可能會有所幫助,但您可能需要採用兩步法。解析數據一次以查看遇到哪些標籤,然後再次填充新對象列表。我想問題是,如果任何mp3文件具有定義的屬性,在將值映射到對象(動態或不是動態)之後,所有這些文件都必須具有該屬性。 – Egor 2010-01-01 00:12:06

4

我的回答從Dynamic column binding in 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 
+1

有趣的方法。我可能需要做類似的事情,但想避免發射碎片。同時使用Expando和Emitted類型似乎是多餘的。感謝您的鏈接;它給了我一些想法。 – dkackman 2009-12-31 20:08:14

1

雖然由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是抽象的,你要繼承它:從mp3文件中的標記來

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) { 
    ... 
    } 
+0

當把'ItemsSource'綁定到'ObservableCollection ' – georgiosd 2017-01-25 17:03:52

+0

這似乎不適用於我這是缺少的部分:http://www.reimers .dk/jacob-reimers-blog/auto-generating-datagrid-columns-from-dynamicobjects – georgiosd 2017-01-25 17:59:08

+0

那個鏈接現在已經死了;任何人都可以在此發佈完整答案嗎? – Sphynx 2017-10-01 13:00:29