Mobile Corner
Compiled Data Binding in the Universal Windows Platform
Nick Randolph investigates the inner workings of the new compiled data binding support available to Windows platform developers in the Universal Windows Platform.
- By Nick Randolph
- 06/17/2015
Developers that have worked with any one of the XAML-based development technologies (Windows Presentation Foundation, Silverlight, Windows Phone, Windows Store, Universal Windows Platform) will most likely be familiar with data binding and how to use it to connect an underlying data model with what's being displayed on the screen. For example, if you have a Person class with properties FirstName and LastName you could present it as:
<Page.Resources>
<local:Person x:Name="MyPerson"
FirstName="Bob"
LastName="Jones" />
</Page.Resources>
<StackPanel DataContext="{StaticResource MyPerson}" Margin="50">
<TextBlock Text="{Binding FirstName}" />
<TextBlock Text="{Binding LastName}" />
</StackPanel>
When I first saw the Binding syntax in XAML I was concerned because each of the paths in the data binding expression (for example, FirstName and LastName) are string literals with no compile-time validation. It turns out that by using Blend for Visual Studio, this becomes less of a problem because the designer helps identify available properties and reduces the chances of errors. The data binding framework is also quite resilient in that if the path doesn't exist on the data model it won't break or throw an exception. During development you may see a binding failure in the Output window within Visual Studio that can aid in identifying incorrect binding expressions.
The other aspect of the Binding syntax to consider is how it works behind the scenes. The data-binding framework is heavily reliant on reflection to retrieve data from the underlying data model. For example, the binding framework needs to locate the FirstName property on the Person class, and then invoke the get in order to retrieve the current value. This is repeated for every PropertyChanged event that's raised as property values change on the data. This introduces a significant performance overhead for applications built using data binding.
As a lot of the applications that Microsoft is working on are now being built using the sample XAML toolset that third-party developers are using, there's a greater emphasis on making the data-binding framework perform better. Hence, the introduction of compiled data binding using the x:Bind syntax. Other than exchanging x:Bind in place of Binding, the rest of the binding syntax is very familiar. However, behind the scenes there are significant implications that will require reworking how data binding is used throughout the application.
The first thing to be aware of when data binding using x:Bind is that the binding expression is validated at compile time. This means that the path needs to exist as a property on the context. With the Binding syntax the DataContext for any element is either set explicitly, or is inherited from its XAML parent elements. For example, if an instance of the Person class is set as the DataContext for the Page, it would also be the DataContext for any child elements on the Page, unless the DataContext property was overridden. The x:Bind syntax uses a context that's set to the Page (or UserControl when working on a UserControl) for all elements on the Page and cannot be overridden. This means that the path on an x:Bind syntax must translate to a property on the Page. As with the Binding syntax, the path on an x:Bind expression can traverse across a number of properties.
The following code illustrates a property on the Page called MyPerson, using the new syntax for setting the property inline, and the use of x:Bind expressions to wire up the FirstName and LastName (note that the path in both expressions includes the MyPerson property on the page as the first step in the path):
public sealed partial class MainPage : Page
{
public Person MyPerson { get; private set; } =
new Person {FirstName = "Bob", LastName = "Jones"};
...
}
<StackPanel Margin="50">
<TextBlock Text="{x:Bind MyPerson.FirstName}" />
<TextBlock Text="{x:Bind MyPerson.LastName}" />
</StackPanel>
This works exactly the same as the Binding expression shown earlier with one significant difference -- it doesn't use any reflection at runtime to connect the properties on the Person entity with the Text attribute on the TextBlock elements. To demonstrate this, I'll expand out the FirstName automatic property and set a breakpoint on the get method, as shown in Figure 1.
When this breakpoint is hit, the Call Stack window in Figure 2 shows multiple call levels that show all the code that's generated during compilation when an x:Bind expression is used.
From the Call Stack window you can click on any level and jump straight to the generated code, allowing you to step through and debug all the generated code. In other words, x:Bind doesn't introduce more smoke and mirrors that can't be debugged and that would just add frustration. If you do step through the generated code you'll observe that the FirstName property is accessed via a strongly typed instance of the Person class. Similarly, the Text attribute on the TextBlock is accessed without the use of reflection.
Another difference with x:Bind is that by default the binding mode is OneTime, now OneWay, which is the default for the Binding syntax. I can demonstrate this with a simple method on the Page that updates the FirstName property (note that I've also updated the Person class to implement INotifyPropertyChanged and raise the PropertyChanged event when the FirstName property changes). If I was using the Binding syntax I would expect the corresponding TextBlock to update with the new FirstName:
public async void Run()
{
while (true)
{
MyPerson.FirstName += " .";
await Task.Delay(1000);
}
}
In order to invoke the Run method I'll add a Button to the Page and again use the x:Bind syntax to reference the Run method. Not only does this provide a strongly typed call to the Run method, without the use of reflection, it also eliminates the need for ICommand, which significantly cleans up the underlying data model:
<Button Click="{x:Bind Run}"> Run </Button>
Getting the TextBlock to update for changes to the FirstName property using the x:Bind syntax is as simple as adding the Mode attribute to the expression, as follows:
<TextBlock Text="{x:Bind MyPerson.FirstName, Mode=OneWay}" />
There are a couple of other points worth mentioning about the x:Bind syntax. The first is how data binding can be used with a data template. A data template doesn't have a context at compile time, so it is necessary to use the x:DataType attribute to define the type that will be used to generate the data-binding code and validate the binding expressions. In the following code snippet the ItemsSource property on the ListView is set to a property People on the Page, which returns an array of Person objects. The ItemTemplate is set inline to a DataTemplate where the x:DataType attribute is set to the Person class. Inside the DataTemplate each x:Bind expression is validated and compiled in reference to the Person class:
<ListView ItemsSource="{x:Bind People}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:Person">
<StackPanel Margin="50">
<TextBlock Text="{x:Bind FirstName}" />
<TextBlock Text="{x:Bind LastName}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The second point, which in a way follows on from the first, is that there are some additional steps necessary if a DataTemplate is defined in a ResourceDictionary. Normally a ResourceDictionary doesn't have a codebehind file associated with it and doesn't have a class name. In order for x:Bind expressions to be evaluated and the relevant code generated, the ResourceDictionary must be given a class name and have a corresponding codebehind file. The DataTemplate used previously has now been moved to the Styles ResourceDictionary as shown in in Listing 1.
Listing 1: Moving the DataTemplate to the Styles ResourceDictionary
<ResourceDictionary x:Class="App7.Styles"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App7">
<DataTemplate x:DataType="local:Person"
x:Name="PersonTemplate">
<StackPanel Margin="50">
<TextBlock Text="{x:Bind FirstName}" />
<TextBlock Text="{x:Bind LastName}" />
</StackPanel>
</DataTemplate>
</ResourceDictionary>
namespace App7
{
public partial class Styles
{
public Styles()
{
InitializeComponent();
}
}
}
In addition, when including the ResourceDictionary in the application or page, it should be done by instantiating an instance of the class, rather than simply loading the ResourceDictionary. Listing 2 shows the Styles ResourceDictionary being merged into the Application Resources; the commented line shows how a normal ResourceDictionary would be loaded (that is, one that doesn't have x:Bind expressions).
Listing 2: The Styles ResourceDictionary Being Merged into ApplicationResources
<Application x:Class="App7.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App7"
RequestedTheme="Light">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<local:Styles />
<!--<ResourceDictionary Source="Styles.xaml" />-->
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
In this article I've walked through the new x:Bind syntax for compiled data binding. This is set to offer XAML developers a significant performance boost when writing applications. Existing applications that are being migrated to the Universal Windows Platform can start to leverage this new syntax with only minimal changes to the XAML.
In summary, the main steps for migration are: switch Binding with x:Bind; add x:DataType to DataTemplate; and add class name to any ResourceDictionary that contains x:Bind expressions. There are some cases where it makes sense to use the Binding syntax, for example, where the type of the data entity being bound to is unknown. However, the performance benefits of x:Bind make it worth the effort of migrating any existing data-binding expressions.
About the Author
Nick Randolph runs Built to Roam, a consulting company that specializes in training, mentoring and assisting other companies build mobile applications. With a heritage in rich client applications for both the desktop and a variety of mobile platforms, Nick currently presents, writes and educates on the Windows Phone platform.