如何在WPF DataGrid中动态生成列?

时间:2009-12-30 23:19:04

标签: wpf dynamic datagrid expandoobject

我试图在WPF数据网格中显示查询结果。我绑定的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值。

4 个答案:

答案 0 :(得分:27)

最终我需要做两件事:

  1. 从查询返回的属性列表中手动生成列
  2. 设置DataBinding对象
  3. 之后内置的数据绑定启动并运行正常,似乎没有任何问题从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 :(得分:5)

这里的问题是clr将为ExpandoObject本身创建列 - 但不能保证一组ExpandoObjects在彼此之间共享相同的属性,引擎无法知道需要创建哪些列。

也许像Linq匿名类型的东西对你来说会更好。我不知道你正在使用什么类型的数据网格,但绑定对于所有这些都应该是相同的。这是telerik数据网格的一个简单示例 link to telerik forums

这实际上并不是真正动态的,需要在编译时知道类型 - 但这是在运行时设置这样的东西的简单方法。

如果你真的不知道你将展示什么样的领域,问题就会变得多毛了。可能的解决方案是:

使用动态linq,您可以在运行时使用字符串创建匿名类型 - 您可以根据查询结果进行汇编。第二个链接的示例用法:

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

在任何情况下,基本的想法是以某种方式将itemgrid设置为一组对象,其共享公共属性可以通过反射找到。

答案 2 :(得分: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

答案 3 :(得分: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是抽象的,你必须继承它:

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