Changing DataContext and Compiled Data Binding in Universal Windows Platform Applications

Changing DataContext and Compiled Data Binding in Universal Windows Platform Applications

Back in April I talked briefly about Compiled Data Binding and how it improves performance throughout your XAML application by eliminating all the reflection calls that go on in the background with traditional data binding. Recently I’ve been spending more time investigating the best way to structure code and design time data to ensure high quality applications. One of the easy pitfalls with the x:Bind syntax is a lack of understanding of the context of the data binding.

To start with, let’s recap how traditional data binding works: Elements on the page have a DataContext, which you can think of as the object that is being data bound to. The DataBinding expression includes a Path, which determines the property on the DataContext that a particular attribute on an element is being data bound to. By default the DataContext flows down the page/usercontrol as each element inherits the DataContext from its parents. However, it is possible to override the DataContext by setting it explicitly for each element. There are plenty of sites/pages that can provide more detail on this form of data binding.

The new compiled data binding, which I’ll refer to as x:Bind, tries to eliminate any use of reflection at runtime. In order to do this, the compiler generates a large quantity of code to connect the data entities and the visual elements. This means that the compiler must know Type information about both the attribute on the visual element, and the properties on the element being data bound. The starting point is that the context for x:Bind expressions on a Page is the Page itself, and similarly for a UserControl, it is the UserControl itself. If we assume that the data we want to data bind to is within our ViewModel, then we need to expose the ViewModel as a property on the Page. For example:

public MainViewModel CurrentViewModel => DataContext as MainViewModel;

and then in the XAML for the Page it can be referenced as follows:

<TextBlock Text=”{x:Bind CurrentViewModel.Name, Mode=OneWay}” />

This references the Name property on the MainViewModel entity returned by the CurrentViewModel property on the page. What’s not immediately obvious here is that despite indicating that the Mode is OneWay (ie it should detect changes) there is no code that notifies the data binding framework if the CurrentViewModel changes. This could happen if the DataContext changes, for example in the following code where the OnNavigatedTo method has a small latency before it sets the DataContext.

protected async override void OnNavigatedTo(NavigationEventArgs e)
{
    base.OnNavigatedTo(e);
    await Task.Delay(2000);

    DataContext = new MainViewModel();
}

In this case, the underlying DataContext is updated after 2 seconds to the new instance of the MainViewModel. This means that the CurrentViewModel value has also changed. However, there has been no attempt to notify the data binding framework, and subsequently the page, of this change. There are a number of ways to address this issue, the two I’m going to present here both rely on the Page implementing the INotifyPropertyChanged interface, exposing an event PropertyChanged, which the data binding framework will listen for.

Option 1 – Raise the PropertyChanged event manually after setting the new DataContext eg

DataContext = new MainViewModel();
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(“CurrentViewModel”));

Option 2 – Intercept the DataContextChanged event and then raise the PropertyChanged

public MainPage()
{
    InitializeComponent();

    DataContextChanged += MainDataContextChanged;
}
private void MainDataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
{
    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(“CurrentViewModel”));
}

Both of these options use a hardcoded property name, CurrentViewModel, which is not recommended. A last option, that might appear more complex to begin with, has the advantage of being simple, clean and less riddled with string constants. It starts with a Wrapper class, which provides a level of indirection between the Page and the ViewModel:

public class Wrapper<T>:INotifyPropertyChanged
{
    public T Entity { get; set; }
    public event PropertyChangedEventHandler PropertyChanged;

    public Wrapper(FrameworkElement element)
    {
        element.DataContextChanged += Element_DataContextChanged;
    }

    private void Element_DataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
    {
        if (args.NewValue is T)
        {
            Entity = (T) args.NewValue;
        }
        else
        {
            Entity = default(T);
        }
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(“Entity”));
    }

    public virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

And using this entity is simple as you just need to define the type of ViewModel:

public Wrapper<MainViewModel> Data => new Wrapper<MainViewModel>(this);

The only other difference is that there is an extra level of indirection in the x:Bind expression.

<TextBlock Text=”{x:Bind Data.Entity.Name, Mode=OneWay}” />

Now as the DataContext on the Page changes the Wrapper class will detect the change and raise the PropertyChanged event indicating that the “Entity” has changed. Since x:Bind connects to any object in the binding path that implements INotifyPropertyChanged, it will handle this event and look for the updated Entity property, which will be the new DataContext value.

As you start to work with x:Bind, you will notice that your binding expressions are longer than you’d be used to. Don’t worry about this, since the data binding itself will be orders of magnitude quicker