Click here to Skip to main content
15,860,972 members
Articles / Programming Languages / C#
Article

Nested Property Binding

Rate me:
Please Sign up or sign in to vote.
4.99/5 (41 votes)
12 Sep 2007CPOL7 min read 180.7K   3K   95   41
Extending the BindingSource component to support nested property binding
Screenshot - Order.jpg

Introduction

I'm currently in the process of developing an object relation mapper for use with Microsoft SQL Server. During this process, I've been faced with a number of challenges related to the fact that I will be working with objects rather than datatables. If you've ever tried binding a datagrid to a list of objects, I'm sure you have come across the problem where you wanted to display properties that are not part of the object type itself.

Example: You have a list of orders, but you would like to display the customer's name and billing address along with the properties belonging to the Order type.

This is normally referred to as nested property binding. Many people create special view objects when this becomes a necessity. I wanted a smoother solution and I wanted it design time. The component I've created derives from Bindingsource and is called ObjectBindingSource. As the name implies, the components are designed to be working with objects and not datasets/datatables. I'm not a great fan when it comes to using datasets in databinding scenarios so this has not been a concern of mine.

Below is a class diagram describing our simple domain model used in this example.

Screenshot - DomainModel.jpg

As you can see from the model, the Order class has a Customer and a DeliveryAddress property. This simple order form allows selecting a customer for the order and displaying customer details using nested property binding. The BillingAddress belongs to the customer and should not be editable here, but the DeliveryAddress is specific to each order and should be editable.

The ITypedList Interface

After some research (on Google and CodeProject), I found out that the ITypedList interface was the thing to inspect. This is a really simple interface although not so simple to implement. Basically what it does is provide a method for you to supply what properties belong to a particular type. I'm not going to talk about how to implement this interface as there are numerous articles on the topic here and on the Internet in general. So I started out creating my own special collection component, implementing IBindingList and what not. It soon became clear that what I really wanted was some kind of bindingsource that supported nested property accessors.

Extending the Standard BindingSource Component

In order to provide design time databinding there has to be a BindingSource component present (or at least some component implementing IBindingList). With this in mind I started to look into extending the standard bindingsource and implement the support for nested properties. As it turns out BindingSource already implements the ITypedList interface and its methods are there for you to override. I extended the BindingSource with a property called BindableProperties which is a way for the developer to specify what properties should be exposed from this bindingsource. Then all I needed to do was to replace the default implementation with my own taking the BindableProperties into account. The ITypedList.GetItemProperties seems to be called very often by the consumer, so I moved the creation of propertydescriptors out of the method and created the list of descriptors only when something changed like the datasource or the datamember.
There is also another property called AutoCreateObjects which we will discuss in a minute.

GetItemProperties is the most important method in the ITypedList interface and here it is implemented by the BindingSource component and then overridden in the ObjectBindingSource component to provide custom/nested property descriptors.

C#
public override PropertyDescriptorCollection GetItemProperties
    (PropertyDescriptor[] listAccessors)
{
    //Check to see if the descriptors should be recreated
    if (_createProperties)
        CreatePropertyDescriptors();
    //Check to see if we have a list of descriptors
    if (_propertyDescriptors.Count > 0)
        return new PropertyDescriptorCollection(_propertyDescriptors.ToArray());
    else
        //If not populated for some reason,
        //we just revert to the default implementation
        return base.GetItemProperties(listAccessors);
}

If we take a look at this code snippet from CreatePropertyDescriptors, it might be easier to understand:

C#
foreach (BindableProperty bindableProperty in _bindableProperties)
{
    //Get the original propertydescriptor based on the property path in bindableProperty
    PropertyDescriptor propertyDescriptor =
        ReflectionHelper.GetPropertyDescriptorFromPath(itemType, bindableProperty.Name);
    //Create an attribute array and make room for one more attribute
    Attribute[] attributes = new Attribute[propertyDescriptor.Attributes.Count + 1];
    //Copy the original attributes to the custom descriptor
    propertyDescriptor.Attributes.CopyTo(attributes, 0);
    //Create a new attribute preserving information about the original property.
    attributes[attributes.Length - 1] =
        new CustomPropertyAttribute(itemType, bindableProperty.Name, propertyDescriptor);
    //Finally add the new custom property descriptor to the list of property descriptors
   _propertyDescriptors.Add(new CustomPropertyDescriptor
        (bindableProperty.Name, propertyDescriptor, attributes,_autoCreateObjects));
}

