How to bind a DataGridView column to a second-level property of a data source


This is a frequently asked question. Suppose that we have a class Person which has a property of type Address(another class). The class Address has its own properties. Now we create a collection of Person objects, e.g. List<Person> and would like to bind a DataGridView to the collection.


 


The following is the code for class Person and Address.


 


class Person


{


         private string id;


         private string name;


         private Address homeAddr;


         public string ID


         {


                   get { return id;}


                   set { id = value;}


         }


         public string Name


         {


                   get { return name;}


                   set { name = value;}


         }


         public Address HomeAddr


         {


                   get { return homeAddr;}


                   set { homeAddr = value;}


         }       


}


 


class Address


{


         private string cityname;


         private string postcode;


         public string CityName


         {


                   get { return cityname;}


                   set { cityname = value;}


         }


         public string PostCode


         {


                   get { return postcode;}


                   set { postcode = value;}


         }


}


 


As we all know, DataGridView columns could only be bound to the first-level properties of class Person, e.g. the ID, Name or HomeAddr properties. The following is the code to bind a DataGridView to a collection of Person objects.


 


List<Person> persons = new List<Person>();


// add some Person objects to the collection



dataGridView1.DataSource = persons;


dataGridView1.Columns[0].DataPropertyName = “ID”;


dataGridView1.Columns[1].DataPropertyName = “Name”;


dataGridView1.Columns[2].DataPropertyName = “HomeAddr”;


 


When a DataGridView column is bound to the HomeAddr property, the name of the class Address will be displayed under the column, i.e. projectname.Address.


 


If we set the DataPropertyName property of a DataGridView column to “HomeAddr.CityName”, an empty text will be displayed under the column, because DataGridView could not find a property called “HomeAddr.CityName” in the class Person.


 


Is there a way to bind a DataGridView column to the CityName or PostCode property of the HomeAddr property? The answer is YES!


 


.NET Framework 1.x can implement ICustomTypeDescriptor interface for the class Person. When a Person object in the collection is going to be displayed in a control, data binding calls ICustomTypeDescriptor.GetProperties method to get the object’s properties. When implementing ICustomTypeDescriptor.GetProperties method, we could create PropertyDescriptor instances for the second-level properties and return them with the original PropertyDescriptor instances of class Person.


 


There’re two problems here. One problem is that if there’s no object in the collection, the ICustomTypeDescriptor.GetProperties method won’t be called so that DataGridView couldn’t ‘see’ secondary-level properties of class Person. The other problem is that it requires modifying the class Person because the class Person needs to implement the ICustomTypeDescriptor interface.


 


To work around the first problem, we could resort to ITypedList interface, i.e. implement the ITypedList interface for the collection. If a data source has implemented the ITypedList interface, data binding calls ITypedList.GetItemProperties to get the properties available for binding. In the ITypedList.GetItemProperties method, we could create an instance of class Person and then call TypeDescriptor.GetProperties(component) method passing the Person instance as the parameter, which in turn calls ICustomTypeDescriptor.GetProperties of the class Person implementation.


 


.NET Framework 2.0 way is to make use of TypeDescriptionProvider and CustomTypeDescriptor classes, which expand support for ICustomTypeDescriptor. It allows you to write a separate class that implements ICustomTypeDescriptor (for convenience, we could derive the class directly from CustomTypeDescriptor which provides a simple default implementation of the ICustomTypeDescriptor interface) and then to register this class as the provider of description for other types.


 


Even if a type is added a TypeDesciptionProvider, data binding won’t call the custom type descriptor’s GetProperties method to get the object’s properties. So we still need to implement ITypedList interface for the collection and in the ITypedList.GetItemProperties method, call TypeDescriptor.GetProperties(type) which in turn calls the custom type descriptor’s GetProperties method.


 


To take advantage of this new functionality, we first need to create a TypeDescriptionProvider, which simply derives from TypeDescriptionProvider and overrides its GetTypeDescriptor method. GetTypeDescriptor returns the ICustomTypeDescriptor implementation or the derived CustomTypeDescriptor that TypeDescriptor should use when querying for property descriptors.


 


When creating providers that are only intended to augment or modify the existing metadata for a type, rather than completely replace it, a recommended approach is to call ‘TypeDescriptor.GetProvider’ method in the new providers’ constructor to get the current provider for the specified type. This base provider can then be used whenever you need to access the underlying type description.


 


