Create your own template

Introduction

If you have some code, that will most likely repeat in several different pages, you might want to make a template from it. You want to make it as convenient as possible, that's why it's important to have constistant templates, that you can easily use just by looking at its code.

Here will be explained creation of 3 files:

Main directory

The very first thing you want to do is create a directory for template in /Templates

.xaml template

First of all, template creation is a bit different from page creation. Two tags are needed as the root:

<?xml version="1.0" encoding="utf-8" ?>
<ResourceDictionary xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
                    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                    x:Class="|PathToNamespace|">
    <ControlTemplate x:Key="|TemplateName|">
        <!-- Content will be here -->
    </ControlTemplate>
</ResourceDictionary>

You can treat this as just ContentPage tag after the template will be applied. The only issue, that can occur is that it supports different properties.

Next, put your page content in it and define changeable content blocks using this:

<ContentPresenter Content="{TemplateBinding |ContentBlockName|}" />

{TemplateBinding |ContentBlockName|} just gets content from class property, that we will create later. |ContentBlockName| is your variable's name. It is recommended to create name ending in -Content, like MainContent, PopupContent.

For example you might get this:

<?xml version="1.0" encoding="utf-8" ?>
<ResourceDictionary xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
                    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                    xmlns:navigationbar="clr-namespace:PlutoFramework.Components.NavigationBar"
                    xmlns:txanalyzer="clr-namespace:PlutoFramework.Components.TransactionAnalyzer"
                    xmlns:password="clr-namespace:PlutoFramework.Components.Password"
                    x:Class="PlutoFramework.Templates.PageTemplate.Page">

    <ControlTemplate x:Key="PageTemplate">
        <AbsoluteLayout AbsoluteLayout.LayoutBounds="0.5, 0.5, 1, 1"
                        AbsoluteLayout.LayoutFlags="All">

            <Image AbsoluteLayout.LayoutBounds="0.5, 0.5, 1, 1"
                   AbsoluteLayout.LayoutFlags="All"
                   Source="{AppThemeBinding Light=whitebackground.png, Dark=darkbackground.png}"
                   Opacity="{AppThemeBinding Light=0.96}"
                   Aspect="AspectFill"/>

            <ScrollView AbsoluteLayout.LayoutBounds="0.5, 0.5, 1, 1"
                        AbsoluteLayout.LayoutFlags="All"
                        Padding="0, 55, 0, 0">
                <!-- Content block -->
                <ContentPresenter Content="{TemplateBinding MainContent}" />
            </ScrollView>

            <navigationbar:TopNavigationBar Title="{TemplateBinding Title}" />
            <txanalyzer:TransactionAnalyzerConfirmationView />

            <!-- Popups block -->
            <ContentPresenter Content="{TemplateBinding PopupContent}" />

            <password:EnterPasswordPopupView />
        </AbsoluteLayout>
    </ControlTemplate>
</ResourceDictionary>

.xaml.cs file

As the root tag is <ResourceDictionary> , we can't make a class, that inherits from <ContentPage> . For that reason there's nothing hard here, just make a dummy class for .xaml:

namespace PlutoFramework.Templates.PageTemplate;

public partial class Page : ResourceDictionary
{
	public Page()
	{
        InitializeComponent();
	}
}

TemplateDictionary.xaml

You might've noticed, that there is one file, that isn't in any directory, when you created a directory in /Templates. That's not a mistake, it is a special file, that has a list of all templates in it.

Now you have to add your newly created template, so after that we will continue to creating class for the template.

In file TemplateDictionary.xaml, add namespace of your template and put template in there:

<?xml version="1.0" encoding="utf-8" ?>
<ResourceDictionary xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
                    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                    xmlns:pagetemplate="clr-namespace:PlutoFramework.Templates.PageTemplate"
                    x:Class="PlutoFramework.Templates.TemplateDictionary">
    <ResourceDictionary.MergedDictionaries>
        <pagetemplate:Page />
    </ResourceDictionary.MergedDictionaries>
