.NET Forum / Windows Forms / WinForm Controls / December 2006
DataGridView bound to complex object?
|
|
Thread rating:  |
Bradley Plett - 30 Nov 2006 21:33 GMT I have a class as follows (this is over-simplified, but representative): class cMessage { Guid UniqueID; string Subject; cActor Sender; cActor Receiver; }
Using a "List" (I love generics!) of this class type works fine as the datasource for a DataGridView. However, as you'll notice, the sender and receiver are themselves instances of: class cActor { Guid UniqueID; string FirstName; string LastName; } I would like to display Sender.FirstName and Sender.LastName as columns in the grid. Simply setting the "DataPropertyName" to, for example, Sender.FirstName compiles, but doesn't show the data. I would think this should be simple, but I have not yet succeeded in doing so. Any ideas?
Thanks! Brad.
Linda Liu [MSFT] - 01 Dec 2006 13:11 GMT Hi Brad,
Thank you for posting here.
This is a quick note to let you know that I am researching on this issue and will get it back to you ASAP.
I appreciate your patience.
Sincerely, Linda Liu Microsoft Online Community Support
================================================== Get notification to my posts through email? Please refer to http://msdn.microsoft.com/subscriptions/managednewsgroups/default.aspx#notif ications. Note: The MSDN Managed Newsgroup support offering is for non-urgent issues where an initial response from the community or a Microsoft Support Engineer within 1 business day is acceptable. Please note that each follow up response may take approximately 2 business days as the support professional working with you may need further investigation to reach the most efficient resolution. The offering is not appropriate for situations that require urgent, real-time or phone-based interactions or complex project analysis and dump analysis issues. Issues of this nature are best handled working with a dedicated Microsoft Support Engineer by contacting Microsoft Customer Support Services (CSS) at http://msdn.microsoft.com/subscriptions/support/default.aspx. ================================================== This posting is provided "AS IS" with no warranties, and confers no rights.
Bradley Plett - 05 Dec 2006 07:22 GMT Any luck?
Brad.
>Hi Brad, > [quoted text clipped - 28 lines] > >This posting is provided "AS IS" with no warranties, and confers no rights. Linda Liu [MSFT] - 05 Dec 2006 12:23 GMT Hi Brad,
Sorry for my delayed reply. I have spent much time researching on this problem, but unfortunately with no luck.
When a DataGridView control is bound to a data source, e.g. a list which contains business objects, DataGridView can only find the first level public properties in the business class. If we bind a column of the DataGridView to a sub property of a property (if this property has its own properties) in the business class, DataGridView couldn't find a public property in the business class with such a name and will return empty strings for the column.
To achieve the ability of binding a column of a DataGridView to a sub property of a property in the business class, I think we have to 'add some extra properties' in the business class, e.g. add properties named 'Sender_FirstName', 'Sender_LastName, etc. in the cMessage class in your scenario.
I thought a possible way to do this is to implement a custom type descriptor for the cMessage class. The TypeDescriptor architecture is built on the core reflection engine and adds additional rules and features. It enhances the capabilities of .NET reflection, for example, it enables an object's metadata to be modified. Thus, we could modify the properties of the cMessage class using TypeDescriptor.
For more information about TypeDescriptor, TypeDescriptionProvider and CustomTypeDescriptor, you may visit the following links.
"Type Descriptor Overview" http://msdn2.microsoft.com/en-us/library/ms171819.aspx
"TypeDescriptor Class " http://msdn2.microsoft.com/en-us/library/system.componentmodel.typedescripto r.aspx
"TypeDescriptionProvider Class" http://msdn2.microsoft.com/en-us/library/system.componentmodel.typedescripti onprovider.aspx
"CustomTypeDescriptor Class" http://msdn2.microsoft.com/en-us/library/system.componentmodel.customtypedes criptor.aspx
You may also visit the following links to get samples of using CustomTypeDescriptor.
".NET Matters ICustomTypeDescriptor, Part 1" http://msdn.microsoft.com/msdnmag/issues/05/04/NETMatters/default.aspx
".NET Matters ICustomTypeDescriptor, Part 2" http://msdn.microsoft.com/msdnmag/issues/05/05/NETMatters/default.aspx
I have performed a test only to find that DataGridView doesn't reply on the custom type descriptor to get properties.
To following is the main code in my test project.
public class MyCustomTypeDescriptor : CustomTypeDescriptor { public MyCustomTypeDescriptor(ICustomTypeDescriptor parent) : base(parent) { }
public override PropertyDescriptorCollection GetProperties() { PropertyDescriptorCollection cols = base.GetProperties(); PropertyDescriptor[] array = new PropertyDescriptor[cols.Count + 1]; cols.CopyTo(array, 0); // add a new PropertyDecriptor to the collection // array[cols.Count] = newprop; PropertyDescriptorCollection newcols = new PropertyDescriptorCollection(array); return newcols; } }
public class MyTypeDescriptionProvider : TypeDescriptionProvider { private ICustomTypeDescriptor td; public MyTypeDescriptionProvider() : this(TypeDescriptor.GetProvider(typeof(cMessage))) { } public MyTypeDescriptionProvider(TypeDescriptionProvider parent) : base(parent) { } public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance) { if (td == null) { td = base.GetTypeDescriptor(objectType, instance); td = new MyCustomTypeDescriptor(td); } return td; } }
[TypeDescriptionProvider(typeof(MyTypeDescriptionProvider))] class cMessage { private int uniqueID; private string subject; private cActor sender; private cActor receiver;
public int UniqueID { get { return uniqueID; } set { uniqueID = value; } } public string Subject { get { return subject; } set { subject = value; } } public cActor Sender { get { return sender; } set { sender = value; } } public cActor Receiver { get { return receiver; } set { receiver = value; } } }
class cActor { private int uniqueID; private string firstName; private string lastName;
public int UniqueID { get { return uniqueID; } set { uniqueID = value; } } public string FirstName { get { return firstName; } set { firstName = value; } } public string LastName { get { return lastName; } set { lastName = value; } } } Unfortunately, when the program is run and the DataGridView is populated with data from the list, the GetProperties method in the MyCustomTypeDescriptor class is not called at all. So this solution is not fit for data binding in DataGridView.
Alternatively, you may add some public readonly properties such as 'Sender_FirstName', 'Sender_LastName' and etc. to the cMessage class to ensure DataGridView could find them. The following is a sample. class cMessage { ..... public string Sender_FirstName { get {return sender.FirstName;} } ..... }
Hope this helps. If you have any concerns, please feel free to let me know.
Sincerely, Linda Liu Microsoft Online Community Support
Bradley Plett - 07 Dec 2006 19:14 GMT Being relatively new to the DataGridView, I'm wondering: is it possible to add a column that will evaluate a function on the data in the row? If so, how?
What I had in mind is something like "=GetFirstName([Sender])", where "GetFirstName" simply returned "Sender.FirstName".
Thanks! Brad.
>Hi Brad, > [quoted text clipped - 176 lines] >Linda Liu >Microsoft Online Community Support Linda Liu [MSFT] - 08 Dec 2006 08:32 GMT Hi Brad,
I am sorry to say that it's impossible to make a column in a DataGridView evaluate a function on the data.
When we bind a DataGridView control to a data source, we could only set the DataPropertyName property of the columns in the data grid view to the public properties on the data.
I still suggest that you add readonly pubilc properties for the sub properties of Sender and Receiver properties in the cMessage class, instead of functions. After you do this, set the DataPropertyName property of the columns in the data grid view to the readonly pubilc properties.
Hope this helps. If you have any concerns, please feel free to let me know.
Sincerely, Linda Liu Microsoft Online Community Support
Bradley Plett - 08 Dec 2006 16:43 GMT That's actually what I've done, but it introduces another problem in my case. The object in question comes from a web service. In order to introduce these new read-only properties, I need to inherit from the class as provided by the WSDL. However, when I do, then I can't easily cast from the old object to the new one (or perhaps I just don't know how).
Brad.
>Hi Brad, > [quoted text clipped - 16 lines] >Linda Liu >Microsoft Online Community Support Bradley Plett - 08 Dec 2006 16:46 GMT P.S. As I learn more about it, I can't help but be a little surprised at the lack of functionality of the DataGridView. Not being able to map a column to a function seems very restricting to me.
Brad.
>That's actually what I've done, but it introduces another problem in >my case. The object in question comes from a web service. In order [quoted text clipped - 25 lines] >>Linda Liu >>Microsoft Online Community Support Linda Liu [MSFT] - 11 Dec 2006 09:04 GMT Hi Brad,
Thank you for your reply.
I understand your feelings. Unfortunately, DataGridView doesn't support binding to a function at present.
Since the cMessage objects come from a web service, it is not convenient to either modify the cMessage class or derive from this class.
In fact, we have another way to display the sub properties of the Sender and Receiver properties in the cMessage object. That is to paint the values of the sub properties into the DataGridView cells by ourselves. To do this, handle the CellPainting event of the DataGridView. In order to update the underlying data source when the values in the cells are update, we could handle the CellValidated event of the DataGridView to do it.
The following is a sample.
public Form1() { this.dataGridView1.CellPainting += new DataGridViewCellPaintingEventHandler(dataGridView1_CellPainting); this.dataGridView1.CellValidated += new DataGridViewCellEventHandler(dataGridView1_CellValidated); }
void dataGridView1_CellValidated(object sender, DataGridViewCellEventArgs e) { if (e.RowIndex >= 0 && e.ColumnIndex >= 0) { cMessage obj = this.dataGridView1.Rows[e.RowIndex].DataBoundItem as cMessage; if (obj != null) { // update the underlying data if (this.dataGridView1.Columns[e.ColumnIndex].Name == "Column3") { obj.Sender.FirstName =this.dataGridView1.Rows[e.RowIndex].Cells[e.ColumnIndex].Value.ToString() ; } else if (this.dataGridView1.Columns[e.ColumnIndex].Name == "Column4") { obj.Sender.LastName=this.dataGridView1.Rows[e.RowIndex].Cells[e.ColumnIndex] .Value.ToString(); } } } }
void dataGridView1_CellPainting(object sender, DataGridViewCellPaintingEventArgs e) { if (e.RowIndex >= 0 && e.ColumnIndex >= 0) { cMessage obj = this.dataGridView1.Rows[e.RowIndex].DataBoundItem as cMessage; if (obj != null) { // show the value of Sender.FirstName property in the Column3 if (this.dataGridView1.Columns[e.ColumnIndex].Name == "Column3") { this.dataGridView1.Rows[e.RowIndex].Cells[e.ColumnIndex].Value = obj.Sender.FirstName; } // show the value of Sender.LastName property in the Column4 else if (this.dataGridView1.Columns[e.ColumnIndex].Name == "Column4") { this.dataGridView1.Rows[e.RowIndex].Cells[e.ColumnIndex].Value = obj.Sender.LastName; } } } }
Hope this helps. If you have anything unclear, please feel free to let me know.
Sincerely, Linda Liu Microsoft Online Community Support
Linda Liu [MSFT] - 14 Dec 2006 02:51 GMT Hi Brad,
How about the problem now?
If the problem is not resolved or you have anything unclear, please feel free to let me know.
Thank you for using our MSDN Managed Newsgroup Support Service!
Sincerely, Linda Liu Microsoft Online Community Support
Free MagazinesGet these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...
|
|
|