Skip to content
Home » Basics of .NET MAUI – Part 12 – Markup Extensions

Basics of .NET MAUI – Part 12 – Markup Extensions

Spread the love

In the preceding parts of the series we were already using markup extensions. You can easily spot them because with most of them (but not all) we use curly braces. Here’s an example from the SettingsPage.xaml file:

Style="{StaticResource labelSectionTitleStyle}"

This is the StaticResources markup extension. We use it here to set the Style property. StaticResource is just one example of markup extensions. In this part of the series we’re going to see some more.

The source code for this article is available on Github.

So, what are markup  extensions and what do we use them for? Well, we use them to set properties to objects or values defined somewhere else, like in the example above, where the labelSectionTitleStyle is referenced. Markup extensions are used to share objects and values, to reference constants and to bind data. As far as data binding is concerned, we’re going to see to it in a separate part of the series. And now let’s have a look at the other use cases.

Shared Resources and the StaticResource Markup Extension

We’ve been using the StaticResource markup extension to set styles so far. But if you look at the XAML code where the styles are defined, you’ll notice that there are some values that are repeated multiple times. In C# code we would probably use constants to store these values. In XAML we can define them as shared resources inside a ResourceDictionary.

Let’s start with the colors. Here’s how the colors are defined inside the implicit Entry style in the App.xaml file:

<?xml version = "1.0" encoding = "UTF-8" ?>
<Application ...>
    <Application.Resources>
        <ResourceDictionary>
            ...
            <Style TargetType="Entry">
                <Setter Property="BackgroundColor" Value="White" />
                ...
                <Setter Property="PlaceholderColor" Value="#A0A0A0" />                
                <Setter Property="VisualStateManager.VisualStateGroups">
                    <VisualStateGroupList>
                        <VisualStateGroup x:Name="ValidityStates">
                            <VisualState x:Name="Valid">
                                <VisualState.Setters>
                                    <Setter Property="BackgroundColor" Value="White" />
                                    <Setter Property="TextColor" Value="Black" />
                                </VisualState.Setters>
                            </VisualState>
                            <VisualState x:Name="Invalid">
                                <VisualState.Setters>
                                    <Setter Property="BackgroundColor" Value="#FFDDEE" />
                                    <Setter Property="TextColor" Value="Red" />
                                </VisualState.Setters>
                            </VisualState>
                            <VisualState x:Name="Empty">
                                <VisualState.Setters>
                                    <Setter Property="BackgroundColor" Value="White" />
                                    <Setter Property="TextColor" Value="White" />
                                    ...
                                </VisualState.Setters>
                            </VisualState>
                        </VisualStateGroup>
                    </VisualStateGroupList>
                </Setter>
            </Style>
            ...

Here we can see colors assigned to different properties, like BackgroundColor or TextColor. Some of the values are repeated, like the named color White. This is just a small portion of the code, but there are more repeated values throughout the entire file. If we now wanted to change all white elements to blue ones, we would have to set the properties one by one. We should avoid such situations, so let’s define a color resource that we can share. Actually, let’s define color resources for all repeated colors in the ResourceDictionary:

<?xml version = "1.0" encoding = "UTF-8" ?>
<Application ...>
    <Application.Resources>
        <ResourceDictionary>            
            <ResourceDictionary.MergedDictionaries>
                ...
            </ResourceDictionary.MergedDictionaries>

            <!--Colors-->
            <Color x:Key="mainButtonColor">#520000</Color>
            <Color x:Key="buttonTextColor">#FFCC1A</Color>
            <Color x:Key="pressedButtonColor">#891C20</Color>
            <Color x:Key="normalEntryColor">White</Color>

            <Style TargetType="ContentPage"
                   ...

Then we can set the colors using the StaticResource markup extension and the names defined in the ResourceDictionary:

