Friday, February 19, 2016

Some XAML basics

This is a quick overview of some of the fundamental XAML syntax rules for creating objects and setting properties. It's stuff that's so basic, most WPF guides won't even both to go over it. But if you don't understand these things, life as a WPF developer will be difficult for you.

I had to learn all this through trial and error and inductive reasoning; hopefully spelling it out here will save someone some else the blood, sweat, tears involved.

XAML is fundamentally about describing an object tree that you want the program to construct. You can order an object of a particular type to be created by writing an XML element with a name that matches the class name. For example:

<!-- Creates a default-constructed TextBlock -->
<TextBlock />

This commands the parser to create a TextBlock object using the class's default constructor. This syntax is not limited to WPF classes; you can create anything you like this way:

<!-- Creates a default-constructed String -->
<!-- Need to have 'xmlns:system="clr-namespace:System;assembly=mscorlib"' in document header -->
<system:String />

That's great, but default construction will only get you so far. How can we specify property values for the objects we create? The full, verbose way to do this is by using what is called element syntax. This is how you would set the Text property on a TextBlock using element syntax:

<TextBlock>
   <TextBlock.Text>Hello world</TextBlock.Text>
</TextBlock>

A few things to note here:
  1. Properties are initialized using sub-elements of the class element (property elements)
  2. Property element names must be given as ClassName.PropertyName
  3. The content of the property element gives the property value
This is all well and good, but it's also a lot of typing. That's why attribute syntax was invented. Here is an equivalent way to construct this TextBlock:

<TextBlock Text="Hello world" />

Attribute syntax saves a lot of electronic trees, but it only works when the value of the property in question can be parsed from a string. It can't help you in cases like this:

<ListView>
   <ListView.ItemTemplate>
      <DataTemplate />
   </ListView.ItemTemplate>
</ListView>

Here, the type of the ListView.ItemTemplate property is DataTemplate, and there is no way to construct a DataTemplate from a string.

This raises an interesting question: what types of objects can the XAML parser construct from strings? Strings, obviously, are trivially constructable from strings. Primitive numerics like Int32 and Double aren't much harder. But then you encounter something like this:

<TextBlock Margin="2,4,8,2" />

FrameworkElement.Margin is of type Thickness. Thickness is a struct that exposes four public properties: Left, Top, Right, and Bottom. How can the XAML parser construct this type from the string given? Does it have hard-coded knowledge of Thickness? Or maybe there's a rule that you can assign to properties in order using a comma-delimited list? It's actually neither. If you look at the source code for the Thickness class, you'll see this:

[TypeConverter(typeof (ThicknessConverter))]
public struct Thickness : IEquatable<Thickness>

The XAML parser will notice that TypeConverterAttribute, instantiate the type of converter it indicates (ThicknessConverter), and ask it to try to convert the String "2,4,8,2" into a Thickness. And ThicknessConverter knows how to do it. If you want to be able to instantiate your own types from strings in XAML, all you need to do is write an appropriate TypeConverter and attach a TypeConverterAttribute that points to it.

There's yet another technique for building objects from strings: the markup extension. The XAML for a markup extension looks like this:

<TextBlock Text="{StaticResource ApplicationName}" />

The XAML always begins with '{' and ends with '}'. The first word following the '{' is the name of a class that descends from MarkupExtension (if the class name ends with "Extension", as convention dictates, the word "Extension" can be omitted). After the class name is a space followed by a comma-delimited list of constructor arguments (if any). After the constructor arguments are optional property assignments as a comma-delimited list of "Name=Value" pairs.

When the XAML parser encounters a markup extension, it looks up and instantiates the type using any provided constructor arguments, initializes its properties as indicated, and then calls its virtual MarkupExtension.ProvideValue() method. This method returns an object, which is then assigned to the property on the left-hand side of the expression.

Markup extensions can also be nested inside of other markup extensions. For example:

<Grid IsHitTestVisible="{Binding IsOpen,
   RelativeSource={RelativeSource TemplatedParent},
   Converter={StaticResource InverseBoolConverter}}" />

Let's break down what's happening here:
  1. A Binding markup extension is instantiated, passing the string "IsOpen" to its constructor
  2. A RelativeSource markup extension is instantiated, passing the enum value RelativeSourceMode.TemplatedParent to its constructor
  3. RelativeSource.ProvideValue() is called, and the return value is assigned to the Binding.RelativeSource property
  4. A StaticResourceExtension markup extension is instantiated, passing the string "InverseBoolConverter" to its constructor
  5. StaticResourceExtension.ProvideValue() is called, and the return value is assigned to the Binding.Converter property
  6. Binding.ProvideValue() is called, and the return value is assigned to the Grid.IsHitTestVisible property
A lot going on, but it's all pretty standard and comprehensible once you understand what the syntax means.

Now that you know about markup extensions, I can make something I said earlier less untrue (this is a technique called teaching by diminishing deception). Consider this code again:

<ListView>
   <ListView.ItemTemplate>
      <DataTemplate />
   </ListView.ItemTemplate>
</ListView>

I said before that ListView.ItemTemplate needs to be set using element syntax because there's no way to construct a DataTemplate from a string. You now know this isn't trueyou could use a markup extension. An obvious candidate is StaticResourceExtension:

<UserControl>
   <UserControl.Resources>
      <DataTemplate x:Key="MyDataTemplate" />
   </UserControl.Resources>

   <ListView ItemTemplate="{StaticResource MyDataTemplate}" />
</UserControl>

Now you know another rule of XAMLthere are always about a half dozen ways to achieve any particular goal.