On thing to notice is that a custom property descriptor is created (not shown here) even if the property is a member of the root object. This is because the CustomPropertyDescriptor uses compiled get/set accessors as opposed to regular reflection. We will look at the accessors in a minute.

If you look closely, you will notice that there is also some attribute stuff going on here. This is just a convenient way of preserving information about the real underlying object and property when using nested property binding. By looking at the CustomPropertyAttribute you can easily find the origin of the nested property. If nested property binding is performed using the path Order.Customer.BillingAddress.StreetAddress, the property name will translate Customer_BillingAddress_StreetAddress and it will look as if it is a part of the Order type. In an application Framework I'm working on, we perform localization on the domain model rather than on the UI components and then this information comes in very handy.

Fast Dynamic Property Access using IDynamicAccessor

The origin for the DynamicAccessor comes from an article written by Herbrandson. Based on a wrapper class created by Joel Martinez at CodeCube.Net, I created a similar wrapper that suited my needs for a dynamic property accessor. The general idea is to make use of the DynamicMethodCompiler present in the .Net 2.0 Framework. I'm not going to discuss this in detail, but the bottom line is that we get a compiled setter/getter for the property on the type we are setting or getting the value for. This has proven so useful to me in a number of scenarios that I've included this in my core library. My core library is by the way included in the download. There is a lot of code there not relevant to this article, but it was easier for me just to include it as a whole. Feel free to explore.

Anyway, back to setting and getting the values. There are two methods from the base class(TypeDescriptor) that are essential when it comes to setting and getting the values from and to the underlying object. From what I have read about this topic, .NET actually uses reflection to get and set the property values. Using the IDynamicAccessor provides a dynamic compiled version of the setters and getters by using the DynamicMethodCompiler feature in .NET 2.0. Under those circumstances, this code should perform significantly faster than regular reflection.

If you look at the ObjectBindingSource properties, you will notice that there is another new property call AutoCreateObjects(default false). This property tells the ObjectBindingSource components to automatically create the objects in the propertypath when setting the values. Example: Order.DeliveryAddress.StreetAddress. If the user enters a value for StreetAddress in the Grid ,there is no guarantee that there will be a valid DeliveryAddress on that object. If AutoCreateObjects is set to true, the Address object will be created and assigned to DeliveryAddress before actually setting the value. In order for this to work, the object should have a parameterless constructor.

C#
public override object GetValue(object component)
{
    object instance = GetNestedObjectInstance(component,_propertyPath,false);
    if (instance != null)
        return DynamicAccessorFactory.GetDynamicAccessor
                (instance.GetType()).GetPropertyValue
                (instance, _originalPropertyDescriptor.Name);
    else
        return null;
}

The first thing we need to do is to get a reference to the object instance for which to retrieve the value. This is done by a call to GetNestedObjectInstance providing the root object instance and the property path (example: Customer.Address.StreetAddress) as arguments. The third parameter indicates if the method should autocreate missing objects as explained above. There is no need to do this in the GetValue method.

C#
public override void SetValue(object component, object value)
{
    object instance = 
        GetNestedObjectInstance(component,_propertyPath,_autoCreateObjects);
    if (instance != null)
    {
        DynamicAccessorFactory.GetDynamicAccessor(instance.GetType()).
            SetPropertyValue(instance, _originalPropertyDescriptor.Name, value);
    }
}

Setting the value is done in a similar way, only this time we will take the _autoCreateObjects option into account.

Using the ObjectBindingSource Component

You will be using the ObjectBindingSource component in exactly the same way that you use the standard BindingSource component. The only difference is that you can now edit the BindableProperties collection to specify the properties you need to bind to.

Screenshot - Properties.jpg

Clicking on the BindableProperties property will bring up this collection editor.

Screenshot - Collection.jpg

You should note that if an invalid property path is entered, there is no error indicating this. If an invalid property path is entered, the Bindable properties are ignored and reverted to the BindingSource's default implementation of the ITypedList interface. Provided in the download is a sample application using the new ObjectBindingSource component.

Points of Interest

History

  • 4 Sep 2007: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Norway Norway
I'm a 39 year old software developer living in Norway.
I'm currently working for a software company making software for the retail industry.

