如何在WPF数据网格中动态生成列?

如何在WPF数据网格中动态生成列?,wpf,dynamic,datagrid,expandoobject,Wpf,Dynamic,Datagrid,Expandoobject,我试图在WPF数据网格中显示查询结果。我绑定到的ItemsSource类型是IEnumerable。由于返回的字段直到运行时才确定,所以在计算查询之前,我不知道数据的类型。每个“行”作为一个ExpandoObject返回,动态属性表示字段 我希望,AutoGenerateColumns(如下所示)能够从ExpandoObject生成列,就像它使用静态类型一样,但它似乎没有 <DataGrid AutoGenerateColumns="True" ItemsSource="{Binding

我试图在WPF数据网格中显示查询结果。我绑定到的ItemsSource类型是
IEnumerable
。由于返回的字段直到运行时才确定,所以在计算查询之前,我不知道数据的类型。每个“行”作为一个
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 });
//ExpandoObject实现IDictionary
IEnumerable rows=dataGrid1.ItemsSource.OfType();
IEnumerable columns=rows.SelectMany(d=>d.Keys).Distinct(StringComparer.OrdinalIgnoreCase);
foreach(列中的字符串s)
添加(新的DataGridTextColumn{Header=s});

所以现在只需要弄清楚如何将列绑定到IDictionary值。

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

也许像Linq匿名类型这样的东西对你更合适。我不知道您使用的是哪种数据网格,但是绑定应该对所有数据网格都是相同的。以下是telerik数据网格的一个简单示例。

这实际上并不是动态的,需要在编译时知道类型——但这是一种在运行时设置类似内容的简单方法

如果你真的不知道你将显示什么样的字段,那么问题会变得更加棘手。可能的解决办法是:

  • 通过使用Reflection.Emit在运行时创建类型映射,我认为可以创建一个通用值转换器,该转换器将接受您的查询结果,创建一个新类型(并维护一个缓存列表),并返回一个对象列表。创建新的动态类型将遵循与创建ExpandoObjects相同的算法





  • 使用动态Linq-这可能是更简单、更快的方法。

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

var orders = db.Orders.Where("OrderDate > @0", DateTime.Now.AddDays(-30)).Select("new(OrderID, OrderDate)");
在任何情况下,基本思想都是以某种方式将itemgrid设置为对象的集合,这些对象的共享公共属性可以通过反射找到。

我的答案来自

我使用了一种遵循伪代码模式的方法

columns = New DynamicTypeColumnList()
columns.Add(New DynamicTypeColumn("Name", GetType(String)))
dynamicType = DynamicTypeHelper.GetDynamicType(columns)
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

最终我需要做两件事:

  • 从查询返回的属性列表手动生成列
  • 设置数据绑定对象
  • 在此之后,内置数据绑定开始工作,工作正常,从
    ExpandoObject
    获取属性值似乎没有任何问题

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

    //因为不能保证所有expandooobject都具有
    //同一组属性,获取不同属性名称的完整列表
    //-这表示列的列表
    var rows=dataGrid1.ItemsSource.OfType();
    var columns=rows.SelectMany(d=>d.Keys).Distinct(StringComparer.OrdinalIgnoreCase);
    foreach(列中的字符串文本)
    {
    //现在为每个属性设置一个列和绑定
    var column=新的DataGridTextColumn
    {
    标题=文本,
    绑定=新绑定(文本)
    };
    dataGrid1.Columns.Add(column);
    }
    
    尽管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;
    }
    

    问题中的数据来自mp3文件中的标签,因此设置确实不一致。事实上,没有编译时知识知道它们将是什么。我可以绕过属性一致性的问题,但不幸的是ExpandooObject对反射是不透明的(尽管我知道这是一个很难解决的问题)。在这种情况下,动态linq可能会有所帮助,但您可能需要一种双通道方法。分析数据一次以查看遇到了哪些标记,然后再分析一次以填充新对象列表。我想问题是,如果任何mp3文件都有一个已定义的属性,那么在将值映射到对象(动态或非动态)之后,所有对象都必须具有该属性。有趣的方法。我可能会做一些类似的事情,但希望避免发射碎片。同时使用Expando和Emission类型似乎是多余的。谢谢你的链接;它给了我一些想法。这很有效,但是你什么时候执行这段代码?在DataContextChangedIn上处理此项时,尚未设置ItemsSource。我的实例ItemSource绑定到名为Results的ViewModel属性。我在视图中有一个inotifyperpertychanged处理程序,它对属性更改做出反应。这是我的想法,但我偶然发现了一个问题。行验证呢?您必须处理ExpandoObjects上的行验证吗?@Ninglin我不需要为我的用例执行行验证这在将
    ItemsSource
    绑定到
    observeCollection
    时似乎对我不起作用这是缺少的一部分:该链接现在已失效;有人能在这里发布完整的答案吗?我在一家专业网站上使用了上面的方案
    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;
    }
    
    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) {
        ...
      }