<?xml version = "1.0" encoding = "UTF-8" ?>
<Application ...>
    ...
            <Style TargetType="Border">
                <Setter Property="Stroke" Value="{StaticResource mainButtonColor}" />
                ...
            <Style TargetType="Button">
                <Setter Property="BackgroundColor" Value="{StaticResource mainButtonColor}" />
                <Setter Property="TextColor" Value="{StaticResource buttonTextColor}" />
                ...
                    <VisualStateGroupList>
                        ...
                            <VisualState x:Name="PointerOver">
                                <VisualState.Setters>
                                    <Setter Property="BackgroundColor" Value="{StaticResource pressedButtonColor}" />
                                </VisualState.Setters>
                            </VisualState>

                            <VisualState x:Name="Pressed">
                                <VisualState.Setters>
                                    ...
                                    <Setter Property="BackgroundColor" Value="{StaticResource pressedButtonColor}" />
                                </VisualState.Setters>
                            </VisualState>
                        </VisualStateGroup>
                    </VisualStateGroupList>
                ...
            <Style TargetType="Entry">
                <Setter Property="BackgroundColor" Value="{StaticResource normalEntryColor}" />
                ...
                    <VisualStateGroupList>
                        <VisualStateGroup x:Name="ValidityStates">
                            <VisualState x:Name="Valid">
                                <VisualState.Setters>
                                    <Setter Property="BackgroundColor" Value="{StaticResource normalEntryColor}" />
                                    ...
                                </VisualState.Setters>
                            </VisualState>
                            ...
                            <VisualState x:Name="Empty">
                                <VisualState.Setters>
                                    <Setter Property="BackgroundColor" Value="{StaticResource normalEntryColor}" />
                                    <Setter Property="TextColor" Value="{StaticResource normalEntryColor}" />
                                    ...
                                </VisualState.Setters>
                            </VisualState>
                        </VisualStateGroup>
                    </VisualStateGroupList>
                ...
            <ControlTemplate x:Key="RadioButtonTemplate">                
                ...
                        <Ellipse
                                Fill="{StaticResource mainButtonColor}"
                                ...
                        <Ellipse
                                ...
                                Fill="{StaticResource buttonTextColor}"
                                ...
                    </Grid>
                    ...

We could put all the colors in the ResourceDictionary to keep them centralized, but here I limited myself to the colors that are repeated at least twice in the code.

So, this is how we can share color values across the app. But we can also share other values, like layout options or numeric values, for example. For the latter we can use tags like <x:Double> or <x:Int32>. Let’s now put all repeated values in the ResourceDictionary and then reference them using the StaticResource markup extension and their names:

<?xml version = "1.0" encoding = "UTF-8" ?>
<Application ...>
    <Application.Resources>
        <ResourceDictionary>            
            ...
            <!--Colors-->
            ...
            <!--Layout options-->
            <LayoutOptions x:Key="defaultLayoutOptions" Alignment="Center" />
            
            <!--Numeric values-->
            <x:Double x:Key="defaultFontSize">18</x:Double>
            
            <!--Other values-->
            <FontAttributes x:Key="emphasized">Bold</FontAttributes>

            <Style TargetType="ContentPage"
                   ...
            <Style TargetType="Button">
                ...
                <Setter Property="FontSize" Value="{StaticResource defaultFontSize}" />
                <Setter Property="FontAttributes" Value="{StaticResource emphasized}" />
                <Setter Property="HorizontalOptions" Value="{StaticResource defaultLayoutOptions}" />
                ...
            <Style x:Key="labelBaseStyle" TargetType="Label">
                <Setter Property="FontSize" Value="{StaticResource defaultFontSize}" />
                <Setter Property="VerticalOptions" Value="{StaticResource defaultLayoutOptions}" />
            </Style>
            
            <Style x:Key="labelSectionTitleStyle" 
                   TargetType="Label"
                   BasedOn="{StaticResource labelBaseStyle}">
                <Setter Property="FontSize" Value="20" />
                <Setter Property="FontAttributes" Value="{StaticResource emphasized}" />
            </Style>

            <Style TargetType="Entry">
                ...
                <Setter Property="FontSize" Value="{StaticResource defaultFontSize}" />
                ...
                <Setter Property="FontAttributes" Value="{StaticResource emphasized}" />
                ...
            <ControlTemplate x:Key="RadioButtonTemplate">                
                <FlexLayout JustifyContent="Start">
                    <FlexLayout.Resources>
                        <Style TargetType="Label">
                            <Setter Property="FontSize" Value="{StaticResource defaultFontSize}" />
                            <Setter Property="VerticalOptions" Value="{StaticResource defaultLayoutOptions}" />
                            ...
                        </Style>
                    </FlexLayout.Resources>
                    <Grid     
                        ...
                        HorizontalOptions="{StaticResource defaultLayoutOptions}"
                        VerticalOptions="{StaticResource defaultLayoutOptions}">
                        <Ellipse
                                ...
                                HorizontalOptions="{StaticResource defaultLayoutOptions}"
                                VerticalOptions="{StaticResource defaultLayoutOptions}" />
                        <Ellipse
                                ...
                                HorizontalOptions="{StaticResource defaultLayoutOptions}"
                                VerticalOptions="{StaticResource defaultLayoutOptions}" />
                    </Grid>
                    ...

