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.
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
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: Data Binding, DataGrid, DataGridView
13 Comments:
thanks for posting this info. i have nested databindings and i could not figure out why it was not displaying my list of strings properly. now i know. personally i think it's ugly that i have to create fictitious object to get around this problem. but at least there is a way around it.
By Anonymous, at 3:17 am
Thanks you thank you thank you!
By Anonymous, at 6:33 pm
Very informative.
How bout some insight on Reflection and its practical use? You seem to have a very good knack of explaining thins.
By Anonymous, at 8:12 am
Hi
I have a datagridview bound to a Datatable through bindingsource component. The Data of One particular column of the datatable is updated at random. But in the datagridview, even though the updates are not comming for that particular column, when the user is entering data in that column, looses the characters typed before hitting enter or the cell validation event is called.
If you have faced a similar problem, kindly help me out with this ..
Cheers,
Harish
By Oriental Explorer, at 7:02 pm
In a gridview (asp.net) you can use Container.DataItem for an array of strings. I used this for a rolodex type application.
By Anonymous, at 12:07 pm
Why not just use a foreach loop on your array, collection or whatever and create the rows on your own.
By Anonymous, at 3:16 am
Thanks man, it saved me a lot of time. Great work!
By Anonymous, at 7:33 am
Wow--so cool--I've been doing mostly ASP.NET development for 4 years--never had much need for Windows development, but always thought a grid is a grid is a grid--guess again!
Thanks for the tip!
By Anonymous, at 6:54 am
A Good way to explain.. Thx :)
By Arya, at 9:49 pm
Recently I encountered the same problem. My solution was:
dataGridView.DataSource = stringArray.Select(s => new { showme = s }).ToArray();
Using LINQ it didn't need to create any class.
By John Lin, at 4:18 am
Who knows where to download XRumer 5.0 Palladium?
Help, please. All recommend this program to effectively advertise on the Internet, this is the best program!
By Anonymous, at 4:14 pm
thanks alot that was very helpful
By Anonymous, at 9:26 pm
Wonderful explanation, and thanks for sharing. It helped me fix a DataGridView/Arraylist binding issue I had been trying to resolve for more than an hour.
By Anonymous, at 1:20 pm
Post a Comment
<< Home