docs-maui/docs/fundamentals/controltemplate.md

28 KiB
Исходник Ответственный История

title description ms.date
Control templates .NET MAUI control templates define the visual structure of ContentView derived custom controls, and ContentPage derived pages. 02/18/2022

Control templates

Browse sample. Browse the sample

.NET Multi-platform App UI (.NET MAUI) control templates enable you to define the visual structure of xref:Microsoft.Maui.Controls.ContentView derived custom controls, and xref:Microsoft.Maui.Controls.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 xref:Microsoft.Maui.Controls.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 xref:Microsoft.Maui.Controls.ContentView derived custom controls, see ContentView.

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

While a xref:Microsoft.Maui.Controls.ControlTemplate can be defined inline, the typical approach to declaring a xref:Microsoft.Maui.Controls.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 xref:Microsoft.Maui.Controls.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">
          <Frame BindingContext="{Binding Source={RelativeSource TemplatedParent}}"
                 BackgroundColor="{Binding CardColor}"
                 BorderColor="{Binding BorderColor}"
                 ...>
              <!-- UI objects that define the CardView visual structure -->
          </Frame>
      </ControlTemplate>
    </ContentPage.Resources>
    ...
</ContentPage>

When a xref:Microsoft.Maui.Controls.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 xref:Microsoft.Maui.Controls.Frame object. The xref:Microsoft.Maui.Controls.Frame object uses the RelativeSource markup extension to set its xref:Microsoft.Maui.Controls.BindableObject.BindingContext to the runtime object instance to which the template will be applied, which is known as the templated parent. The xref:Microsoft.Maui.Controls.Frame 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 xref:Microsoft.Maui.Controls.BindableObject.BindingContext from the root xref:Microsoft.Maui.Controls.Frame element. For more information about the RelativeSource markup extension, see Relative bindings.

Consume a ControlTemplate

A xref:Microsoft.Maui.Controls.ControlTemplate can be applied to a xref:Microsoft.Maui.Controls.ContentView derived custom control by setting its xref:Microsoft.Maui.Controls.ControlTemplate property to the control template object. Similarly, a xref:Microsoft.Maui.Controls.ControlTemplate can be applied to a xref:Microsoft.Maui.Controls.ContentPage derived page by setting its xref:Microsoft.Maui.Controls.ControlTemplate property to the control template object. At runtime, when a xref:Microsoft.Maui.Controls.ControlTemplate is applied, all of the controls that are defined in the xref:Microsoft.Maui.Controls.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 xref:Microsoft.Maui.Controls.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 xref:Microsoft.Maui.Controls.Frame object for the control template sets its xref:Microsoft.Maui.Controls.BindableObject.BindingContext to the templated parent, the xref:Microsoft.Maui.Controls.Frame 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:

:::image type="content" source="media/controltemplate/relativesource-controltemplate.png" alt-text="Screenshot of two templated CardView objects.":::

[!IMPORTANT] The point in time that a xref:Microsoft.Maui.Controls.ControlTemplate is applied to a control instance can be detected by overriding the xref:Microsoft.Maui.Controls.TemplatedView.OnApplyTemplate%2A 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 xref:Microsoft.Maui.Controls.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 xref:Microsoft.Maui.Controls.Xaml.TemplateBindingExtension.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 xref:Microsoft.Maui.Controls.ControlTemplate. However, attempting to use a TemplateBinding expression outside of a xref:Microsoft.Maui.Controls.ControlTemplate will not result in a build error or an exception being thrown.

The following XAML example shows a xref:Microsoft.Maui.Controls.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">
            <Frame BackgroundColor="{TemplateBinding CardColor}"
                   BorderColor="{TemplateBinding BorderColor}"
                   ...>
                <!-- UI objects that define the CardView visual structure -->                   
            </Frame>
        </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:

:::image type="content" source="media/controltemplate/templatebinding-controltemplate.png" alt-text="Screenshot of templated CardView objects.":::

[!IMPORTANT] Using the TemplateBinding markup extension is equivalent to setting the xref:Microsoft.Maui.Controls.BindableObject.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 xref:Microsoft.Maui.Controls.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 xref:Microsoft.Maui.Controls.Style is automatically applied to each CardView object, and sets the xref:Microsoft.Maui.Controls.ControlTemplate property of each CardView to CardViewControlTemplate.

For more information about styles, see Styles.

Redefine a controls UI

