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

image image

 

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:

image


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.

 

Logo

For more information about onsite, online and video training, mentoring and consulting solutions for .NET, SharePoint or Silverlight please visit http://www.thewahlingroup.com.

comments powered by Disqus

8 Comments

  • You could do something very similar with a value converter.

  • Bart,

    You could, but you'd have to walk up the object tree to get to the DataContext (since it's not exposed directly in the Convert or ConvertBack methods) plus apply it to every binding where it's needed which could be many across multiple user controls. I'm not implying that the DataContextProxy solution shown here is the best or only solution of course, but it makes it so that you can do binding the "normal" way without much extra effort.

    Having said that, I'd definitely be interested in seeing any samples you might have related to using a value converter in this type of a scenario. I'm always looking for other options. :-)

  • In WPF I use attached inherited property, I called it TagEx (typeof object),

    I set it on the parent to any object, and it's value is propagated down through hierarchy.

    Later I can bind to it this way:

    {Binding RelativeSource={RelativeSource Self}, Path=(local;General.TagEx).&lt;Path to property in the object&gt;}

    But as far as I know, there are no inherited properties in silverlight (and no relativeSource=Self?)

    Good idea to use proxy control in this situation!

    Thanks,

    Kirill

  • Thanks for sharing the WPF trick Kirill. Always good to see different options.

  • Dan,

    Great post - wonder if you could offer a bit advice on a similar problem but with a generic user control. I have a control that displays a listbox in a standard format of a dozen different entities in my application. I have a VM associated with the control and then I create descendent VM for each of my entities.

    If I take a large composite view where I might have 3 or 4 of these lists I'm trying to get the VM into the usercontrol. I have them as properties of the mainview VM and have tried databinding to pass them in but I'm not having much luck.

    My only workaround is in the loaded event to manually set the Model property of each child list to the exposed ChlidModel property.

    Hopefully that makes a bit of sense. I'm using SilverlightFx which has exposed the ViewModel as a DependencyProperty (Model).

    jack

  • Jack,

    I'm doing something quite similar with ListBoxes (will probably write up a post about it at some point) so it should definitely work. As long as the Model property is setup properly as a DependencyProperty and the data is available at that level of nesting then it should work out. I haven't tried SilverlightFx so I'm not sure if it's something to do with that. Here's an example of one of my properties that I'm binding data to in my user control. I suspect yours looks identical overall but thought I'd throw it out there.

    public static readonly DependencyProperty AvailableItemsSourceProperty =
    DependencyProperty.Register("AvailableItemsSource", typeof(IEnumerable),
    typeof(ItemPicker), new PropertyMetadata(new PropertyChangedCallback(AvailableItemsSourceChanged)));

    public IEnumerable AvailableItemsSource
    {
    get { return (IEnumerable)GetValue(AvailableItemsSourceProperty); }
    set { SetValue(AvailableItemsSourceProperty, value); }
    }

  • Thanks for the trick. Very helpful !

  • Great trick Dan! We are using this with DataForms since everything in the form is bound to the item. Works great!

Comments have been disabled for this content.