UI Code Expert

Exploring XAML Markup Extensions

Learn about the often-overlooked XAML extensibility resource, and how you can make custom markup extensions of your own.

Early on in a developer's XAML programming experience, he'll encounter the ability to assign values to properties using either XAML object element syntax or XAML attribute syntax. The following code demonstrates each approach using a System.Windows.Controls.Text-Box (the attribute usage syntax appears first):

<!-- Attribute Usage -->
<TextBox HorizontalAlignment="Left" VerticalAlignment="Top" 
          Name="AttributeUsageTextBox" 
          Text="Hello! My name is Inigo Montoya." />

<!-- Element Usage -->
<TextBox >
  <TextBox.HorizontalAlignment>Left</TextBox.HorizontalAlignment>
  <TextBox.VerticalAlignment>Top</TextBox.VerticalAlignment>
  <TextBox.Margin>0,28,0,0</TextBox.Margin>
  <TextBox.Name>ObjectElementUsageSyntaxTextBox</TextBox.Name>
  <TextBox.Text>You killed my father, prepare to die</TextBox.Text>
</TextBox>

On occasion, however, the attribute syntax is combined with curly braces ({}), and the value specified is interpreted with special syntax, rather than as a literal string, as shown here:

<TextBox HorizontalAlignment="Left" VerticalAlignment="Top" 
          Name="AttributeUsageTextBox" 
          Text="{x:Static system:Environment.UserName}" />

In this case, rather than providing hardcoded text, we assign the text to a static property, System.Environment.UserName.

Parenthetically, we could use object element syntax to specify the user name. I show this approach in the following code (note that this syntax is noticeably more verbose):

<!-- Element Usage -->
<TextBox >
  <TextBox.HorizontalAlignment>Left</TextBox.HorizontalAlignment>
  <TextBox.VerticalAlignment>Top</TextBox.VerticalAlignment>
  <TextBox.Name>ObjectElementUsageSyntaxTextBox</TextBox.Name>
  <TextBox.Text>
    <x:Static Member="system:Environment.UserName" />
  </TextBox.Text>
</TextBox>

A second parenthetical note when using XAML attribute syntax: If you explicitly want curly braces, you can have them for the entire string by prefixing with empty curly braces (for instance, {}{Stop that rhyming}), or by prefixing each curly brace with a backslash.

The key thing about the attribute syntax is that it reveals the use of an extensibility mechanism within XAML: the markup extension. x:Static identifies a class within the winfx namespace called System.Windows.Markup.StaticExtension. In fact, the full name, "StaticExtension," could be used instead of the abbreviated name, "Static," as illustrated here:

<TextBox HorizontalAlignment="Left" VerticalAlignment="Top" 
          Name="AttributeUsageTextBox" 
          Text="{x:StaticExtension system:Environment.UserName}" />

This is similar to the way that the suffix of "Attribute" can be dropped from a .NET attribute's class name when it's used to decorate a construct within C# or Visual Basic .NET. As an example, consider [Serializable]class Person{} rather than [SerializableAttribute]class Person{}. (Note that the optional use of the "Extension" suffix is not available in Silverlight.)

There are other ways that the XAML markup extension syntax mirrors how .NET attributes are used in C# and VB.NET. For example, notice that, when using the Static extension with the previous XAML element syntax, we needed to specify the value of "Member," whereas in the XAML attribute syntax this was implied.

This was possible because System.Windows.Markup.Static-Ex-tension contains a constructor that takes a single string parameter called "member," and thereby enables a positional means of providing values for a StaticExtension instance. The constructor is declared as public StaticExtension(string member), implying that the first (and only) parameter on the constructor will set the Member property on a StaticExtension instance. In the XAML object element syntax, no positional parameter was provided, so StaticExtension's default constructor is called and a named argument ("Member") explicitly assigned the Member property on the StaticExtension instance.

Here are some additional examples of markup extensions.

First, I show specifying a resource element from a resource dictionary using the StaticResource markup extension (System.Windows.StaticResourceExtension):

<StackPanel>
  <StackPanel.Resources>
    <ResourceDictionary>
      <system:String x:Key="TextBox.Text">
        Hello! My name is Inigo Montoya.</system:String>
    </ResourceDictionary>
  </StackPanel.Resources>
  <TextBox HorizontalAlignment="Left" VerticalAlignment="Top" 
            Name="TextBox" 
            Text="{StaticResource ResourceKey=TextBox.Text}" />
</StackPanel>

Next, setting a property to null using the Null markup extension (System.Windows.Markup.NullExtension):