</ResourceDictionary>

Here it is added as xmlns:pagetemplate and then Page class used as tag.

.cs file

Moving on to the hardest part: creating class, that will manage our template.

Firstly, make namespace and class:

namespace PlutoFramework.Templates.PageTemplate
{
    public class PageTemplate : ContentPage
    { 
        public PageTemplate()
        {
        }
    }
}

Next, to connect the class with template, set ControlTemplate in class' constructor:

namespace PlutoFramework.Templates.PageTemplate
{
    public class PageTemplate : ContentPage
    { 
        public PageTemplate()
        {
            ControlTemplate = (ControlTemplate)Application.Current.Resources["PageTemplate"];
        }
    }
}

We can do this because we've added template to Resources in previous step and that template has x:Key property set (so we can reference it like Resources[<x:Key>]).

Now, you have to add BindableProperty and configure it for each content block. But first, add alias for Microsoft.Maui.Controls.View, because .net MAUI requires to have View directory, that overwrites it with directory's namespace:

using MauiView = Microsoft.Maui.Controls.View;

Now you can add variable for content and BindableProperty for each content block like this:

public MauiView MainContent
{
    get => (MauiView)GetValue(MainContentProperty);
    set => SetValue(MainContentProperty, value);
}
public static readonly BindableProperty MainContentProperty =
    BindableProperty.Create(nameof(MainContent), typeof(MauiView), typeof(PageTemplate), default(MauiView));

Which leaves us with the following code:

using MauiView = Microsoft.Maui.Controls.View;

namespace PlutoFramework.Templates.PageTemplate
{
    public class PageTemplate : ContentPage
    {
        public MauiView MainContent
        {
            get => (MauiView)GetValue(MainContentProperty);
            set => SetValue(MainContentProperty, value);
        }
        public MauiView PopupContent
        {
            get => (MauiView)GetValue(PopupContentProperty);
            set => SetValue(PopupContentProperty, value);
        }

        public static readonly BindableProperty MainContentProperty =
            BindableProperty.Create(nameof(MainContent), typeof(MauiView), typeof(PageTemplate), default(MauiView));

        public static readonly BindableProperty PopupContentProperty =
            BindableProperty.Create(nameof(PopupContent), typeof(MauiView), typeof(PageTemplate), default(MauiView));
        

        public PageTemplate()
        {
            ControlTemplate = (ControlTemplate)Application.Current.Resources["PageTemplate"];
        }
    }
}

And as a last tweak, if you want to set main content block, which can be used without tag, directly in root tag, you can set corresponding property's name as content property by appending [ContentProperty(nameof(MainContent))] before class declaration.

Now you can add all methods needed and everything needed in constructor, but make sure for ControlTemplate = ... to be the first in constructor.

And with this, finished code looks like this:

using MauiView = Microsoft.Maui.Controls.View;

namespace PlutoFramework.Templates.PageTemplate
{
    [ContentProperty(nameof(MainContent))]
    public class PageTemplate : ContentPage
    {
        public MauiView MainContent
        {
            get => (MauiView)GetValue(MainContentProperty);
            set => SetValue(MainContentProperty, value);
        }
        public MauiView PopupContent
        {
            get => (MauiView)GetValue(PopupContentProperty);
            set => SetValue(PopupContentProperty, value);
        }

        public static readonly BindableProperty MainContentProperty =
            BindableProperty.Create(nameof(MainContent), typeof(MauiView), typeof(PageTemplate), default(MauiView));

        public static readonly BindableProperty PopupContentProperty =
            BindableProperty.Create(nameof(PopupContent), typeof(MauiView), typeof(PageTemplate), default(MauiView));
        

        public PageTemplate()
        {
            ControlTemplate = (ControlTemplate)Application.Current.Resources["PageTemplate"];
            
            // Other calls, not needed in every template
            NavigationPage.SetHasNavigationBar(this, false);
            Shell.SetNavBarIsVisible(this, false);
        }
    }
}

Now you can use your very own template - Using already existing templates

Last updated