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.
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...
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).
The DataSource method of the DataGridView supports the IListSource interface, such as the DataTable and DataSet.
This MSDN article has more info if needed.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With