Again, we could put all the values in the ResourceDictionary and then reference them, but I only put there those that are repeated throughout the code. If you run the code now, everything will look exactly as before.

The resources defined in the App.xaml file are available everywhere throughout the app. So, for example, the ThumbColor and MinimumTrackColor properties in the Bets content view are set to the same value as the buttons and borders. We defined the color as mainButtonColor, so let’s use it now. Here’s the Bets.xaml file:

<?xml version="1.0" encoding="utf-8" ?>
<ContentView ...>

    <ContentView.Resources>
        ...
        <Style TargetType="Slider">
            <Setter Property="ThumbColor" Value="{StaticResource mainButtonColor}" />
            <Setter Property="MinimumTrackColor" Value="{StaticResource mainButtonColor}" />
        </Style>
    ...

We can also use some of the other resources that we defined in App.xaml in other pages and content views.  For example, in the GameOverPage we can set the FontSize and HorizontalOptions properties like so:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage ...>

    <ContentPage.Resources>
                
        <Style TargetType="Label">
            <Setter Property="FontAttributes" Value="{StaticResource emphasized}" />
            <Setter Property="HorizontalOptions" Value="{StaticResource defaultLayoutOptions}" />
            ...

Or, in the SettingsPage, we can set FontAttributes and VerticalOptions like this:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage ...>    
    ...
        <!--the Settings label-->
        <Label 
            Text="Settings"
            FontAttributes="{StaticResource emphasized}"
            FontSize="24" />

        <!--the Players panel-->
        ...
                <VerticalStackLayout VerticalOptions="{StaticResource defaultLayoutOptions}">                              
                    <Label
                        Text="The Players"
                        Style="{StaticResource labelSectionTitleStyle}" />
                    ...
        <!--the Ending Conditions panel-->
        <Border
            Grid.Row="2">
            <VerticalStackLayout VerticalOptions="{StaticResource defaultLayoutOptions}">
                ...

There are also values that are repeated inside one particular page or content view. In such cases, we can define the resources locally in the ResourceDectionary inside that page or view. As we’re at the SettingsPage now, let’s start there. A couple values are repeated, like for example WidthRequest, which is set to 300, and Opacity, which is set to 0. Let’s turn them into resources and then reference them:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage ...>    

    <ContentPage.Resources>
        <x:Double x:Key="entryWidth">300</x:Double>
        <x:Double x:Key="invisible">0</x:Double>
        
        <Style TargetType="RadioButton">
            <Setter Property="Margin" Value="0, 0, 0, 10" />
        </Style>
    </ContentPage.Resources>    
    ...
        <!--the Ending Conditions panel-->
        ...
                    <Entry
                        ...
                        WidthRequest="{StaticResource entryWidth}"
                        HorizontalOptions="Start"                         
                        Opacity="{StaticResource invisible}"
                        .../>
                    ...
                    <Entry
                        ...
                        WidthRequest="{StaticResource entryWidth}" 
                        HorizontalOptions="Start" 
                        Opacity="{StaticResource invisible}"
                        .../>                    
                </Grid>
            ...