As for PropertyDescriptor, it is an abstract class that derives from another abstract class, MemberDescriptor. MemberDescriptor provides the basic information for each property, and PropertyDescriptor adds functionality related to changing a property’s value and determining when that value has changed. To create a PropertyDescriptor on the class Person for each second-level property, we first need to create a custom class that derives from PropertyDescriptor. Pass the original PropertyDescriptor of the second-level property in the new PropertyDescriptor class’s constructor, and then use the original PropertyDescriptor whenever we need to query the information of the second-level property.


 


The following is the code of a custom class that derives from PropertyDescriptor.


 


public class SubPropertyDescriptor : PropertyDescriptor


{


        private PropertyDescriptor _subPD;


        private PropertyDescriptor _parentPD;


 


        public SubPropertyDescriptor(PropertyDescriptor parentPD,PropertyDescriptor subPD,string pdname)


            : base(pdname,null)


        {           


            _subPD = subPD;


            _parentPD = parentPD;


        }


 


        public override bool IsReadOnly { get { return false; } }


        public override void ResetValue(object component) { }


        public override bool CanResetValue(object component) { return false; }


        public override bool ShouldSerializeValue(object component)


        {


            return true;


        }


 


        public override Type ComponentType


        {


            get { return _parentPD.ComponentType; }


        }


        public override Type PropertyType { get { return _subPD.PropertyType; } }


 


        public override object GetValue(object component)


        {


           return _subPD.GetValue(_parentPD.GetValue(component));


        }


 


        public override void SetValue(object component, object value)


        {           


            _subPD.SetValue(_parentPD.GetValue(component), value);


            OnValueChanged(component, EventArgs.Empty);


        }


}


 


The following is the code of a derived CustomTypeDescriptor class.


 


public class MyCustomTypeDescriptor : CustomTypeDescriptor


{      


        public MyCustomTypeDescriptor(ICustomTypeDescriptor parent)


            : base(parent)


        {


        }


 


        public override PropertyDescriptorCollection GetProperties()


        {


            PropertyDescriptorCollection cols = base.GetProperties();


 


            PropertyDescriptor addressPD = cols[“HomeAddr”];


            PropertyDescriptorCollection homeAddr_child = addressPD.GetChildProperties();


          


            PropertyDescriptor[] array = new PropertyDescriptor[cols.Count + 2];


            cols.CopyTo(array, 0);


            array[cols.Count] = new SubPropertyDescriptor(addressPD,homeAddr_child[“CityName”],”HomeAddr_CityName”);


            array[cols.Count + 1] = new SubPropertyDescriptor(addressPD, homeAddr_child[“PostCode”], “HomeAddr_PostCode”);


 


            PropertyDescriptorCollection newcols = new PropertyDescriptorCollection(array);


            return newcols;           


        }    


  }


 


The following is the code of the custom TypeDescriptorProvider.


 


public class MyTypeDescriptionProvider : TypeDescriptionProvider


{


        private ICustomTypeDescriptor td;


 


        public MyTypeDescriptionProvider()


           : this(TypeDescriptor.GetProvider(typeof(Person)))


        {           


        }


        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;


        }       


}


 


At the end, we adorn TypeDescriptionProviderAttribute to the class Person.


 


[TypeDescriptionProvider(typeof(MyTypeDescriptionProvider))]


class Person


{…}


 


Then we could bind a DataGridView column to the second-level properties in the class Person as follows:


 


dataGridView1.Columns[2].DataPropertyName = “HomeAddr_CityName”;


dataGridView1.Columns[3].DataPropertyName = “HomeAddr_PostCode”;


 


 


Linda Liu


