Muokkaa

Jaa


Control templates

Browse sample. Browse the sample

.NET Multi-platform App UI (.NET MAUI) control templates enable you to define the visual structure of ContentView derived custom controls, and ContentPage derived pages. Control templates separate the user interface (UI) for a custom control, or page, from the logic that implements the control or page. Additional content can also be inserted into the templated custom control, or templated page, at a pre-defined location.

For example, a control template can be created that redefines the UI provided by a custom control. The control template can then be consumed by the required custom control instance. Alternatively, a control template can be created that defines any common UI that will be used by multiple pages in an app. The control template can then be consumed by multiple pages, with each page still displaying its unique content.

Create a ControlTemplate

The following example shows the code for a CardView custom control:

public class CardView : ContentView
{
    public static readonly BindableProperty CardTitleProperty =
        BindableProperty.Create(nameof(CardTitle), typeof(string), typeof(CardView), string.Empty);
    public static readonly BindableProperty CardDescriptionProperty =
        BindableProperty.Create(nameof(CardDescription), typeof(string), typeof(CardView), string.Empty);

    public string CardTitle
    {
        get => (string)GetValue(CardTitleProperty);
        set => SetValue(CardTitleProperty, value);
    }

    public string CardDescription
    {
        get => (string)GetValue(CardDescriptionProperty);
        set => SetValue(CardDescriptionProperty, value);
    }
    ...
}

The CardView class, which derives from the ContentView class, represents a custom control that displays data in a card-like layout. The class contains properties, which are backed by bindable properties, for the data it displays. However, the CardView class does not define any UI. Instead, the UI will be defined with a control template. For more information about creating ContentView derived custom controls, see ContentView.

A control template is created with the ControlTemplate type. When you create a ControlTemplate, you combine View objects to build the UI for a custom control, or page. A ControlTemplate must have only one View as its root element. However, the root element usually contains other View objects. The combination of objects makes up the control's visual structure.

While a ControlTemplate can be defined inline, the typical approach to declaring a ControlTemplate is as a resource in a resource dictionary. Because control templates are resources, they obey the same scoping rules that apply to all resources. For example, if you declare a control template in your app-level resource dictionary, the template can be used anywhere in your app. If you define the template in a page, only that page can use the control template. For more information about resources, see Resource dictionaries.

The following XAML example shows a ControlTemplate for CardView objects:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             ...>
    <ContentPage.Resources>
      <ControlTemplate x:Key="CardViewControlTemplate">
          <Border BindingContext="{Binding Source={RelativeSource TemplatedParent}}"
                  BackgroundColor="{Binding CardColor}"
                  Stroke="{Binding BorderColor}"
                 ...>
              <!-- UI objects that define the CardView visual structure -->
          </Border>
      </ControlTemplate>
    </ContentPage.Resources>
    ...
</ContentPage>

When a ControlTemplate is declared as a resource, it must have a key specified with the x:Key attribute so that it can be identified in the resource dictionary. In this example, the root element of the CardViewControlTemplate is a Border object. The Border object uses the RelativeSource markup extension to set its BindingContext to the runtime object instance to which the template will be applied, which is known as the templated parent. The Border object uses a combination of controls to define the visual structure of a CardView object. The binding expressions of these objects resolve against CardView properties, due to inheriting the BindingContext from the root Border element. For more information about the RelativeSource markup extension, see Relative bindings.

Consume a ControlTemplate

A ControlTemplate can be applied to a ContentView derived custom control by setting its ControlTemplate property to the control template object. Similarly, a ControlTemplate can be applied to a ContentPage derived page by setting its ControlTemplate property to the control template object. At runtime, when a ControlTemplate is applied, all of the controls that are defined in the ControlTemplate are added to the visual tree of the templated custom control, or templated page.

The following example shows the CardViewControlTemplate being assigned to the ControlTemplate property of two CardView objects:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:controls="clr-namespace:ControlTemplateDemos.Controls"
             ...>
    <StackLayout Margin="30">
        <controls:CardView BorderColor="DarkGray"
                           CardTitle="John Doe"
                           CardDescription="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla elit dolor, convallis non interdum."
                           IconBackgroundColor="SlateGray"
                           IconImageSource="user.png"
                           ControlTemplate="{StaticResource CardViewControlTemplate}" />
        <controls:CardView BorderColor="DarkGray"
                           CardTitle="Jane Doe"
                           CardDescription="Phasellus eu convallis mi. In tempus augue eu dignissim fermentum. Morbi ut lacus vitae eros lacinia."
                           IconBackgroundColor="SlateGray"
                           IconImageSource="user.png"
                           ControlTemplate="{StaticResource CardViewControlTemplate}" />
    </StackLayout>
