Every now and then a question comes up concerning a missing DataContext. The questions usually center around a ViewModel being set to a View properly, but then developer can not bind some command or property to a FrameworkElement. And typically the FrameworkElement in question is nested within an ItemsControl’s ItemTemplate or other similar structure.
First things first, once a DataContext is set, it is available for every child and all nested children until another DataContext is set. That’s why the following Xaml snippet works, and we don’t need to set the DataContext on both TextBlocks.
This is basic data-binding and is at the heart of WPF, Silverlight and Windows Phone 7 development. So, no matter how many elements we nest within the Grid, they all will have access to the DataContext until an element is assigned its own DataContext.
The question of the lost DataContext usually arises when the Xaml is slightly more complex. For example,
This can tend to trip up developers new to WPF and Silverlight. Why?
The first reason is that Grid on line 5 has had its DataContext changed transparently; the DataContext of the ItemsControl is still MyViewModel, but the internal grid has its DataContext set to an object in the bound collection (seen in line 2 as the ItemsSource). In other words, the DataContext of the grid inside the ItemTemplate is an instance of an object in the collection MyItems. For the above binding to work, the objects in MyItems would need have a property named MyItemText and a property called DeleteItemCommand.
The second reason is even if a developer realizes that the DataContext of the ItemTemplate is an instance of an object in the list, they still want to bind to a property on the original ViewModel. Most likely, the main ViewModel contains the consolidated business logic necessary for the requested task. Or perhaps the list of items contains basic objects pulled from a RESTful service, so adding additional properties (especially ICommands) is too prohibitive.
So, what can we do?
As with most development, there are multiple ways to accomplish this task. I’m going to show several of the more common solutions for obtaining a reference to what I call the main ViewModel. In the two snippets I showed above, I’m assuming a StaticResource contains an instance of the ViewModel I’m concerned with. However, that’s typically not the best practice for setting a ViewModel on a view. More than likely, the main ViewModel will be set in code or through dependency injection.
For the sake of completeness, if you did have a StaticResource pointing to your ViewModel, then you would change line 8 to read:
If, however, your ViewModel is set in code, what can you do? Well, the ItemsControl’s DataContext would still be set to the main ViewModel in this instance and there are several ways you can get to it.
The first way uses element-to-element binding. In order to use this, you’ll have to explicitly name your ItemsControl, then use a slightly different binding syntax to reference it:
Another method would to use the RelativeSource option for the binding.
My personal preference is to use the RelativeSource option so that I don’t need to sync the element names. The only caveat with RelativeSource would be multiple nesting of ItemsControls (or your container of choice) and then requiring access all the way back to the original ViewModel.
One last thing. While my example is contrived, the DeleteItemCommand will need to know what item to delete. Since we are not changing the DataContext on the button, we still have access to the forced DataContext the ItemsControl imposed on us, namely an instance of an item in the bound collection. Therefore, we could also include a CommandParameter so that the delete command knows what to delete. In all three cases presented above, you would add the following property to the delete button:
So there you have it. In certain surprisingly common scenarios, mainly involving the ItemTemplate of ItemsControl and its derived classes such as ListBox, the DataContext you get may not be the one you’re expecting. However, with element-to-element and RelativeSource bindings you can easily obtain the references you need.