In the SlugInfo content view we both define a view-scoped resource and reuse some resources defined globally:

<?xml version="1.0" encoding="utf-8" ?>
<ContentView ...>
    
    <ContentView.Resources>
        <Color x:Key="infoTextColor">White</Color>
    </ContentView.Resources>

    <Grid 
        ...
        <Label 
            ...
            FontAttributes="{StaticResource emphasized}"
            TextColor="{StaticResource infoTextColor}" />
        <Label 
            ...
            TextColor="{StaticResource infoTextColor}" />
        <Label 
            ...
            VerticalOptions="{StaticResource defaultLayoutOptions}"
            ...
            FontAttributes="{StaticResource emphasized}"
            TextColor="{StaticResource infoTextColor}" />
    </Grid>    
</ContentView>

In all the examples above we were using the StaticResource markup extension. Let’s now have a look at some others. Let’s start with the x:Static markup extension.

The x:Static Markup Extension

The x:Static markup extension is used to reference a public static field, a public static property, a public constant field or an enumeration member that are defined somewhere in the code. We’re not going to use it in our app, so let’s use the TestPage to demonstrate it. But first of all, move the TestPage.xaml and TestPage.xaml.cs files to the Views folder so that we can use it as the starting page in AppShell.xaml without any additional modifications. This will require a few changes in the files so that the correct namespaces are defined.

Now make sure the TestPage is the starting page. Then open the TestPage.xaml file and make sure it looks like this:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="Slugrace.Views.TestPage"
             Title="TestPage">
    <Grid>
        <BoxView 
            Color="Red"
            WidthRequest="600"
            HeightRequest="600" />        
    </Grid>
</ContentPage>

So, here we have a BoxView control with some basic settings. Now open the code-behind and make sure it looks like this:

namespace Slugrace.Views;
public partial class TestPage : ContentPage
{    
    public TestPage()
    {
        InitializeComponent();        
    }
}

Watch the namespace. It’s Slugrace.Views, not Slugrace like before, because we moved the file into the Views folder.

If you run the app, you will just see a red box:

We set three properties on the view using literal values. Instead, we could create some resources and reference them using the StaticResource markup extension. But we can also define some static fields or properties somewhere in the code and then reference them using the x:Static extension. Let’s temporarily add a field and a property to the Helpers class that is defined inside the Helpers.cs file in the root of our app:

namespace Slugrace
{
    public static class Helpers
    {
        public static readonly double BoxSize = 600;
        public static Color BoxColor { get; set; } = Colors.Red;