<StackPanel>
  <StackPanel.Resources>
   <Style x:Key="DefaultTextBox"  
      TargetType="{x:Type TextBox}">
      <Setter Property="Control.Background" Value="Red"/>
    </Style>  
  </StackPanel.Resources>
  <!-- Attribute Usage -->
  <TextBox HorizontalAlignment="Left" VerticalAlignment="Top" 
            Name="TextBox" 
            Text="{x:StaticExtension system:Environment.UserName}"
            Background="{x:Null}" 
            Style="{StaticResource DefaultTextBox}"/>
</StackPanel>

In the previous example, the Background is set to red by the style, but we override that style by explicitly setting it to null using the Null markup extension.

Then there's identifying a type within XAML using the Type markup extension (System.Windows.Markup.TypeExtension). This occurs in the previous example with:

TargetType="{x:Type TextBox}">

Finally, there's creating a collection of values within XAML using the Array markup extension (System.Windows.Markup.Array-Ex-tension), as shown here:

<ListBox>
  <ListBox.ItemsSource>
    <x:Array Type="{x:Type system:String}">
      <system:String>Doc</system:String>
      <system:String>Happy</system:String>
      <system:String>Bashful</system:String>
    </x:Array>
  </ListBox.ItemsSource>
</ListBox>

In this example we use the XAML object element syntax because the ArrayExtension doesn't support XAML attribute syntax, as the elements of the array need to be specified.

Although not shown in the preceding example, one of the most common markup extensions is System.Windows.Data.Binding (which doesn't happen to follow the convention of ending with the "Extension" suffix). We'll save further discussion of Binding for a future UI column, except to point out that XAML with the Binding markup extension frequently uses nested markup extensions. Nested markup extensions are specified by nesting the curly braces within each other, as in:

Content="{Binding Path="Name", RelativeSource="{RelativeSource Self}}" 

As pointed out earlier, markup extensions are an extensibility mechanism within XAML. Therefore, it should be no surprise that it's possible to create custom markup extensions of your own. Simply declare a class that derives from System.Windows.Markup.Markup-Ex-tension and then implement its ProvideValue abstract member. Here's some sample code:

public class UniqueIdExtension : System.Windows.Markup.MarkupExtension
{
  public override object ProvideValue(IServiceProvider serviceProvider)
  {
    return Guid.NewGuid();
  }
}

As with StaticResource, adding a constructor with parameters (in addition to the default constructor) will add support for passing arguments via position, and adding public properties will enable support for named arguments.

Most programmers have learned XAML by example. However, it's not uncommon that they're unaware of markup extensions and the details of how they work. In this article, we were able to highlight these details to not only clarify their use, but also to explain how to create your own custom markup extensions.

About the Author

Mark Michaelis (http://IntelliTect.com/Mark) is the founder of IntelliTect and serves as the Chief Technical Architect and Trainer. Since 1996, he has been a Microsoft MVP for C#, Visual Studio Team System, and the Windows SDK and in 2007 he was recognized as a Microsoft Regional Director. He also serves on several Microsoft software design review teams, including C#, the Connected Systems Division, and VSTS. Mark speaks at developer conferences and has written numerous articles and books - Essential C# 5.0 is his most recent. Mark holds a Bachelor of Arts in Philosophy from the University of Illinois and a Masters in Computer Science from the Illinois Institute of Technology. When not bonding with his computer, Mark is busy with his family or training for another triathlon (having completed the Ironman in 2008). Mark lives in Spokane, Washington, with his wife Elisabeth and three children, Benjamin, Hanna and Abigail.

comments powered by Disqus

Featured

  • Compare New GitHub Copilot Free Plan for Visual Studio/VS Code to Paid Plans

    The free plan restricts the number of completions, chat requests and access to AI models, being suitable for occasional users and small projects.

  • Diving Deep into .NET MAUI

    Ever since someone figured out that fiddling bits results in source code, developers have sought one codebase for all types of apps on all platforms, with Microsoft's latest attempt to further that effort being .NET MAUI.

  • Copilot AI Boosts Abound in New VS Code v1.96

    Microsoft improved on its new "Copilot Edit" functionality in the latest release of Visual Studio Code, v1.96, its open-source based code editor that has become the most popular in the world according to many surveys.

  • AdaBoost Regression Using C#

    Dr. James McCaffrey from Microsoft Research presents a complete end-to-end demonstration of the AdaBoost.R2 algorithm for regression problems (where the goal is to predict a single numeric value). The implementation follows the original source research paper closely, so you can use it as a guide for customization for specific scenarios.

  • Versioning and Documenting ASP.NET Core Services

    Building an API with ASP.NET Core is only half the job. If your API is going to live more than one release cycle, you're going to need to version it. If you have other people building clients for it, you're going to need to document it.

Subscribe on YouTube