Wednesday, March 30, 2011

Why should I use ObjectDataProvider?

There are many ways to instantiate an object that will be used as the data source for bindings. I have seen many people create the object in code and set the DataContext of the Window to that instance, which is a good way to do this. You may have noticed that I have been adding the source object to the Window’s Resource Dictionary in most of my previous posts, which works well too. We have an ObjectDataProvider class in data binding that can also be used to instantiate your source object in XAML. I will explain in this post the differences between adding the source object directly to the resources and using ObjectDataProvider. This will hopefully give you guidance on how to evaluate your scenario and decide on the best solution.

As I describe these features, I will walk you through building a little application that allows people to type their weight on Earth and calculates their weight on Jupiter.
When adding the source object directly to the resources, the Avalon data binding engine calls the default constructor for that type. The instance is then added to a dictionary of resources, using the key specified by x:Key. Here is an example of the markup for this solution:

    <Window.Resources>
        <local:MySource x:Key="source" />
        (…)
    </Window.Resources>

As an alternative, you can add an ObjectDataProvider to the resources and use that as the source of your bindings. ObjectDataProvider is a class that wraps your source object to provide some extra functionality. I will discuss here the following distinguishing capabilities of ObjectDataProvider:

Passing parameters to the constructor
Binding to a method (which may take parameters)
Replacing the source object
Creating the source object asynchronously

Passing parameters to the constructor
When adding the source object to the resources directly, Avalon always calls the default constructor for that type. It may happen that you have no control over the source data, and the class you’re given has no default constructor. In that case, you can still create an instance in XAML by using ObjectDataProvider in the following way:

    <ObjectDataProvider ObjectType="{x:Type local:MySource}" x:Key="odp1">
        <ObjectDataProvider.ConstructorParameters>
            <system:String>Jupiter</system:String>
        </ObjectDataProvider.ConstructorParameters>
    </ObjectDataProvider>

This markup creates a new instance of MySource by calling its constructor and passing the string “Jupiter”. It also creates an instance of ObjectDataProvider, which will wrap the MySource object.
MySource has a public property called “Planet” that exposes an actual Planet object – the one whose name matches the string passed in the constructor parameter (in this case, “Jupiter”). I want to have a title Label in my application that binds to the Planet’s Name property. Binding to subproperties can be done in Avalon by using “dot notation”. The syntax for this is Path=Property.SubProperty, as you can see in the following markup:

    <Label Content="{Binding Source={StaticResource odp1}, Path=Planet.Name}" Grid.ColumnSpan="2" HorizontalAlignment="Center" FontWeight="Bold" Foreground="IndianRed" FontSize="13" Margin="5,5,5,15"/>

You may be looking at this binding statement and thinking that it makes no sense. It seems like we are binding to the Name subproperty of the Planet property of ObjectDataProvider. But I just mentioned that MySource is the one that has a Planet property, not ObjectDataProvider. The reason this markup works is that the binding engine treats ObjectDataProvider specially, by applying the Path to the source object that is being wrapped. Note that this special treatment is also used when binding to XmlDataProvider and CollectionViewSource.

Binding to a method
There is a method in MySource that takes as a parameter a person’s weight on Earth and calculates that person’s weight on the planet passed to the constructor. I want to pass some weight to this method in XAML and bind to its result. This can also be done with ObjectDataProvider:

    <ObjectDataProvider ObjectInstance="{StaticResource odp1}" MethodName="WeightOnPlanet" x:Key="odp2">
        <ObjectDataProvider.MethodParameters>
            <system:Double>95</system:Double>
        </ObjectDataProvider.MethodParameters>
    </ObjectDataProvider>

Read more: Bea Stollnitz