Conditional XAML and x:Bind

By Fons Sonnemans, posted on
8699 Views

Recently I used Conditional XAML for the first time in one of my UWP apps. I wanted to use a ColorPicker control which is only available in the Fall Creators Update (version 1709, build 16299). The Microsoft documentation explains what Conditional XAML is really well.

Conditional XAML provides a way to use the ApiInformation.IsApiContractPresent method in XAML markup. This lets you set properties and instantiate objects in markup based on the presence of an API without needing to use code behind. It selectively parses elements or attributes to determine whether they will be available at runtime. Conditional statements are evaluated at runtime, and elements qualified with a conditional XAML tag are parsed if they evaluate to true; otherwise, they are ignored.

Conditional XAML is available starting with the Creators Update (version 1703, build 15063). To use conditional XAML, the Minimum Version of your Visual Studio project must be set to build 15063 (Creators Update) or later, and the Target Version be set to a later version than the Minimum. See Version adaptive apps for more info about configuring your Visual Studio project.

While testing my app I noticed that it can be a little tricky when you combine it with {x:Bind}. Let me explain this using a simple demo.

The Problem

To explain the problem, I have created an Universal App which uses the MVVM design pattern. I have a simple Employee model class and MainViewModel class.

class Employee {

    public string Name { get; set; }
    public double Salary { get; set; }

    public Employee(string name, double salary) {
        this.Name = name;
        this.Salary = salary;
    }

}

sealed class MainViewModel  {

    public static MainViewModel Current { get; } = new MainViewModel();

    public Employee Employee { get; } = new Employee("Fons", 2000);

    private MainViewModel() { // Singleton
    }

}

In my Page I use {x:Bind} to databind the Text of the TextBlock controls to the Name and Salary of the Employee from the MainViewModel.

<Page x:Class="ConditionalXamlAndCompiledBindingDemo.Views.Pages.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:vm="using:ConditionalXamlAndCompiledBindingDemo.ViewModels"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      mc:Ignorable="d">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
          Padding="8">

        <StackPanel>
            <TextBlock Text="{x:Bind vm:MainViewModel.Current.Employee.Name}" />
            <TextBlock Text="{x:Bind vm:MainViewModel.Current.Employee.Salary}" />
        </StackPanel>

    </Grid>
</Page>

I want the StackPanel only to be used on computers running the Fall Creators Update. So, I used Conditional XAML. I declared two conditional XAML namespaces at the top of my page: fallOrHigher and lowerThanFall. I used the IsApiContractPresent(ContractName, VersionNumber) and IsApiContractNotPresent(ContractName, VersionNumber) methods to check for the UniversalApiContract version 5 (Fall Creators Update). The StackPanel is prefixed with the fallOrHigher namespace.

<Page x:Class="ConditionalXamlAndCompiledBindingDemo.Views.Pages.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:vm="using:ConditionalXamlAndCompiledBindingDemo.ViewModels"
      xmlns:fallOrHigher="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractPresent(Windows.Foundation.UniversalApiContract,5)"
      xmlns:lowerThanFall="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractNotPresent(Windows.Foundation.UniversalApiContract,5)"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      mc:Ignorable="d">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
          Padding="8">

        <fallOrHigher:StackPanel>
            <TextBlock Text="{x:Bind vm:MainViewModel.Current.Employee.Name}" />
            <TextBlock Text="{x:Bind vm:MainViewModel.Current.Employee.Salary}" />
        </fallOrHigher:StackPanel>

    </Grid>
</Page>

When I tested the application it seemed to work fine, the StackPanel was visible. But you are tricked. You also have to test it on an older version of Windows (1703). To do/simulate this I changed the IsApiContractPresent() method of the fallOrHigher namespace into an IsApiContractNotPresent(). The application crashed with a NullReferenceException.

The Solution

Examining the exception I learned that it was caused by the generated code of the {x:Bind}.

System.NullReferenceException

The StackTrace shows it occured in the Set_Windows_UI_Xaml_Controls_TextBlock_Text() method which is automatically generated by the {x:Bind}. That's why it is called compiled binding. The solution is easy, don't generate this code. We can do this by adding the fallOrHigher namespace prefix in front of the databound properties of the inner controls of the StackPanel. Or we can add the prefix in front of the all inner controls which use {x:Bind}. This makes the {x:Bind} conditional and solves our problem.

<fallOrHigher:StackPanel>
    <TextBlock fallOrHigher:Text="{x:Bind vm:MainViewModel.Current.Employee.Salary}" />    
    <fallOrHigher:TextBlock Text="{x:Bind vm:MainViewModel.Current.Employee.Name}" />
</fallOrHigher:StackPanel>

When you test your app you won't get the exception and the StackPanel isn't there. Don't forget to change the IsApiContractNotPresent() method of the fallOrHigher namespace back into the IsApiContractPresent().

Closure

Testing Conditional XAML on correct use is very important. It's not difficult but easily overlooked. Conditional XAML is very powerful and I will use it a lot in my apps. The 1803 version of the SDK has some new properties and the TreeView control which I want to start using.

You can download the sample code using the download button below.

Fons

Download

All postings/content on this blog are provided "AS IS" with no warranties, and confer no rights. All entries in this blog are my opinion and don't necessarily reflect the opinion of my employer or sponsors. The content on this site is licensed under a Creative Commons Attribution By license.

Leave a comment

Blog comments

0 responses