XAML markup extensions

Completed

Much of your XAML definition will be settled at compile time. You often know where elements should be positioned, what colors and fonts will be used, and which literal values should be assigned to properties.

However, sometimes you need to set a property value to a value that can't be determined at compile time. These values are only known when the program is running. In these situations, you can create an object that provides a value to XAML at runtime. XAML supports Markup Extensions for this purpose.

In this unit, you'll learn how to create and use markup extensions.

What is a markup extension?

A markup extension is a class that you use in XAML to access runtime values. Suppose you have many labels defined in your XAML UI, and you want to set the FontSize property to the same value throughout the app to ensure that all label styling is consistent. You can set the FontSize property using XAML as shown in the following example:

<Label Text="Hello, World!"
            Grid.Row="0"
            SemanticProperties.HeadingLevel="Level1"
            FontSize="28"
            HorizontalOptions="CenterAndExpand"/>

You can repeat this same setting for every label, but what if you want to change this value later? You need find every instance of this property and make the change. Also, suppose you don't know what value to use; it could be calculated at runtime based on factors such as device orientation, screen resolution, or other considerations. In these cases, you need something more sophisticated than a hard-coded literal. This is where a markup extension is useful. Markup extensions give you flexibility in how you obtain a value that gets used in XAML.

Creating a markup extension

A markup extension is a class that implements the Microsoft.Maui.Controls.Xaml.IMarkupExtension interface. This interface defines one method, named ProvideValue, with the following signature:

public object ProvideValue(IServiceProvider serviceProvider)
{
    ...
}

This method's purpose is to supply a value to your XAML markup. Notice that the return type is object, so the value can be of any type as long as it's appropriate to the place in which it's being used. For example, in a markup extension that calculates and returns a font size, the return type should be a double.

The serviceProvider parameter contains contextual information about where the markup extension is being used in your XAML code; among other pieces of information, it identifies the control to which the extension is being applied.

You can keep the markup extension for the FontSize property simple. In the following example, the MainPage class exposes a double field named MyFontSize. The GlobalFontSizeExtension class implements the IMarkupExtension interface, and the ProvideValue method returns the value of MyFontSize variable:

namespace MyMauiApp;

public partial class MainPage : ContentPage
{
    public const double MyFontSize = 28;

    public MainPage()
    {
        InitializeComponent();
        ...
    }
    ...
}

public class GlobalFontSizeExtension : IMarkupExtension
{
    public object ProvideValue(IServiceProvider serviceProvider)
    {
        return MainPage.MyFontSize;
    }
}

Note

The MyFontSize field must be a static member of the MainPage class to allow it to be referenced in the ProvideValue method in this way. Good practice implies that in this case, the variable should also be a constant. A const value is static.

The ProvideValue method could also make adjustments to the value returned, depending on the orientation and device form factor.

Applying the markup extension to a control in XAML

To use the markup extension in your XAML code, add the namespace containing the GlobalFontSizeExtension class to the list of namespaces in the ContentPage tag. In the following example, this namespace is given the alias mycode:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:mycode="clr-namespace:MyMauiApp"
             x:Class="MyMauiApp.MainPage">

You can use the markup extension to set the FontSize property like this. Note the convention is that a markup extension has the suffix Extension in its name. XAML recognizes this suffix, and you don't need to include it when you call the extension from your XAML code. In the following example, the GlobalFontSizeExtension class is referenced simply as GlobalFontSize:

<Label Text="Hello, World!"
            Grid.Row="0"
            SemanticProperties.HeadingLevel="Level1"
            FontSize="{mycode:GlobalFontSize}"
            HorizontalOptions="CenterAndExpand"/>

You can apply the same markup extension throughout your XAML code for any control that needs to specify the font size. Later, if you decide to change the font size, you only need to modify the definition of the MyFontSize variable in the MainPage class.

The StaticExtension class

As useful as the GlobalFontSize markup extension is, it's unlikely that you'd create such an extension. The reason for this is simple; .NET MAUI already provides a more generalized extension that allows you to reference any static value in your code. This extension is named StaticExtension, or Static for short. The following code shows this extension class's basic outline:

[ContentProperty ("Member")]
public class StaticExtension : IMarkupExtension
{
    public string Member {get; set;}
    public object ProvideValue (IServiceProvider serviceProvider)
    {
        ...
    }
}

Note

The purpose of custom markup extensions is to allow you to handle more complex situations rather than the simple static case. For example, you might need to dynamically change the font size based on device form factor.

To use this class in your XAML code, provide the name of the static variable you want to reference in the Member property, and the ProvideValue method returns value in this variable. The following example illustrates how to use it:

<Label Text="Hello, World!"
            Grid.Row="0"
            SemanticProperties.HeadingLevel="Level1"
            FontSize="{x:Static Member=mycode:MainPage.MyFontSize}"
            HorizontalOptions="CenterAndExpand"/>

.NET MAUI provides a set of other markup extension classes, which you can use for scenarios such as data binding, referencing dynamic resources and styles, and handling arrays of data.

Knowledge check

1.

Which markup extension allows you to set a XAML property to a static value that's defined in a code-behind class?

2.

Which interface do you use to create a custom markup extension?