</ContentPage>

In this example, the controls in the CardViewControlTemplate become part of the visual tree for each CardView object. Because the root Border object for the control template sets its BindingContext to the templated parent, the Border and its children resolve their binding expressions against the properties of each CardView object.

The following screenshot shows the CardViewControlTemplate applied to the the CardView objects:

Screenshot of two templated CardView objects.

Important

The point in time that a ControlTemplate is applied to a control instance can be detected by overriding the OnApplyTemplate method in the templated custom control, or templated page. For more information, see Get a named element from a template.

Pass parameters with TemplateBinding

The TemplateBinding markup extension binds a property of an element that is in a ControlTemplate to a public property that is defined by the templated custom control or templated page. When you use a TemplateBinding, you enable properties on the control to act as parameters to the template. Therefore, when a property on a templated custom control or templated page is set, that value is passed onto the element that has the TemplateBinding on it.

Important

The TemplateBinding markup expression enables the RelativeSource binding from the previous control template to be removed, and replaces the Binding expressions.

The TemplateBinding markup extension defines the following properties:

The ContentProperty for the TemplateBinding markup extension is Path. Therefore, the "Path=" part of the markup extension can be omitted if the path is the first item in the TemplateBinding expression. For more information about using these properties in a binding expression, see Data binding.

Warning

The TemplateBinding markup extension should only be used within a ControlTemplate. However, attempting to use a TemplateBinding expression outside of a ControlTemplate will not result in a build error or an exception being thrown.

The following XAML example shows a ControlTemplate for CardView objects, that uses the TemplateBinding markup extension:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             ...>
    <ContentPage.Resources>
        <ControlTemplate x:Key="CardViewControlTemplate">
            <Border BackgroundColor="{TemplateBinding CardColor}"
                    Stroke="{TemplateBinding BorderColor}"
                   ...>
                <!-- UI objects that define the CardView visual structure -->                   
            </Border>
        </ControlTemplate>
    </ContentPage.Resources>
    ...
</ContentPage>

In this example, the TemplateBinding markup extension resolves binding expressions against the properties of each CardView object. The following screenshot shows the CardViewControlTemplate applied to the CardView objects:

Screenshot of templated CardView objects.

Important

Using the TemplateBinding markup extension is equivalent to setting the BindingContext of the root element in the template to its templated parent with the RelativeSource markup extension, and then resolving bindings of child objects with the Binding markup extension. In fact, the TemplateBinding markup extension creates a Binding whose Source is RelativeBindingSource.TemplatedParent.

Apply a ControlTemplate with a style

Control templates can also be applied with styles. This is achieved by creating an implicit or explicit style that consumes the ControlTemplate.

The following XAML example shows an implicit style that consumes the CardViewControlTemplate:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:controls="clr-namespace:ControlTemplateDemos.Controls"
             ...>
    <ContentPage.Resources>
        <ControlTemplate x:Key="CardViewControlTemplate">
            ...
        </ControlTemplate>

        <Style TargetType="controls:CardView">
            <Setter Property="ControlTemplate"
                    Value="{StaticResource CardViewControlTemplate}" />
        </Style>
    </ContentPage.Resources>
    <StackLayout Margin="30">
        <controls:CardView BorderColor="DarkGray"
                           CardTitle="John Doe"
                           CardDescription="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla elit dolor, convallis non interdum."
                           IconBackgroundColor="SlateGray"
                           IconImageSource="user.png" />
        ...
    </StackLayout>
</ContentPage>

In this example, the implicit Style is automatically applied to each CardView object, and sets the ControlTemplate property of each CardView to CardViewControlTemplate.

For more information about styles, see Styles.

Redefine a control’s UI

When a ControlTemplate is instantiated and assigned to the ControlTemplate property of a ContentView derived custom control, or a ContentPage derived page, the visual structure defined for the custom control or page is replaced with the visual structure defined in the ControlTemplate.

For example, the CardViewUI custom control defines its user interface using the following XAML:

<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="ControlTemplateDemos.Controls.CardViewUI"
             x:Name="this">
    <Border BindingContext="{x:Reference this}"
            BackgroundColor="{Binding CardColor}"
            Stroke="{Binding BorderColor}"
           ...>
        <!-- UI objects that define the CardView visual structure -->           
    </Border>
</ContentView>

However, the controls that comprise this UI can be replaced by defining a new visual structure in a ControlTemplate, and assigning it to the ControlTemplate property of a CardViewUI object:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             ...>
    <ContentPage.Resources>
        <ControlTemplate x:Key="CardViewCompressed">
            <Grid RowDefinitions="100"
                  ColumnDefinitions="100, *">
                <Image Source="{TemplateBinding IconImageSource}"
                       BackgroundColor="{TemplateBinding IconBackgroundColor}"
                       ...>
                <!-- Other UI objects that define the CardView visual structure -->
            </Grid>
        </ControlTemplate>
    </ContentPage.Resources>
    <StackLayout Margin="30">
        <controls:CardViewUI BorderColor="DarkGray"
                             CardTitle="John Doe"
                             CardDescription="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla elit dolor, convallis non interdum."
                             IconBackgroundColor="SlateGray"
                             IconImageSource="user.png"
                             ControlTemplate="{StaticResource CardViewCompressed}" />
        ...
    </StackLayout>
</ContentPage>

In this example, the visual structure of the CardViewUI object is redefined in a ControlTemplate that provides a more compact visual structure that's suitable for a condensed list:

Screenshot of templated CardViewUI objects.

Substitute content into a ContentPresenter

A ContentPresenter can be placed in a control template to mark where content to be displayed by the templated custom control or templated page will appear. The custom control or page that consumes the control template will then define content to be displayed by the ContentPresenter. The following diagram illustrates a ControlTemplate for a page that contains a number of controls, including a ContentPresenter marked by a blue rectangle:

Control template for a ContentPage.

The following XAML shows a control template named TealTemplate that contains a ContentPresenter in its visual structure:

<ControlTemplate x:Key="TealTemplate">
    <Grid RowDefinitions="0.1*, 0.8*, 0.1*">
        <BoxView Color="Teal" />
        <Label Margin="20,0,0,0"
               Text="{TemplateBinding HeaderText}"
               ... />
        <ContentPresenter Grid.Row="1" />
        <BoxView Grid.Row="2"
                 Color="Teal" />
        <Label x:Name="changeThemeLabel"
               Grid.Row="2"
               Margin="20,0,0,0"
               Text="Change Theme"
               ...>
            <Label.GestureRecognizers>
                <TapGestureRecognizer Tapped="OnChangeThemeLabelTapped" />
            </Label.GestureRecognizers>
        </Label>
        <controls:HyperlinkLabel Grid.Row="2"
                                 Margin="0,0,20,0"
                                 Text="Help"
                                 Url="https://learn.microsoft.com/dotnet/maui/"
                                 ... />
    </Grid>
</ControlTemplate>

The following example shows TealTemplate assigned to the ControlTemplate property of a ContentPage derived page:

<controls:HeaderFooterPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
                           xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                           xmlns:controls="clr-namespace:ControlTemplateDemos.Controls"                           
                           ControlTemplate="{StaticResource TealTemplate}"
                           HeaderText="MyApp"
                           ...>
    <StackLayout Margin="10">
        <Entry Placeholder="Enter username" />
        <Entry Placeholder="Enter password"
               IsPassword="True" />
        <Button Text="Login" />
    </StackLayout>
</controls:HeaderFooterPage>

At runtime, when TealTemplate is applied to the page, the page content is substituted into the ContentPresenter defined in the control template:

Screenshot of templated page object.

Get a named element from a template

Named elements within a control template can be retrieved from the templated custom control or templated page. This can be achieved with the GetTemplateChild method, which returns the named element in the instantiated ControlTemplate visual tree, if found. Otherwise, it returns null.

After a control template has been instantiated, the template's OnApplyTemplate method is called. The GetTemplateChild method should therefore be called from a OnApplyTemplate override in the templated control or templated page.

Important

The GetTemplateChild method should only be called after the OnApplyTemplate method has been called.

The following XAML shows a control template named TealTemplate that can be applied to ContentPage derived pages:

<ControlTemplate x:Key="TealTemplate">
    <Grid>
        ...
        <Label x:Name="changeThemeLabel"
               Text="Change Theme"
               ...>
            <Label.GestureRecognizers>
                <TapGestureRecognizer Tapped="OnChangeThemeLabelTapped" />
            </Label.GestureRecognizers>
        </Label>
        ...
    </Grid>
</ControlTemplate>

In this example, the Label element is named, and can be retrieved in the code for the templated page. This is achieved by calling the GetTemplateChild method from the OnApplyTemplate override for the templated page:

public partial class AccessTemplateElementPage : HeaderFooterPage
{
    Label themeLabel;

    public AccessTemplateElementPage()
    {
        InitializeComponent();
    }

    protected override void OnApplyTemplate()
    {
        base.OnApplyTemplate();
        themeLabel = (Label)GetTemplateChild("changeThemeLabel");
        themeLabel.Text = OriginalTemplate ? "Aqua Theme" : "Teal Theme";
    }
}

In this example, the Label object named changeThemeLabel is retrieved once the ControlTemplate has been instantiated. changeThemeLabel can then be accessed and manipulated by the AccessTemplateElementPage class. The following screenshot shows that the text displayed by the Label has been changed:

Screenshot of templated page object that's changed.

Bind to a viewmodel

A ControlTemplate can data bind to a viewmodel, even when the ControlTemplate binds to the templated parent (the runtime object instance to which the template is applied).

The following XAML example shows a page that consumes a viewmodel named PeopleViewModel:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:ControlTemplateDemos"
             xmlns:controls="clr-namespace:ControlTemplateDemos.Controls"
             ...>
    <ContentPage.BindingContext>
        <local:PeopleViewModel />
    </ContentPage.BindingContext>

    <ContentPage.Resources>
        <DataTemplate x:Key="PersonTemplate">
            <controls:CardView BorderColor="DarkGray"
                               CardTitle="{Binding Name}"
                               CardDescription="{Binding Description}"
                               ControlTemplate="{StaticResource CardViewControlTemplate}" />
        </DataTemplate>
    </ContentPage.Resources>

    <StackLayout Margin="10"
                 BindableLayout.ItemsSource="{Binding People}"
                 BindableLayout.ItemTemplate="{StaticResource PersonTemplate}" />
</ContentPage>

In this example, the BindingContext of the page is set to a PeopleViewModel instance. This viewmodel exposes a People collection and an ICommand named DeletePersonCommand. The StackLayout on the page uses a bindable layout to data bind to the People collection, and the ItemTemplate of the bindable layout is set to the PersonTemplate resource. This DataTemplate specifies that each item in the People collection will be displayed using a CardView object. The visual structure of the CardView object is defined using a ControlTemplate named CardViewControlTemplate:

<ControlTemplate x:Key="CardViewControlTemplate">
    <Border BindingContext="{Binding Source={RelativeSource TemplatedParent}}"
            BackgroundColor="{Binding CardColor}"
            Stroke="{Binding BorderColor}"
           ...>
        <!-- UI objects that define the CardView visual structure -->           
    </Border>
</ControlTemplate>

In this example, the root element of the ControlTemplate is a Border object. The Border object uses the RelativeSource markup extension to set its BindingContext to the templated parent. The binding expressions of the Border object and its children resolve against CardView properties, due to inheriting the BindingContext from the root Border element. The following screenshot shows the page displaying the People collection:

Screenshot of three templated CardView objects that bind to a viewmodel.

While the objects in the ControlTemplate bind to properties on its templated parent, the Button within the control template binds to both its templated parent, and to the DeletePersonCommand in the viewmodel. This is because the Button.Command property redefines its binding source to be the binding context of the ancestor whose binding context type is PeopleViewModel, which is the StackLayout. The Path part of the binding expressions can then resolve the DeletePersonCommand property. However, the Button.CommandParameter property doesn't alter its binding source, instead inheriting it from its parent in the ControlTemplate. Therefore, the CommandParameter property binds to the CardTitle property of the CardView.

The overall effect of the Button bindings is that when the Button is tapped, the DeletePersonCommand in the PeopleViewModel class is executed, with the value of the CardName property being passed to the DeletePersonCommand. This results in the specified CardView being removed from the bindable layout.

For more information about relative bindings, see Relative bindings.