How to know when the DataContext changed in your control

The DataContext is a wonderful property, you can set it somewhere in the logical tree and any child control can just bind to properties without having to know where the DataContext was set. A great capability that makes live much simpler when writing XAML. But sometimes when building a control of some sorts you just need to know when the DataContext has changed. For example when binding to events on whatever object is set as the current DataContext.

Take for example the following simple form.

image

In this form I am data bound to a real simple Person object which implements the INotifyPropertyChanged interface so we can be informed that a property has changed. The bottom control is a very simple custom control that displays the last property being changed. The code looks like this:

public partial class MyControl : UserControl
{
    public MyControl()
    {
        InitializeComponent();
        Loaded += new RoutedEventHandler(MyControl_Loaded);
    }

    void MyControl_Loaded(object sender, RoutedEventArgs e)
    {
        INotifyPropertyChanged person = DataContext as INotifyPropertyChanged;
        if (person != null)
            person.PropertyChanged += new PropertyChangedEventHandler(person_PropertyChanged);
    }

    void person_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        PropertyName.Text = e.PropertyName;
    }
}


The Page code looks like this:



public partial class Page : UserControl
{
    public Page()
    {
        InitializeComponent();
        DataContext = new Person() { FirstName = "Maurice", LastName = "de Beijer" };
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        DataContext = new Person() { FirstName = "John", LastName = "Doe" };
    }
}


 



Pretty simple and it works just fine as long as I don’t press the button.







Pressing the button means I set the DataContext to a new person object. The data binding notices and shows the required changes just fine. However the INotifyPropertyChanged event is still hooked on the old person object and we somehow need to unhook this object and hook the new person object.



In WPF there is an DataContextChanged event that will let us know the DataContext is changed and we need to change out event binding. Unfortunately Silverlight the FrameworkElement class in Silverlight has the same event but it is marked as internal so we cannot use it [:(].



So how can we know the DataContext has changed is the event is not public?



It turns out the standard DataBinding holds the answer to the problem. All we need to do is create an extra DependencyProperty and bind that to the DataContext. Sounds complicated but is actually quite simple.



The new version of my custom control is below.



public partial class MyControl : UserControl
{
    public MyControl()
    {
        InitializeComponent();
        SetBinding(MyDataContextProperty, new Binding());
    }


    public static readonly DependencyProperty MyDataContextProperty =
        DependencyProperty.Register("MyDataContext",
                                    typeof(Object),
                                    typeof(MyControl),
                                    new PropertyMetadata(DataContextChanged));

    private static void DataContextChanged(
        object sender, 
        DependencyPropertyChangedEventArgs e)
    {
        MyControl myControl = (MyControl)sender;
        INotifyPropertyChanged person = e.NewValue as INotifyPropertyChanged;
        if (person != null)
            person.PropertyChanged += myControl.person_PropertyChanged;
    }

    void person_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        PropertyName.Text = e.PropertyName;
    }
}


Instead of just reading the DataContext property I first created a new DependencyProperty called MyDataContextProperty. The reason this exists is so we can set the PropertyChangedCallback that lets us know when the property is changed. Note there are no normal property get and set methods as we don’t need them. The remaining and most important part to this to work is the line:



SetBinding(MyDataContextProperty, new Binding());


This line in the constructor actually bind the value of the MyDataContext to the real DataContext property.





With this set up I am free to do whatever I want in the DataContextChanged event that gets fired whenever the DataContext changes. In this example the code just removes the vent handler for the old DataContext PropertyChanged event and adds an event hook on the PropertyChanged event for the new DataContext.



private static void DataContextChanged(
    object sender,
    DependencyPropertyChangedEventArgs e)
{
    MyControl myControl = (MyControl)sender;
    INotifyPropertyChanged person;

    person = e.OldValue as INotifyPropertyChanged;
    if (person != null)
        person.PropertyChanged -= myControl.person_PropertyChanged;

    person = e.NewValue as INotifyPropertyChanged;
    if (person != null)
        person.PropertyChanged += myControl.person_PropertyChanged;
}




 



So all it takes is a few lines of code and we know exactly when the DataContext was changed [:)].



[f1]
[f2]

2 thoughts on “How to know when the DataContext changed in your control

  1. Just FYI:
    The Binding instance in SetBinding(…) is from the System.Windows.Data namespace. Using Visual Studio’s Resolve tool may display System.ServiceModel.Channels first, which contains an abstract Binding class.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>