Star date: 2009.297
Josh Smith just made a blog post about XAML DataContext comments when using the MVVM pattern. He makes a great point, which is that in many cases it is not immediately obvious what the DataContext of a view is intended to be. A simple comment as Josh suggests will go a long way, but the downside is that comments create a maintainability issue. If you rename a model view, or refactor code so that a page, window or user control expects a different DataContext you also need to update the comment. Here's an example:
<!-- DataContext = SampleModelView -->
<UserControl x:Class="SampleUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Width="300" Height="300">
<Grid>
</Grid>
</UserControl>
But now what if I change my mind and decide that instead of SampleModelView, this UserControl will have AlternateModelView as its DataContext? If I forget to update the comment then it is now a source of misinformation. What I would really like to do is somehow specify the expected data context type for a given UI element, so I created an attached property called ExpectedDataContextType. When the element is loaded, it will fail at runtime if the DataContext is not of the desired type. It looks like this:
<UserControl x:Class="TestControl"
xmlns:local="clr-namespace:ExpectedDataContextType"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Width="300" Height="300"
local:DataContextHelper.ExpectedDataContextType="{x:Type local:TestModelView}">
<Grid>
<TextBlock>Hello</TextBlock>
</Grid>
</UserControl>
The code is extremely simple. It just attaches a handler to the Loaded event and then checks the type. When the types do not match up it gives you a warning via a message box if you are debugging. I chose not to throw an exception because the exception gets covered up and just ends up as a tiny message on Visual Studio immediate window. When I am debugging and a control has the wrong DataContext I want to make sure I find out about it, hence the message box. You can always replace the message box code with something else though.
Option Strict On
Public Class DataContextHelper
Public Shared Function GetExpectedDataContextType(
ByVal element As DependencyObject) As Type
If element Is Nothing Then
Throw New ArgumentNullException("element")
End If
Return DirectCast(element.GetValue(ExpectedDataContextTypeProperty), Type)
End Function
Public Shared Sub SetExpectedDataContextType(
ByVal element As DependencyObject, ByVal value As Type)
If element Is Nothing Then
Throw New ArgumentNullException("element")
End If
element.SetValue(ExpectedDataContextTypeProperty, value)
End Sub
Public Shared ReadOnly ExpectedDataContextTypeProperty As _
DependencyProperty = DependencyProperty.RegisterAttached("ExpectedDataContextType", _
GetType(Type), GetType(DataContextHelper), _
New FrameworkPropertyMetadata(Nothing, AddressOf OnExpectedDataContextTypeChanged))
Private Shared Sub OnExpectedDataContextTypeChanged(
ByVal obj As DependencyObject, ByVal args As DependencyPropertyChangedEventArgs)
Dim element = DirectCast(obj, FrameworkElement)
AddHandler element.Loaded, AddressOf OnElementLoaded
End Sub
Private Shared Sub OnElementLoaded(
ByVal sender As Object, ByVal args As RoutedEventArgs)
Dim element = DirectCast(sender, FrameworkElement)
RemoveHandler element.Loaded, AddressOf OnElementLoaded
' Compare the expected type to the actual type
Dim expectedDataContextType = GetExpectedDataContextType(element)
Dim actualDataContextType = element.DataContext.GetType
If expectedDataContextType IsNot actualDataContextType Then
#If DEBUG Then
' The types don't match and debug mode is on so notify the developer
' that the element has the wrong data context
MessageBox.Show(String.Format("DataContext has type {0}. Expected {1}.", _
actualDataContextType.ToString, _
expectedDataContextType.ToString))
#End If
End If
End Sub
End Class
The end result is that you are still specifying what the type of your DataContext should be, but now it is actually enforced. The best part is that if the you rename your model view without updating the ExpectedDataContextType property you will get a compile error because the type no longer exists. If the type still exists then you have to settle for a runtime error.