数据绑定如何与DataTable一起使用?

rym*_*urf 5 .net c# winforms

假设我有一个WinForms DataGridView控件,我将数据绑定到自定义类型的IList,如下所示:

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

private void Form1_Load(object sender, EventArgs e)
{
    var data = new ArrayList();

    data.Add(new Person("Bob", 25));
    data.Add(new Person("Alice", 23));

    this.dataGridView1.DataSource = data;
}
Run Code Online (Sandbox Code Playgroud)

这将显示列表项,列名称为"名称"和"年龄",从列表项的公共属性中巧妙地推断(通过反射).(根据我的测试,第一项.)

但是如果我使用DataTable做同样的事情:

DataTable dt = new DataTable();

dt.Columns.Add("Name");
dt.Columns.Add("Row");

dt.Rows.Add("Bob", 25);
dt.Rows.Add("Alice", 23);

this.dataGridView1.DataSource = dt;
Run Code Online (Sandbox Code Playgroud)

... DataGridView如何知道如何使用DataTable的列而不是其公共属性?DataTable和DataRow似乎都没有实现提供此信息的任何接口.或者DataGridView是否了解DataTable类型,并以不同方式处理这种类型的数据源?

我问的原因是因为我想实现我自己的"动态"数据源类型,它不依赖于固定属性.

rym*_*urf 3

迈克尔为我指明了正确的方向。“神奇”的接口是ICustomTypeDescriptor,它是在 DataRowView 中实现的(而不是在 DataRow 中)。使用这些信息以及这篇博客文章,我创建了这个自定义 PropertyDescriptor 类:

public class MyPropertyDescriptor<TComponent, TValue> : PropertyDescriptor
{
    private readonly Func<TComponent, TValue> getter;

    private readonly Action<TComponent, TValue> setter;

    public MyPropertyDescriptor(string name, Func<TComponent, TValue> getter, Action<TComponent, TValue> setter)
        : base(name, null)
    {
        this.getter = getter;
        this.setter = setter;
    }

    public override bool CanResetValue(object component)
    {
        return true;
    }

    public override object GetValue(object component)
    {
        return getter((TComponent)component);
    }

    public override void ResetValue(object component)
    {
        setter((TComponent)component, default(TValue));
    }

    public override void SetValue(object component, object value)
    {
        setter((TComponent)component, (TValue)value);
    }

    public override bool ShouldSerializeValue(object component)
    {
        return true;
    }

    public override Type ComponentType
    {
        get { return typeof(TComponent); }
    }

    public override bool IsReadOnly
    {
        get { return false; }
    }

    public override Type PropertyType
    {
        get { return typeof(TValue); }
    }
}
Run Code Online (Sandbox Code Playgroud)

然后我修改了 Person 类来实现 ICustomTypeDescriptor:

public class Person : ICustomTypeDescriptor 
{
    public string Name { get; set; }

    public int Age { get; set; }

    /* ... Unimplemented ICustomTypeDescriptor methods left out ... */

    public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
    {
        PropertyDescriptor[] propertyDescriptors = new PropertyDescriptor[]
                                                    {
                                                        new MyPropertyDescriptor<Person, string>("My Name", p => p.Name, (p, s) => p.Name = s), 
                                                        new MyPropertyDescriptor<Person, int>("My Age", p => p.Age, (p, i) => p.Age = i)
                                                    };

        return new PropertyDescriptorCollection(propertyDescriptors);
    }
}
Run Code Online (Sandbox Code Playgroud)

现在 GridView 看起来像这样......

在此输入图像描述

当然,这只是一个测试其是否有效的示例。在生产代码中,类型描述符可能不会映射到公共属性,尽管很高兴看到它们也可以用于自定义列显示名称(在类成员标识符中不允许使用空格和其他字符)。