Comments (27)

  1. Marc C says:

    Thanks Linda this was what I was looking for.

    I would like to be able to have an class that implements runtime properties. Then attach to a objectDataSource control and hook the objectDataSource to a gridview.

    After making a few changes to the code I was able to get it almost working but there are a few problems.

    1) The dynamic fields do not appear by default in the gridview. They are in the bindable field list but they are not in the selected list.

    2) When I hit the refresh schema button in the designer an error occurs saying that one of the dynamic properties already exists.

    Any help would be welcome.

    Marc

  2. Michael Bursill says:

    I ran into problems running this example, but was able to get it going with a few small changes. I’ll post them here in case anyone else runs into this.

    1) In the GetProperties method change senderprop to addressPD. I’m not even sure what senderprop is?

    2) The wrong overloaded version of GetProperties is overridden. Use this one:

    public override PropertyDescriptorCollection GetProperties(Attribute[] attributes)

    3) Because of #2 you should call GetProperties of the base like so:

    PropertyDescriptorCollection cols = base.GetProperties(attributes);

  3. Michael Bursill says:

    Here’s a question…

    How do I make the now data bound second-level properties editable via the DataGridView?

    If I follow the sample here, when I try to use the DataGridView to change CityName I throw an Invalid Cast because the DataGridView is trying to set the string value CityName to the property HomeAddr.

  4. Michael Bursill says:

    Last time I post here, I swear 😉

    I was able to make the DataGridView support editing of the first and second level properties. It only required two changes in the SubPropertyDescriptor.

    1) when returning the property type I return the sub property type, not the parent:

    public override Type PropertyType { get { return _subPD.PropertyType; } }

    2) When calling SetValue you need to find the value to set from inside the parent like this:

    _subPD.SetValue(_parentPD.GetValue(component), value);

  5. Marc C says:

    Michael thanks for the post.

    I will try your changes.

    Appreciate the work you put in.

  6. Linda says:

    Thanks Michael! I am terribly sorry that I made several mistakes in this artical.

    It should be ‘PropertyDescriptorCollection homeAddr_child = addressPD.GetChildProperties();’ in the override GetProperties method within the MyCustomTypeDescriptor class.

    In addition, what you’ve done to make the data bound second-level properties editable in the DataGridView is correct.

    But to get the DataGridView to bind to a second-level property in a data source, we only need to override the GetProperties method without parameter in the custom type descriptor.

    I will modify my sample code in the article.

  7. Linda says:

    Hi Marc, thank you for your interest in this article!

    It seems that your problem is related to ASP.NET. Right? Sorry that I am not familiar with ASP.NET.

    If you still need help, please explain your problem more in detail.

    Thanks!

  8. Erick says:

    Hi. I’ve tried but this is not working… I think I’m missing something…

    Maybe problems with names spaces of classes?

  9. Tom W says:

    One point of detail – is there a way to associate the newly created TypeDescriptionProvider with the target class without modifying the class file?  that is, do all the stuff mentioned here without having access to the source for the ‘Person’ class?

  10. Dave Beseke says:

    I am attempting to use a combo box in a datagrid view to select a value for a field from another table.  The table bound to the datagridview is a list of doctor’s credentials.  The table bound to the combo box is a contact table.  I want to assign the contact table key value to a field in the doctor table.  I want to use the contact name as the display value.  When I attempt to do this, even though I have the name set as the displaymember and the key as the valuemember, the key value is being displayed instead of the name.  The correct display values are shown in the combobox when I click it, but as soon as I tab out of that field the key is displayed instead of the name.  Here’s the code:

                   doctorGrid.DataSource = doctorTable;

                   doctorTable.Columns["contactID"].DefaultValue = -1;

                   DataGridViewComboBoxColumn cbc = new DataGridViewComboBoxColumn();

                   cbc.DataSource = nameTable;

                   cbc.DataPropertyName = "contactID";

                   cbc.DisplayMember = "contactName";

                   cbc.ValueMember = "contactID";

                   cbc.HeaderText = "Name";

                   cbc.Width = 150;

                   cbc.Name = "docName";

                   cbc.AutoSizeMode = DataGridViewAutoSizeColumnMode.NotSet;

                   doctorGrid.Columns.Insert(2, cbc);

  11. Erick says:

    Hi, this article is great.

    But, what can I do for n-level properties?

    -Product

    -Category

     -Subcategory

  12. caddzooks says:

    Thanks for a great example.

    One comment, is that you mentioned that a downsize to using ICustomTypeDescriptor was that it required modification of the class.

    However, your solution using TypeDescriptionProvider also requires that by way of the TypeDescriptionProvider attribute.

    You can apply your solution to any class, without having to apply the TypeDescriptionProvider attribute, by simply calling TypeDescriptor.AddProvider() to add the TypeDescriptionProvider for the type (or an instance of it). That means you can implement the solution for any class without the need for its source.

  13. Barry says:

    I have a question that might be the same as was asked by Erick.

    I have a multi-level class structure. Something like this:

    ClassA contains an instance of ClassB. ClassA employs a description provider to expose properties of ClassB.

    ClassB contains an instance of ClassC. Now I have implemented ITypedList on ClassA and ClassB to get at ClassC’s properties, but cannot seem to get at them. Do I need to add GetChildProperties calls in ClassA to get the properties in ClassC? Nothing seems to work.

    Any help would be appreciated.

  14. Mabsterama says:

    Here’s the last step in creating our RoleProvider class, which surfaces custom properties for roles you

  15. Cassio Tavares says:

    Hi Linda. I adaptaded your solution to a more generic one using generics. This is what I was looking for. Thank you!!!

  16. CoderDude says:

    The dotnet framework is an excellent tool, with many shining features. However, in my mind this represents a truly fundamental design flaw. The level of complexity that needs to be invoked here to accomplish this simple task is unacceptable.

  17. Harold Valdivia Garcia says:

    Hi. Men and Women. I have an easy solution for this problem.

    If Oneself overrides ToString() of the object Addres,his DataGridview could show the second level.

    public override ToString()

    {

    return cityname+" "postcode;

    }

  18. Guillermo says:

    How about overriding the CellFormatting event of the DataGridView and using reflection with something like the following?:

           private void dataGridView1_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)

           {

               if ((dataGridView1.Rows[e.RowIndex].DataBoundItem != null) &&

                   (dataGridView1.Columns[e.ColumnIndex].DataPropertyName.Contains(".")))

               {

                   string[] nameAndProp = dataGridView1.Columns[e.ColumnIndex].DataPropertyName.Split(new char[]{‘.’});

                   object unboundValue = ((DataRowView)dataGridView1.Rows[e.RowIndex].DataBoundItem)[nameAndProp[0]];

                   PropertyInfo objectProperty = unboundValue.GetType().GetProperty(nameAndProp[1]);

                   e.Value = objectProperty.GetValue(unboundValue, null).ToString();

               }

           }

    This can be extended to make it recursive in case of nested subclasses…

  19. Guillermo says:

    Sorry, I forgot to add that in the column we can use the "dot notation" we’re used to:

    this.dataGridView1.Columns["name"].DataPropertyName = "Class.Property";

  20. Suja Sebastian says:

    I know I am very late. But still, if you see my post, I am lucky.

    I am doing a "web" application and using listview and objectcontainerdatasource for binding. My classes are as follows.

    public class StudentContactCC : PersonCC

    {

    public StudentContactCC()

    {

    }

    //Base Members

    public String ContactID { get; set; }

    public String StudentID { get; set; }

    public String ContactType { get; set; }

    }

    public class Person

    {

    ..

    PhoneNumberCC HomePhone{get;set;}

    }

    public class PhoneNumberCC

    {

    public String PersonID { get; set; }

    public String PhoneID { get; set; }

    public String PhoneType { get; set; }

    public String AreaCode { get; set; }

    public String ExchangeCode { get; set; }

    public String LocalNumber { get; set; }

    public String Extension { get; set; }

    }

    After reading your article I created the 3 classes:

    public class SubPropertyDescriptor : PropertyDescriptor

       {

           private PropertyDescriptor _subPD;

           private PropertyDescriptor _parentPD;

           public SubPropertyDescriptor(PropertyDescriptor parentPD, PropertyDescriptor subPD, string pdname)

               : base(pdname, null)

           {

               _subPD = subPD;

               _parentPD = parentPD;

           }

           public override bool IsReadOnly { get { return false; } }

           public override void ResetValue(object component) { }

           public override bool CanResetValue(object component) { return true; }

           public override bool ShouldSerializeValue(object component)

           {

               return true;

           }

           public override Type ComponentType

           {

               get { return _parentPD.ComponentType; }

           }

           public override Type PropertyType { get { return _subPD.PropertyType; } }

           public override object GetValue(object component)

           {

               return _subPD.GetValue(_parentPD.GetValue(component));

           }

           public override void SetValue(object component, object value)

           {

               _subPD.SetValue(_parentPD.GetValue(component), value);

               OnValueChanged(component, EventArgs.Empty);

           }

    public class StudentContactTypeDescriptionProvider : TypeDescriptionProvider

       {

           private ICustomTypeDescriptor td;

           public StudentContactTypeDescriptionProvider()

               : this(TypeDescriptor.GetProvider(typeof(StudentContactCC )))

           {

           }

           public StudentContactTypeDescriptionProvider(TypeDescriptionProvider parent)

               : base(parent)

           {

           }

           public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance)

           {

               if (td == null)

               {

                   td = base.GetTypeDescriptor(objectType, instance);

                   td = new StudentContactCCTypeDescriptor(td);

               }

               return td;

           }

       }

    public class StudentContactCCTypeDescriptor : CustomTypeDescriptor

       {

           public StudentContactCCTypeDescriptor(ICustomTypeDescriptor parent)

               : base(parent)

           {

           }

           public override PropertyDescriptorCollection GetProperties()

           {

               PropertyDescriptorCollection cols = base.GetProperties();

               PropertyDescriptor homephonePD = cols["HomePhone"];

               PropertyDescriptorCollection homePhone_child = homephonePD.GetChildProperties();

               PropertyDescriptor[] array = new PropertyDescriptor[cols.Count + 3]; //3 for each home,cell,work

               cols.CopyTo(array, 0);

               array[cols.Count] = new SubPropertyDescriptor(homephonePD, homePhone_child["AreaCode"], "HomePhoneAreaCode");

               array[cols.Count + 1] = new SubPropertyDescriptor(homephonePD, homePhone_child["ExchangeCode"], "HomePhoneExchangeCode");

               array[cols.Count + 2] = new SubPropertyDescriptor(homephonePD, homePhone_child["LocalNumber"], "HomePhoneLocalNumber");

               PropertyDescriptorCollection newcols = new PropertyDescriptorCollection(array);

               return newcols;

           }

       }

    Now, I created a list like this :

    [Serializable()]

       public class ContactList<T> : List<T>, ITypedList

       {

           [NonSerialized()]

           private PropertyDescriptorCollection properties;

           public ContactList()

               : base()

           {

               PropertyDescriptorCollection pdc = TypeDescriptor.GetProperties(typeof(T), new Attribute[] { new BrowsableAttribute(true) });

               pdc.Sort();

           }

               #region ITypedList Members

                   public PropertyDescriptorCollection  GetItemProperties(PropertyDescriptor[] listAccessors)

                   {

                        PropertyDescriptorCollection pdc = null;

                            if (null == listAccessors)

                            {

                                pdc = properties;

                            }

                            else

                            {

                                StudentContactTypeDescriptionProvider ContactProvider = new StudentContactTypeDescriptionProvider();

                                StudentContactCC Contact = new StudentContactCC();

                                StudentContactCCTypeDescriptor Desccriptor = new StudentContactCCTypeDescriptor(ContactProvider.GetTypeDescriptor(Contact));

                                pdc = Desccriptor.GetProperties();

                            }

                           return pdc;

                   }

                   public string  GetListName(PropertyDescriptor[] listAccessors)

                   {

                       return typeof(T).Name;

                   }

                   #endregion

       }

    In my code behind, I assign a ContactList to my datasource:

    ContactList <StudentContactCC> Guardians = _controller.GetStudentContactList (_controller.GetStudentID());

    And I bind it to my datasource:

     ocdsGuardians.DataSource = Guardians ;

                   ocdsGuardians.DataBind();

    In the UI, I do binding like this:

    <asp:TextBox ID="txtHomePhoneAreaCode" runat="server" Width="2em" MaxLength="3"

                         Text='<%# Bind("HomePhoneAreaCode") %>’></asp:TextBox>

    When I go to the edit mode of my listview, areacode get populated.But when I try to update, I am not getting the user entered value.

    Please keep in mind that I am doing web development.

  21. Soben says:

    Thank for the useful technique, but I figure out one problem while trying to put more than one.

    example:

    [TypeDescriptionProvider(typeof(MyTypeDescriptionProvider))]

    [TypeDescriptionProvider(typeof(MyOtherTypeDescriptionProvider))]

    class Person{}

    It shows the error message that duplicate TypeDescriptionProvider, Could you please pointing out how to solve this.

  22. Bernardo says:

    The code below implements a DeepBindingList<T> class that exposes nested properties (to any level). It also supports sorting. I hope it's useful to some of you:

    using System;

    using System.ComponentModel;

    using System.Collections.Generic;

    using System.Text;

    namespace DeepBindingList

    {

       /// <summary>

       /// Extends the BindingList<T> to provide sorting and deep property

       /// binding (e.g. Address.Street).

       /// </summary>

       public class DeepBindingList<T> : BindingList<T>, ITypedList

       {

           //———————————————————————–

           #region ** IBindingList overrides (to provide sorting)

           PropertyDescriptor _sort;

           ListSortDirection _direction;

           protected override bool IsSortedCore

           {

               get { return _sort != null; }

           }

           protected override void RemoveSortCore()

           {

               _sort = null;

           }

           protected override ListSortDirection SortDirectionCore

           {

               get { return _direction; }

           }

           protected override PropertyDescriptor SortPropertyCore

           {

               get { return _sort; }

           }

           protected override bool SupportsSortingCore

           {

               get { return true; }

           }

           protected override void ApplySortCore(PropertyDescriptor pd, ListSortDirection direction)

           {

               // get list to sort

               var items = this.Items as List<T>;

               // apply the sort

               if (items != null)

               {

                   var pc = new PropertyComparer<T>(pd, direction);

                   items.Sort(pc);

               }

               // save new settings and notify listeners

               _sort = pd;

               _direction = direction;

               this.OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));

           }

           // PropertyComparer (used to sort the list)

           class PropertyComparer<TC> : IComparer<TC>

           {

               PropertyDescriptor _pd;

               ListSortDirection _direction;

               public PropertyComparer(PropertyDescriptor pd, ListSortDirection direction)

               {

                   _pd = pd;

                   _direction = direction;

               }

               public int Compare(TC x, TC y)

               {

                   try

                   {

                       var v1 = _pd.GetValue(x) as IComparable;

                       var v2 = _pd.GetValue(y) as IComparable;

                       int cmp =

                           v1 == null && v2 == null ? 0 :

                           v1 == null ? +1 :

                           v2 == null ? -1 :

                           v1.CompareTo(v2);

                       return _direction == ListSortDirection.Ascending ? +cmp : -cmp;

                   }

                   catch

                   {

                       return 0; // comparison failed…

                   }

               }

           }

           #endregion

           //———————————————————————–

           #region ** ITypedList (to expose inner properties)

           public PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors)

           {

               var list = new List<PropertyDescriptor>();

               foreach (PropertyDescriptor pd in TypeDescriptor.GetProperties(typeof(T)))

               {

                   AddProperties(pd, null, list);

               }

               return new PropertyDescriptorCollection(list.ToArray());

           }

           void AddProperties(PropertyDescriptor pd, PropertyDescriptor parent, List<PropertyDescriptor> list)

           {

               // add this property

               pd = new DeepPropertyDescriptor(pd, parent);

               list.Add(pd);

               // and subproperties for non-value types

               if (!pd.PropertyType.IsValueType && pd.PropertyType != typeof(string))

               {

                   foreach (PropertyDescriptor sub in TypeDescriptor.GetProperties(pd.PropertyType))

                   {

                       AddProperties(sub, pd, list);

                   }

               }

           }

           public string GetListName(PropertyDescriptor[] listAccessors)

           {

               return null;

           }

           // property descriptor with support for sub properties (e.g. Address.Street)

           class DeepPropertyDescriptor : PropertyDescriptor

           {

               PropertyDescriptor _pd;

               PropertyDescriptor _parentPD;

               public DeepPropertyDescriptor(PropertyDescriptor pd, PropertyDescriptor parentPd)

                   : base(pd.Name, null)

               {

                   _pd = pd;

                   _parentPD = parentPd;

               }

               public override string Name

               {

                   get

                   {

                       return _parentPD != null

                           ? string.Format("{0}.{1}", _parentPD.Name, _pd.Name)

                           : _pd.Name;

                   }

               }

               public override bool IsReadOnly

               {

                   get { return _pd.IsReadOnly; }

               }

               public override void ResetValue(object component)

               {

                   _pd.ResetValue(component);

               }

               public override bool CanResetValue(object component)

               {

                   return _pd.CanResetValue(component);

               }

               public override bool ShouldSerializeValue(object component)

               {

                   return _pd.ShouldSerializeValue(component);

               }

               public override Type ComponentType

               {

                   get { return _pd.ComponentType; }

               }

               public override Type PropertyType

               {

                   get { return _pd.PropertyType; }

               }

               public override object GetValue(object component)

               {

                   if (_parentPD != null)

                   {

                       component = _parentPD.GetValue(component);

                   }

                   return _pd.GetValue(component);

               }

               public override void SetValue(object component, object value)

               {

                   _pd.SetValue(_parentPD.GetValue(component), value);

                   OnValueChanged(component, EventArgs.Empty);

               }

           }

           #endregion

       }

    }

  23. Deeps says:

    Overriding the CellFormatting event described by Guillermo works like a charm!

  24. ahole *** says:

    hey Linda Liu where is some source code for download you stupid *** ***.

    As usual article is crapola and author is some foreign half burnt ***

  25. Dan says:

    FYI, binding to nested public properties of basic types, i.e., "HomeAddr.CityName" works in .NET 4.0.

  26. M23 says:

    @Dan

    That doesn't work for me. Using the same Person and Address class it just show blank cells.

  27. Thomas says:

    Hi,

    Thx Linda

    It's work for me.

    But I'm using Entities & WPF. It work on the server side but not on the client side.

    I tried to serialize the 3 classes but no success.