Comments and Discussions

 
GeneralGratefull Pin
zimplexness18-Oct-18 5:09
zimplexness18-Oct-18 5:09 
BugUsability Pin
Member 1113244922-Jul-15 4:27
Member 1113244922-Jul-15 4:27 
Questiongreat implementation Pin
Pascalsz8-May-14 5:12
Pascalsz8-May-14 5:12 
QuestionWhy are rows not added to associated DataGridView when unit testing? Pin
ScottHH31-Aug-12 12:32
ScottHH31-Aug-12 12:32 
AnswerRe: Why are rows not added to associated DataGridView when unit testing? Pin
ScottHH1-Sep-12 6:24
ScottHH1-Sep-12 6:24 
GeneralA solution to INotifyPropertyChanged in nested objects Pin
Daniel Prado Velasco29-Dec-10 13:20
Daniel Prado Velasco29-Dec-10 13:20 
GeneralRe: A solution to INotifyPropertyChanged in nested objects Pin
wwwings9-Jan-12 22:24
wwwings9-Jan-12 22:24 
GeneralRe: A solution to INotifyPropertyChanged in nested objects Pin
Daniel Prado Velasco20-Mar-12 3:49
Daniel Prado Velasco20-Mar-12 3:49 
GeneralSorting solution Pin
Fabien P.26-Jul-10 23:29
Fabien P.26-Jul-10 23:29 
GeneralThanks! ... and a question Pin
Paul Brower21-Jan-10 6:44
Paul Brower21-Jan-10 6:44 
GeneralVisual Studio 2008 Pin
kingpin310-Aug-09 21:35
kingpin310-Aug-09 21:35 
GeneralRe: Visual Studio 2008 Pin
kingpin310-Aug-09 23:40
kingpin310-Aug-09 23:40 
GeneralRe: Visual Studio 2008 Pin
Sergei Petrik21-Feb-11 2:21
Sergei Petrik21-Feb-11 2:21 
QuestionMore programmatic problems... Pin
Justin Shands7-Apr-09 21:58
Justin Shands7-Apr-09 21:58 
GeneralINotifyPropertyChanged in nested objects Pin
Someone@AnotherWorld10-Mar-09 7:21
Someone@AnotherWorld10-Mar-09 7:21 
Hi,

nice stuff! But after a couple of time using your solution I realize the ObjectBindingSource ignores the PropertyChanged event of nested objects.

E.g. I've got a class 'Foo' with two properties named 'Name' and 'Bar'. 'Name' is a string an 'Bar' reference an instance of class 'Bar', which has a 'Name' property of type string too and both classes implements INotifyPropertyChanged.

With your binding source reading and writing with both properties ('Name' and 'Bar_Name') works fine but the PropertyChanged event works only for the 'Name' property, because the binding source listen only for events of 'Foo'.

One workaround is to retrigger the PropertyChanged event in the appropriate class (here 'Foo'). What's very unclean! The other approach would be to extend ObjectBindingSource so that all owner of nested property which implements INotifyPropertyChanged get used for receive changes, but how?


Thanks!
GeneralCompile-Error in VS 2008 Pin
Pfotenhauer19-Feb-09 22:21
Pfotenhauer19-Feb-09 22:21 
GeneralNeed help using Pin
Marty Spallone16-Dec-08 9:39
Marty Spallone16-Dec-08 9:39 
GeneralRe: Need help using Pin
seesharper6-Jan-09 10:08
seesharper6-Jan-09 10:08 
GeneralPerformance Can Be Improved With Little Changes Pin
Izzet Kerem Kusmezer22-Sep-08 6:49
Izzet Kerem Kusmezer22-Sep-08 6:49 
GeneralError while binding dropdowns Pin
abyjet20-May-08 3:35
abyjet20-May-08 3:35 
GeneralRe: Error while binding dropdowns Pin
abyjet20-May-08 5:32
abyjet20-May-08 5:32 
QuestionStumped [modified] Pin
Eddy Proft12-Apr-08 16:22
Eddy Proft12-Apr-08 16:22 
QuestionDatabinding and null values Pin
NickB15-Jan-08 10:35
NickB15-Jan-08 10:35 
QuestionFor asp.net? Pin
atmuc10-Nov-07 22:25
atmuc10-Nov-07 22:25 
AnswerRe: For asp.net? Pin
seesharper11-Nov-07 21:41
seesharper11-Nov-07 21:41 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.