Binding to Nested Properties
My workmate Vijay is playing with WCF with a small project, and was asking me how to display a "nested" property of his business object in a DataGridView. I'll walk through an example here, changing the class names to protect the innocent.
Let's say you have a collection of characters and the movies they appeared in. Let's say the classes look like this:
class Studio { public String Name { /* ... */ } /* ... */ } class Movie { public String Name { /* ... */ } public Studio Studio { /* ... */ } /* ... */ } class Character { public String Name { /* ... */ } public Movie Movie { /* ... */ } }
You want to display them in a grid, like this:
Character | Movie | Studio |
---|---|---|
Chief Brody | Jaws | Universal |
Han Solo | Star Wars | 20th Century Fox |
So you've bound your DataGridView to your list of characters (maybe it's a BindingList<Character> collection) and added some columns. The first column's easy - it's just bound to the "Name" property of the "Character" class. What about the next column? Bind it to the "Movie" property and run your app and you'll see something like this:
Character | Movie |
---|---|
Chief Brody | WindowsApplication1.Movie |
Han Solo | WindowsApplication1.Movie |
That doesn't look right. What has happened? The DataGridView only knows that the second column is bound to a property of type "Movie", and it has no idea how to represent a "Movie" object on screen. You need to tell it that you would like to see the name of the movie. An easy way to do this is to handle the CellFormatting event of the grid, like this:
private void dataGridView1_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e) { if (e.Value is Movie) { e.Value = (e.Value as Movie).Name; } }
See what's happening there? We're looking for a cell whose value is of type "Movie", and we're replacing its content with the name of the movie in question. Rerun, and our grid looks better:
Character | Movie |
---|---|
Chief Brody | Jaws |
Han Solo | Star Wars |
So now we need to get the "Studio" column in there. We add a new column, and bind to ... what? "Movie.Studio"? That doesn't work. How the heck can we show the studio in the grid?
If we had control over the "Character" class, the easiest solution would be to add a "Studio" property to the class itself. However, in this scenario we're talking about objects returned from a service, and it might be one we didn't write. We can't just go changing the definition of "Character"! Or ... can we?
The beauty of the classes generated by Visual Studio when you add a "web reference" or "service reference" is that they're partial classes. You have the ability to extend them by defining the "other half" of the partial class. This is exactly the way Windows Forms work in .NET - the "designer" part of the form (the generated code that creates and places controls) is one half of a partial class, and your code is the other half.
So we can add to the structure of a class that's returned from a service. Let's add a "Studio" property to the "Character" class! First we'll add a new file to our project, and call it Character.cs. Next, we have to make sure we're defining this partial class in the correct namespace - otherwise the compiler will have no idea that it's the "other half" of an existing code. For this example I'll assume that the "Character" class was generated in a service reference called CharServiceRef.
namespace WindowsApplication1.CharServiceRef { public partial class Character { public Studio Studio { get { return this.Movie.Studio; } } } }
This is a very simple read-only property that returns the studio of the character's movie. Now we can add a column to our grid that is bound to the "Studio" property, and adjust our CellFormatting event handler thusly:
private void dataGridView1_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e) { if (e.Value is Movie) { e.Value = (e.Value as Movie).Name; } else if (e.Value is Studio) { e.Value = (e.Value as Studio).Name; } }
Et voila! Now your grid can display the nested property ("Studio") and looks just like our original example:
Character | Movie | Studio |
---|---|---|
Chief Brody | Jaws | Universal |
Han Solo | Star Wars | 20th Century Fox |
The ability to extend types that you didn't define through the use of partial classes is an invaluable piece of the "service oriented" puzzle. This is just one small use for this feature. There are plenty of others! Go forth and extend!
# Trackback from Databinding nested properties « maonet technotes on 4/12/2007 6:36 AM
# Trackback from Binding to Nested Properties « Devmanic – Software Development Mania on 17/11/2009 6:52 AM
Comments
# Philippe Dykmans
1/07/2008 9:33 AM
Sounds fine. We're displaying a nested property in DataGridView now. But what if we want to edit this nested property? Ok, i suppose there's also some lousy way to do that. But the bottomline is that DataGridView is just too dumb or too lazy to navigate something as easy as a dotted-notation property path. I've been breaking my head for hours getting DGV to work with a nested property. Thinking that if DGV couldn't do it, no component could. Until i tried it with a free component (DataListView from CodeProject) and it worked immediately. Found out later that almost ALL components EXCEPT DGV can do that. Go figure :-)
# mabster
1/07/2008 10:02 AM
Hi Philippe,
Yeah, editing becomes a problem. I typically don't allow in-place editing in my grids so it hasn't bothered me too much to date.
WPF's powerful databinding solves this problem of course, but in Windows Forms you basically have to go with a third-party component I guess.
# Madina
22/10/2013 1:25 AM
Thanks a lot! I finally made it work for a CheckBoxList in the DataRepeater thanks to your example.