Creating a Silverlight DataContext Proxy to Simplify Data Binding in Nested Controls
Data binding is one of my favorite features and something that I use a lot while building Silverlight applications. Silverlight makes it really easy 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. Here’s an example of binding a ComboBox control to an object that defines a property named Branches:
<ComboBox 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.
This works great as long as the ComboBox has access to the Branches property as shown in Figure 1 below (note that the Page shown in the figure is a Silverlight Page class or User Control not a web page). However, 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 obviously) so the ComboBox no longer has access to it for data binding. Or does it?
Figure 1 – All Systems go for binding |
Figure 2 – Houston, we have a problem |
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 items control) it can still get to the ViewModel object’s Branches property with a few minor changes. The first way that this little dilemma 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. This works but who wants to write code when you don’t have to? The second way is to create a mini ViewModel class that each row binds to 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. Here’s an example of defining a the ViewModel object declaratively in XAML:
<UserControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:vm="clr-namespace:JobPlan.ViewModel;assembly=JobPlan.ViewModel" x:Class="JobPlan.View.UserControls.JobEdit"> <UserControl.Resources> <vm:ViewModel x:Key="ViewModel" /> </UserControl.Resources> ... </UserControl>
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:
<ComboBox 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. Pretty cool 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 an intermediary ViewModel lookup object when things need to be as loosely coupled as possible.
Now that you see how nested objects can get to properties defined at higher levels I can cover the main point of this post. 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 one final problem where the StaticResource binding trick won’t work. Take a look at the figure below to get an idea of the problem:
If the DataGrid and nested ComboBox controls are moved into a User Control you can’t use the StaticResource keyword to get to the ViewModel object. Why? Because the ViewModel object is defined in the Resources of the parent not in the actual User Control itself. You could of course 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 with a lot of controls), but what if you want to bind everything declaratively in XAML? I don’t know of a built-in way to do that (and I’ve asked many people and have yet to hear an answer that didn’t involve mini ViewModels for each row or writing code) so I ended up creating a fairly simple solution to address the problem.
The DataContextProxy Class
When I first tried to solve this problem I figured I could define the ViewModel again up in the Resources of the nested User Control. Try this and you’ll quickly realize that it doesn’t work because it’ll cause a new ViewModel object to be created and two 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 but couldn’t do it using Resources it seemed…without writing code anyway (on a side note, I’m not opposed to writing code at all but the application I’m working on encounters the problem defined here frequently and the code started to get ridiculous). I started thinking about how the ScriptManagerProxy class works in ASP.NET AJAX and played around with something I now call the “DataContextProxy”. I needed a way to get to the parent’s DataContext and came up with the following class that does just that:
using System; using System.Windows; using System.Windows.Controls; using System.Windows.Data; namespace JobPlan.Controls { public class DataContextProxy : FrameworkElement { public DataContextProxy() { this.Loaded += new RoutedEventHandler(DataContextProxy_Loaded); } void DataContextProxy_Loaded(object sender, RoutedEventArgs e) { Binding binding = new Binding(); if (!String.IsNullOrEmpty(BindingPropertyName)) { binding.Path = new PropertyPath(BindingPropertyName); } binding.Source = this.DataContext; binding.Mode = BindingMode; this.SetBinding(DataContextProxy.DataSourceProperty, binding); } public Object DataSource { get { return (Object)GetValue(DataSourceProperty); } set { SetValue(DataSourceProperty, value); } } public static readonly DependencyProperty DataSourceProperty = DependencyProperty.Register("DataSource", typeof(Object), typeof(DataContextProxy), null); public string BindingPropertyName { get; set; } public BindingMode BindingMode { get; set; } } }
The DataContextProxy class grabs the parent DataContext that flows down to the child User Control and binds it to a dependency property named DataSourceProperty. The DataContextProxy object can be defined in the User Control’s Resource section as shown next:
<UserControl.Resources> <controls:DataContextProxy x:Key="DataContextProxy" /> </UserControl.Resources>
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 using the following binding syntax:
<ComboBox 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.
Wrapping it Up
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 post 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. Hopefully this will help some of you out down the road when you’re working with a lot of nested controls.
For more information about onsite, online and video training, mentoring and consulting solutions for .NET, SharePoint or Silverlight please visit http://www.thewahlingroup.com.