        public static void ValidateNumericInputAndSetState(...)
        ...

In order to be able to reference these static values in the TestPage, we must add a namespace with a prefix there:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:Slugrace"
             x:Class="Slugrace.Views.TestPage"
             Title="TestPage">
    <Grid>
        ...

This way the program will know where to look for the values. Don’t worry if you don’t understand how it works. We’re going to discuss namespaces in XAML in one of the following parts of the series.

Anyway, now we can reference the field and property like so:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage ...>
    <Grid>
        <BoxView 
            Color="{x:Static local:Helpers.BoxColor}"
            WidthRequest="{x:Static local:Helpers.BoxSize}"
            HeightRequest="{x:Static local:Helpers.BoxSize}" />        
    </Grid>
</ContentPage>

If you run the app, it will work like before. And now let’s see how we can use enumeration members. Modify the XAML code so that it looks like this:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage ...>
    <Grid>
        <BoxView 
            WidthRequest="400"
            HeightRequest="400"
            Color="{x:Static Colors.Red}"
            VerticalOptions="{x:Static LayoutOptions.Start}"
            HorizontalOptions="{x:Static LayoutOptions.Center}"/>        
    </Grid>
</ContentPage>

As you can see, we’re using the x:Static markup extension to set the Color, VerticalOptions and HorizontalOptions properties.

When you’re done experimenting, remove the field and property from the Helpers class because we don’t want to clutter it with stuff we won’t be using in the app. And now let’s move on to the next markup extensions.

Markup Extension Properties

Markup extensions can have properties. If there are more than one, they’re separated with commas. We assign values to the properties using equals signs and we don’t use quotation marks inside the curly braces. Here’s an example of the x:Static markup extension that we used in our code:

Color="{x:Static local:Helpers.BoxColor}"

We don’t clearly see the property name here, just the value. This is because it’s the value of the content property, and the name of a content property can be omitted provided it’s the first or the only property. The content property of the x:Static markup extension is Member, so we could rewrite the code like this:

Color="{x:Static Member=local:Helpers.BoxColor}"

There may be only one content property.

CollectionView, the x:Array and x:Type Markup Extensions

Unlike our Slugrace application, many apps display data retrieved from a list or other collection, sometimes from the disk, sometimes from an API, sometimes the data is hardcoded in the program. In .NET MAUI we have a couple controls that we can use to display such data. One of them is ListView. The recommended one, however, is CollectionView. It’s more flexible and performant than ListView.

We’re going to use the TestPage.xaml file again, because the collection that we are about to see will not be part of our app. To create a CollectionView, we must provide two pieces of information to the object: the collection itself and the template for a single item in the collection. We use the ItemsSource property to populate the view. To define the appearance of each item in the collection, we set the ItemTemplate property to DataTemplate.

Let’s create a CollectionView in the TestPage.xaml file then so that we have something to look at as we discuss the details. Make sure your code looks like this:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage ...>
    <CollectionView Margin="50">
        <CollectionView.ItemsSource>
            <x:Array Type="{x:Type x:Int32}">
                <x:Int32>128</x:Int32>
                <x:Int32>526</x:Int32>
                <x:Int32>311</x:Int32>
                <x:Int32>781</x:Int32>
                <x:Int32>982</x:Int32>
            </x:Array>
        </CollectionView.ItemsSource>
        <CollectionView.ItemTemplate>
            <DataTemplate>
                <Border HeightRequest="100" WidthRequest="500">
                    <Grid>
                        <Image
                            Source="racetrack.png"
                            Scale="1"/>
                        <Button 
                            Text="{Binding}" 
                            FontSize="20" />
                    </Grid>                    
                </Border>                                           
            </DataTemplate>
        </CollectionView.ItemTemplate>
    </CollectionView>
</ContentPage>

You can clearly see the two properties in the code. First, we set the ItemsSource property to an array of integer numbers and then we set the ItemTemplate property to a DataTemplate which defines each individual item in the array. Let’s start with the ItemsSource property.

In the example above we hardcoded the items of the array. We use the x:Array markup extension to define an array in XAML. Unlike with all the other markup extensions that we discussed before, with the x:Array extension we don’t use curly braces, but rather the items are listed between the opening and closing tags.

And here comes x:Type, another markup extension that we often use with the x:Array extension. This one is used with curly braces. Here it’s used to define the type of the items. So, our CollectionView contains five integer numbers. These could be strings, colors, images or any other objects.

Next, we must define the template for a single item. This is what DataTemplate is for. We could present the data as labels, as images with text, as custom content views, or anything else. Here each item is displayed inside a border and consists of an image with a button on it. The Text property of the button is set to the appropriate number from the array. As you can see, we’re using yet another markup extension to set the Text property, Binding. It just binds the Text property to each item from the collection one by one. We’re not going to discuss it in more detail here, because there’s going to be a separate part of the series about data binding.

Now let’s run the app (with the TestPage set as the starting page) and we should see the five items displayed like this:

Naturally, there are more markup extensions, but we’ve covered the most important ones. Now that our app looks so great… But wait, does it? That’s the problem. We’ve been running it exclusively on Windows, but what about Android? Does it look as great on your cell phone? And if not, what can we do about it? In the next part of the series we’ll be talking about platform differences.


Spread the love

Leave a Reply