Brain Spew - Neville Mehta's Blog

Saturday, October 14, 2006

DataGrid / DataGridView DataBinding

I was recently playing around with binding to Windows Forms grids. I have never had to do anything really compilated with them before and have always just assumed that when I do, I will easily be able to figure it out on the spot.

Anyway, I cant remember why, but I wanted to bind a simple string array to a Win Forms DataGridView. I had always assumed that you would simply need to do something like this:

string customerNames = new string[] { "Jill", "Jack", "Johnny", "Jackson", "Jim" };

DataGridView dataGridView = new DataGridView();
dataGridView.DataSource = customerNames;


But, I was wrong! All that appeared in the grid was one column with a header that says "Length" and the rows would only contain the length of the strings, not the values of them....so I thought Ill try a list of strings and see what happens...

List listOfStrings = new List();

Same story...I got a column with the lengths of the string. By now I had caught onto what was happening. All the grid was doing was determining that the DataSource was an IList and so it simply got the objects from the list, reflected over the type and saw that Length was the only property and so displayed a column of length.
So how do you display an array of strings in a grid? The answer is, you cant just set the DataSource to the array like I did above, you have to create an object and then expose a property of type string and use that...something like this:

public class Customer
{
private string _customerName;

public void Customer()
{
}

public void Customer(string NewCustomerName)
{
_customerName = NewCustomerName;
}

public string CustomerName
{
get
{
return customerName;
}
set
{
_customerName = value;
}
}

Customer[] customers = new Customer[] { new Customer("Jack"), new Customer("Jill"), new Customer("Jim") };

DataGridView dataGridView = new DataGridView();
dataGridView.DataSource = customers;

What will happen here is that the grid will look through the array, pull out the Customer objects, reflect over them and see that there is a CustomerName property and display a column of customer names.

Ok, thats fine...but then out of curiosity I pulled out Reflector and decided to have a look at what happens when a DataSet or DataTable is set as the DataSource of the grid...something like this (note that the code below is only to show you something, you should probably be using a stringly typed dataset, and always check for nulls and that there are more than zero tables in the dataset etc.):

DataSet myDataSet = new DataSet();

/**** Populate dataset and do other stuff here ****/

DataGridView dataGridView = new DataGridView();
dataGridView.DataSource = myDataSet;
dataGridView1.DataMember = myDataSet.Tables[0];

Say the first table had two columns on it like "CustomerID" and "CustomerName" then what you would see in the grid are two columns named "CustomerID" and "CustomerName"...wait a second...how did the grid know that? There are no properties on the DataSet, DataTable, DataRow, DataView, DataRowView objects that have those names. So how did the grid just figure out that it had to display those columns when it couldnt figure out how to display my string array?

Well, lets take a look at some of the objects involved. The DataSet and DataTable both implement System.ComponentModel.IListSource. This interface has a property on it called ContainsListCollection and more importantly contains a method called GetList() which returns an IList.

When GetList() is called for the DataSet, it returns a DataViewManager, when GetList() is called for the DataTable, it returns a DataView. Both these classes implement the System.Collections.IList interface. Ok, we are getting closer...The DataView is actually a list of DataRowView object among other things.

Now what happens is, when the grid calls GetList() on the say...DataTable which is set to the DataMember property of the grid, it returns a DataView object. Since this implements IList, the grid knows it can iterate through the list and get a bunch of objects, in this case DataRowView objects.

Now, the DataRowView objects are whats returned, but when the grid reflects over these objects to create columns etc. there are no CustomerID properties or CustomerName properties, so how did it know to create these columns. Well, the DataRowView object implements an interface called System.ComponentModel.ICustomTypeDescriptor which has a GetProperties method. This is very important as the grid looks for this interface before reflecting over the type normally (using Reflection). If the object implements this interface then the grid will call its GetProperties method to obtain its properties and hence the columns. So, the DataRowView is smart enough to return the column names of the underlying DataTable to the grid so it will create those columns in the DataGridView instead of actually returning its properties like Row, RowVersion etc. (which can be returned using normal reflection anyway).

I hope this post gives you a basic insight into the way the DataGrid / DataGridView binds to objects.

Labels: , ,