Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does data binding work with DataTable?

Tags:

c#

.net

winforms

Suppose I have a WinForms DataGridView control and I data bind it to an IList of a custom type, like this:

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;
}

This displays the list items with column names "Name" and "Age" neatly inferred (by reflection) from the public properties of the list items. (The first item according to my tests.)

But if I do the same using a 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;

...how does the DataGridView know how to use the DataTable's columns instead of its public properties? Neither DataTable nor DataRow seem to implement any interface supplying this information. Or does DataGridView know about the DataTable type, and treats this type of data source differently?

The reason I'm asking is because I would like to implement my own "dynamic" data source type that does not rely on fixed properties.

like image 483
rymdsmurf Avatar asked Dec 10 '13 16:12

rymdsmurf


2 Answers

Michael pointed me in the right direction. The "magic" interface is ICustomTypeDescriptor, and it's implemented in DataRowView (not in DataRow). Using this information and also this blog post I created this custom PropertyDescriptor class:

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); }
    }
}

I then modified my Person class to implement 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);
    }
}

And now the GridView looks like this...

enter image description here

Of course, this is just a sample to test that it works. In production code the type descriptors would probably not map to public properties, although it's nice to see that they can also be used to customize column display names (with spaces and other characters not allowed in a class member identifier).

like image 136
rymdsmurf Avatar answered Oct 13 '22 00:10

rymdsmurf


The DataSource method of the DataGridView supports the IListSource interface, such as the DataTable and DataSet.

This MSDN article has more info if needed.

like image 34
Jmoreland91 Avatar answered Oct 13 '22 00:10

Jmoreland91