Simplify Silverlight Data Binding in Nested Controls
Access a parent's DataContext when controls are nested inside of other controls
January 3, 2010
Data binding is one of the more powerful features available in Silverlight that allows developers to work with data and controls in a more natural way. It's quite straightforward to bind data to different controls and move data from controls back into the original bound object, without having to write any C# or VB code in many cases. (See my article, Data-Centric Coding in Silverlight Applications.) Here’s an example of binding a ComboBox control to an object that defines a property named Branches:
ItemsSource="\{Binding Branches\}"
DisplayMemberPath="Title"/>
This code binds the ItemsSource property to the DataContext object’s Branches property and displays the Branch object’s Title property in the ComboBox. The DataContext could be assigned directly to the ComboBox through code or could be assigned to a parent object. If it’s defined on a parent, then all children have access to the object's data, which is extremely useful when binding an object to multiple controls.
This type of binding works great as long as the ComboBox has access to the Branches property, as it does in Figure 1. Note that the page shown in Figure 1 is a Silverlight Page class or User Control as opposed to a web page. In this case, an object containing the data (called the ViewModel when following the Model-View ViewModel pattern) is bound to the page. The DataContext from the page is then available in the child ComboBox.
Figure 2 demonstrates a problem that can occur as the ComboBox control’s DataContext is changed to something other than the ViewModel object that defines the Branches property. In this example, a DataGrid binds to the Jobs property of the ViewModel and each row’s DataContext is a Job type. The Job type doesn’t have a Branches property (the ViewModel object does) so the ComboBox no longer has access to it for data binding. While code can be written to handle this scenario, what if you'd like to handle all of the binding declaratively in XAML? Figure 3 shows the code to handle this task.
Using a Static Resource When Binding Nested Controls
While the ComboBox can’t bind to the Branches property using the standard \{Binding Branches\} syntax when it’s nested in something like a DataGrid (or ListBox or another item's control), it can still get to the ViewModel object’s Branches property with a few minor changes. The first way that this problem can be solved is to write code that assigns the ViewModel object’s Branches collection to each ComboBox in the LoadingRow event of the DataGrid. While this works, the code would be required each time a ComboBox or other control is nested in an item's control such as the DataGrid. The second way is to create a mini ViewModel class that binds to each row that references the Branches collection. That’s also an option, but there’s a “codeless” way to do it as well that’s quite easy. If the ViewModel object is defined in the Resources section of the Page or UserControl, you can get to it using its key name directly in XAML. Figure 4 shows an example of defining a ViewModel object declaratively in XAML.
A ComboBox control within a DataGrid row (or nested in another type of control) can get to the ViewModel object’s Branches property using the following binding syntax:
ItemsSource="\{Binding Source=\{StaticResource
ViewModel\}, Path=Branches\}"
DisplayMemberPath="Title"/>
This XAML code tells the ComboBox control to access a statically-defined resource with a key of “ViewModel,” and then find a property of that resource object named Branches. This is a nice approach, because you don’t have to write any C# or VB code at all by using this solution, which is great if you’re dealing with a lot of nested ComboBox controls. For developers that don’t like defining their ViewModel objects in XAML, you can always use a lookup object to locate the ViewModel that should be used when things need to be kept as loosely coupled as possible.
Now that you see how nested objects can get to properties defined at higher levels, we can discuss the main focus of this article. Everything mentioned to this point works great out of the box, especially if you want to avoid writing code as much as possible. However, there’s still a problem when the StaticResource binding trick shown earlier won’t work, as Figure 4 shows.
If the DataGrid and nested ComboBox controls are moved into a User Control, then you can’t use the StaticResource keyword (shown earlier in Figure 3) to get to the ViewModel object. This is due to the fact that the ViewModel object is defined in the Resources of the parent and not in the actual User Control. You could write code to get to it and then bind the data (writing code is always an option but can quickly get out of control in larger applications when a lot of controls are involved), but what if you want to bind everything declaratively in XAML? Let's take a look at one option for doing this.
The DataContextProxy Class
When I first thought through different ways for solving this problem, I tried to define the ViewModel in the Resources of the nested User Control. This means that it's defined in the parent control and in the child User Control. If you try this, you’ll quickly realize that it doesn’t work because a new ViewModel object will be created and two object instances will now be in memory (one for the parent and one for the child User Control). I needed a way to get to the DataContext from the parent while in the User Control, but couldn’t do it using Resources—at least without writing code. (For the record, writing code to deal with this scenario is certainly a viable option, but if you find yourself in this situation across multiple pages and User Controls the code can quickly get out of control, making the application more difficult to maintain.) After thinking through this, I decided to create a solution modeled after the ScriptManagerProxy available in ASP.NET and created a class called DataContextProxy. The class shown in Figure 5 can be used to get to parent objects for data binding with minimal effort even when controls are nested within child User Controls.
The DataContextProxy class grabs the parent DataContext that flows down to the child User Control and binds it to a dependency property named DataSourceProperty. Looking through the code, you'll see that the Loaded event creates a Binding object with a source set to the current DataContext. Once the Binding object is created, SetBinding is called to bind it to the dependency property named DataSourceProperty. (Although the subject of dependency properties are outside the scope of this article, you can find more information about them at msdn.microsoft.com/en-us/library/cc221408(VS.95).aspx.) Once the DataContextClass is added to a Silverlight project, it can be compiled and used in XAML to help with the data binding in nested controls.
The DataContextProxy object can be defined in the nested User Control’s Resources section as shown next:
This allows the ViewModel defined in the parent to be accessed using XAML in the child User Control. For example, a ComboBox control nested in a DataGrid can easily get to the Branches property of a ViewModel object bound to the parent using the following binding syntax:
DisplayMemberPath="Title"
ItemsSource="\{Binding Source=\{StaticResource
DataContextProxy\},Path=DataSource.Branches\}" />
The binding syntax tells the ComboBox to bind to the statically-defined DataContextProxy object’s DataSource property. This allows the control to get to the original ViewModel object originally defined in the parent’s Resources section. The syntax then says to bind to the Branches property of the ViewModel by using the Path property. By using this approach, it's easy to access objects not normally exposed in deeply nested controls without writing code each time.
Break Down Data Binding Barriers
Silverlight has a lot of nice data binding features, but there will be times when you’ll run into a few road blocks as you’re developing applications. In this article you’ve seen different techniques that can be used when binding nested controls to data objects and seen how the DataContextProxy object can be used to access a parent's DataContext when controls are nested inside of other controls. I'm currently using this approach in several places within a large enterprise Silverlight application with great success.
About the Author
You May Also Like