With the release of Windows 8 and WinRT, I began the process, during RECESS, of porting an existing app we’d created for the Microsoft Surface (the team that is now called “Pixel Sense”) to be a Windows Store application. The application followed more of an MVC (Model View Controller) approach, rather than MVVM (Model View ViewModel), so there we a number of controls that subscribed to the DataContext and Visibility changed events in their code behind, which don’t exist in WinRT.
WinRT (Windows Store) applications seem to have many of the same limitations that Silverlight had, compared to WPF, and rather than rewrite the parts of the application to try and avoid needing to know when these DPs changed, I came up with a simple framework to register for a callback when a specified DP changed.
First Solution
The way to get around this issue was actually really straight forward. In each control, I created and registered a DP for each DP I wanted a change notification from (ie. DataContextEx, VisibilityEx, etc…).
public static readonly DependencyProperty DataContextExProperty =
DependencyProperty.Register( "DataContextEx", typeof( object ), typeof(DemoControl ),
new PropertyMetadata( null, OnDataContextExChanged ) );
public object DataContextEx
{
get { return (object)GetValue( DataContextExProperty ); }
set { SetValue( DataContextExProperty, value ); }
}
private static void OnDataContextExChanged( DependencyObject d, DependencyPropertyChangedEventArgs e )
{
( (DemoControl)d ).OnDataContextExChanged( e );
}
private void OnDataContextExChanged( DependencyPropertyChangedEventArgs e )
{
//Handle the change
}
Then, in the control’s constructor, I bound the created DP(s) to the related DP I wanted to know had changed. With this binding, whenever the source DP (ie. DataContext, Visibility, etc…) changed it would cause the “extension” DP to change and that would call the changed handler I registered when creating the DP.
var binding = new Binding
{
Path = new PropertyPath("DataContext"),
RelativeSource = new RelativeSource
{
Mode = RelativeSourceMode.Self
}
};
SetBinding(DataContextExProperty, binding);
With this I had all that I needed. However, I hated the idea of having to rewrite this code on every control that I needed the notification on. So I decided to create a framework to allow me to register for a changed callback on any FrameworkElement.
Framework
I decided to go the route of creating a FrameworkElement extension method called RegisterDependencyPropertyChanged that takes a lambda expression for the property to bind to and a callback for when that property changes.
this.RegisterDependencyPropertyChanged( () => DataContext, DataContextChangedHandler );
The extension then calls RegisterDependencyPropertyBinding on FrameworkElementAttachedProperties. FrameworkElementAttachedProperties is an internal class that holds the functionality to create and register an Attached DP for each type of DP (ie. DataContext, IsHitTestVisible, Visibility, etc..) the user desires a callback for and bind that Attached DP to the DP (using the same process defined under First Solution. Then whenever that value changes it attempts to find a callback registered for it, and if one is found, calls the registered callback with the DependencyPropertyChangedEventArgs provided.
Attached DPs are only able to be bound to one property of an object at a time (but can be bound to the same property of multiple objects). This is why an Attached DP is created and registered for each DP of a control as there may be a case where a change callback is registered for more than one property of that control. Therefore whenever FrameworkElementAttachedProperties.RegisterDependencyPropertyBinding is called it calls GetNextUnusedAttachedPropertyForFrameworkElement. This either grabs an existing Attached DP registered for that property, or it will create and register a new one and return it.
private static DependencyProperty GetNextUnusedAttachedPropertyForFrameworkElement( string propertyName )
{
if ( !_staticExtensionDPs.ContainsKey( propertyName ) )
{
var unusedDependencyProperty = DependencyProperty.RegisterAttached( _extensionDPsNamePrefix + "_" + propertyName,
typeof( object ),
typeof( FrameworkElementAttachedProperties ),
new PropertyMetadata( null, DependencyPropertyExPropertyChanged ) );
_staticExtensionDPs.Add( propertyName, unusedDependencyProperty );
}
return _staticExtensionDPs[propertyName];
}
The registered callbacks are stored in the DependencyPropertyCallbacks Attached DP on FrameworkElementAttachedProperties. By storing the callbacks in the Attached DP, when the control that has registered is unloaded, it can be collected by the Garbage Collector and there aren’t memory leaks.
Conclusion
I tend to prefer the MVVM approach, but there are cases when constraints dictate that a different approach is used. This is a nice little addition for when that occurs. Here is a demo solution containing the framework (FrameworkElementExtension.cs) and an example of how to use it.