When a xref:Microsoft.Maui.Controls.ControlTemplate is instantiated and assigned to the xref:Microsoft.Maui.Controls.ControlTemplate property of a xref:Microsoft.Maui.Controls.ContentView derived custom control, or a xref:Microsoft.Maui.Controls.ContentPage derived page, the visual structure defined for the custom control or page is replaced with the visual structure defined in the xref:Microsoft.Maui.Controls.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">
    <Frame BindingContext="{x:Reference this}"
           BackgroundColor="{Binding CardColor}"
           BorderColor="{Binding BorderColor}"
           ...>
        <!-- UI objects that define the CardView visual structure -->           
    </Frame>
</ContentView>

However, the controls that comprise this UI can be replaced by defining a new visual structure in a xref:Microsoft.Maui.Controls.ControlTemplate, and assigning it to the xref:Microsoft.Maui.Controls.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 xref:Microsoft.Maui.Controls.ControlTemplate that provides a more compact visual structure that's suitable for a condensed list:

:::image type="content" source="media/controltemplate/redefine-controltemplate.png" alt-text="Screenshot of templated CardViewUI objects.":::

Substitute content into a ContentPresenter

A xref:Microsoft.Maui.Controls.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 xref:Microsoft.Maui.Controls.ContentPresenter. The following diagram illustrates a xref:Microsoft.Maui.Controls.ControlTemplate for a page that contains a number of controls, including a xref:Microsoft.Maui.Controls.ContentPresenter marked by a blue rectangle:

:::image type="content" source="media/controltemplate/controltemplate.png" alt-text="Control template for a ContentPage." border="false":::

The following XAML shows a control template named TealTemplate that contains a xref:Microsoft.Maui.Controls.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 xref:Microsoft.Maui.Controls.ControlTemplate property of a xref:Microsoft.Maui.Controls.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 xref:Microsoft.Maui.Controls.ContentPresenter defined in the control template:

:::image type="content" source="media/controltemplate/tealtemplate-contentpage.png" alt-text="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 xref:Microsoft.Maui.Controls.TemplatedView.GetTemplateChild%2A method, which returns the named element in the instantiated xref:Microsoft.Maui.Controls.ControlTemplate visual tree, if found. Otherwise, it returns null.

After a control template has been instantiated, the template's xref:Microsoft.Maui.Controls.TemplatedView.OnApplyTemplate%2A method is called. The xref:Microsoft.Maui.Controls.TemplatedView.GetTemplateChild%2A method should therefore be called from a xref:Microsoft.Maui.Controls.TemplatedView.OnApplyTemplate%2A override in the templated control or templated page.

[!IMPORTANT] The xref:Microsoft.Maui.Controls.TemplatedView.GetTemplateChild%2A method should only be called after the xref:Microsoft.Maui.Controls.TemplatedView.OnApplyTemplate%2A method has been called.

The following XAML shows a control template named TealTemplate that can be applied to xref:Microsoft.Maui.Controls.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 xref:Microsoft.Maui.Controls.Label element is named, and can be retrieved in the code for the templated page. This is achieved by calling the xref:Microsoft.Maui.Controls.TemplatedView.GetTemplateChild%2A method from the xref:Microsoft.Maui.Controls.TemplatedView.OnApplyTemplate%2A 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 xref:Microsoft.Maui.Controls.Label object named changeThemeLabel is retrieved once the xref:Microsoft.Maui.Controls.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 xref:Microsoft.Maui.Controls.Label has been changed:

:::image type="content" source="media/controltemplate/get-named-element.png" alt-text="Screenshot of templated page object that's changed.":::

Bind to a viewmodel

A xref:Microsoft.Maui.Controls.ControlTemplate can data bind to a viewmodel, even when the xref:Microsoft.Maui.Controls.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 xref:Microsoft.Maui.Controls.BindableObject.BindingContext of the page is set to a PeopleViewModel instance. This viewmodel exposes a People collection and an xref:System.Windows.Input.ICommand named DeletePersonCommand. The xref:Microsoft.Maui.Controls.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 xref:Microsoft.Maui.Controls.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 xref:Microsoft.Maui.Controls.ControlTemplate named CardViewControlTemplate:

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

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

:::image type="content" source="media/controltemplate/viewmodel-controltemplate.png" alt-text="Screenshot of three templated CardView objects that bind to a viewmodel.":::

While the objects in the xref:Microsoft.Maui.Controls.ControlTemplate bind to properties on its templated parent, the xref:Microsoft.Maui.Controls.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 xref:Microsoft.Maui.Controls.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 xref:Microsoft.Maui.Controls.ControlTemplate. Therefore, the CommandParameter property binds to the CardTitle property of the CardView.

The overall effect of the xref:Microsoft.Maui.Controls.Button bindings is that when the xref:Microsoft.Maui.Controls.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.