Шаблоны элементов управления Xamarin.Forms

Download Sample Скачайте пример

Шаблоны элементов управления Xamarin.Forms позволяют определить визуальную структуру производных пользовательских элементов управления ContentView и производных страниц ContentPage. Шаблоны элементов управления отделяют пользовательский интерфейс элемента управления или страницы от логики, которая их реализует. Вы можете добавить дополнительное содержимое в шаблонный пользовательский элемент управления или шаблонную страницу в заранее определенное расположение.

Например, можно создать шаблон элемента управления, который переопределяет пользовательский интерфейс, предоставленный пользовательским элементом управления. Шаблон элемента управления затем можно использовать в обязательном экземпляре пользовательского элемента управления. Кроме того, вы можете создать шаблон элемента управления, определяющий любой общий пользовательский интерфейс, который будет использоваться на нескольких страницах в приложении. Шаблон элемента управления затем можно использовать на нескольких страницах. Каждая страница будет по-прежнему отображать уникальное содержимое.

Создание шаблона ControlTemplate

В следующем примере показан код для пользовательского элемента управления CardView:

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);
    }
    // ...
}

Класс CardView, производный от класса ContentView, представляет пользовательский элемент управления, который отображает данные в карточном представлении макета. Класс содержит свойства, поддерживаемые привязываемыми свойствами, для отображаемых данных. Однако класс CardView не определяет никаких пользовательских интерфейсов. Вместо этого пользовательский интерфейс будет определен с помощью шаблона элемента управления. Дополнительные сведения о создании производных пользовательских элементов управления ContentView см. в статье Xamarin.Forms ContentView.

Создаваемый шаблон элемента управления имеет тип ControlTemplate. При создании ControlTemplate вы объединяете объекты View, чтобы создать пользовательский интерфейс для пользовательского элемента управления или страницы. Шаблон ControlTemplate должен иметь только один объект View в качестве корневого элемента. Однако корневой элемент обычно содержит другие объекты View. Комбинация объектов составляет визуальную структуру элемента управления.

Хотя ControlTemplate можно определить в строке кода, обычно объект ControlTemplate объявляют как ресурс в словаре ресурсов. Так как шаблоны элементов управления являются ресурсами, для них действуют те же правила определения области, которые применяются ко всем ресурсам. Например, если объявить шаблон элемента управления в корневом элементе XAML-файла определения приложения, шаблон можно использовать в любом месте приложения. Если вы определяете шаблон на странице, шаблон элемента управления можно будет использовать только на ней. Дополнительные сведения о ресурсах см. в статье Словари ресурсов Xamarin.Forms.

В следующем примере кода XAML показан шаблон ControlTemplate для объектов CardView:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             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}"
                 CornerRadius="5"
                 HasShadow="True"
                 Padding="8"
                 HorizontalOptions="Center"
                 VerticalOptions="Center">
              <Grid>
                  <Grid.RowDefinitions>
                      <RowDefinition Height="75" />
                      <RowDefinition Height="4" />
                      <RowDefinition Height="Auto" />
                  </Grid.RowDefinitions>
                  <Grid.ColumnDefinitions>
                      <ColumnDefinition Width="75" />
                      <ColumnDefinition Width="200" />
                  </Grid.ColumnDefinitions>
                  <Frame IsClippedToBounds="True"
                         BorderColor="{Binding BorderColor}"
                         BackgroundColor="{Binding IconBackgroundColor}"
                         CornerRadius="38"
                         HeightRequest="60"
                         WidthRequest="60"
                         HorizontalOptions="Center"
                         VerticalOptions="Center">
                      <Image Source="{Binding IconImageSource}"
                             Margin="-20"
                             WidthRequest="100"
                             HeightRequest="100"
                             Aspect="AspectFill" />
                  </Frame>
                  <Label Grid.Column="1"
                         Text="{Binding CardTitle}"
                         FontAttributes="Bold"
                         FontSize="Large"
                         VerticalTextAlignment="Center"
                         HorizontalTextAlignment="Start" />
                  <BoxView Grid.Row="1"
                           Grid.ColumnSpan="2"
                           BackgroundColor="{Binding BorderColor}"
                           HeightRequest="2"
                           HorizontalOptions="Fill" />
                  <Label Grid.Row="2"
                         Grid.ColumnSpan="2"
                         Text="{Binding CardDescription}"
                         VerticalTextAlignment="Start"
                         VerticalOptions="Fill"
                         HorizontalOptions="Fill" />
              </Grid>
          </Frame>
      </ControlTemplate>
    </ContentPage.Resources>
    ...
</ContentPage>

Если шаблон ControlTemplate объявлен как ресурс, он должен иметь ключ, заданный с помощью атрибута x:Key, чтобы его можно было определить в словаре ресурсов. В этом примере корневым элементом шаблона CardViewControlTemplate является объект Frame. Объект Frame использует расширение разметки RelativeSource, чтобы установить для BindingContext значение экземпляра объекта среды выполнения, к которому применяется шаблонный родительский элемент. В объекте Frame используется сочетание объектов Grid, Frame, Image, Label и BoxView, чтобы определить визуальную структуру объекта CardView. Выражения привязки этих объектов разрешаются с учетом свойств CardView из-за наследования BindingContext от корневого элемента Frame. Дополнительные сведения о расширении разметки RelativeSource см. в статье Относительные привязки Xamarin.Forms.

Использование шаблона ControlTemplate

Шаблон ControlTemplate можно применить к производному пользовательскому элементу управления ContentView, присвоив свойству ControlTemplate значение объекта шаблона элемента управления. Аналогичным образом, шаблон ControlTemplate можно применить к производной странице ContentPage, присвоив свойству ControlTemplate значение объекта шаблона элемента управления. Когда во время выполнения применяется шаблон ControlTemplate, все элементы управления, определенные в ControlTemplate, добавляются к визуальному дереву шаблонного пользовательского элемента управления или шаблонной страницы.

В приведенном ниже примере показан шаблон CardViewControlTemplate, присваиваемый свойству ControlTemplate каждого объекта CardView:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             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}" />
        <controls:CardView BorderColor="DarkGray"
                           CardTitle="Xamarin Monkey"
                           CardDescription="Aliquam sagittis, odio lacinia fermentum dictum, mi erat scelerisque erat, quis aliquet arcu."
                           IconBackgroundColor="SlateGray"
                           IconImageSource="user.png"
                           ControlTemplate="{StaticResource CardViewControlTemplate}" />
    </StackLayout>
</ContentPage>

В этом примере элементы управления шаблона CardViewControlTemplate становятся частью визуального дерева каждого объекта CardView. Так как корневой объект Frame шаблона элемента управления присваивает значение BindingContext шаблонному родительскому элементу, Frame и его дочерние элементы разрешают свои выражения привязки с учетом свойств каждого объекта CardView.

На приведенных ниже снимках экрана показан шаблон CardViewControlTemplate, применяемый к трем объектам CardView:

Screenshots of templated CardView objects, on iOS and Android

Внимание

Момент времени, когда шаблон ControlTemplate применяется к экземпляру элемента управления, можно определить путем переопределения метода OnApplyTemplate в шаблонном пользовательском элементе управления или шаблонной странице. Дополнительные сведения см. в разделе Извлечение именованного элемента из шаблона.

Передача параметров с помощью TemplateBinding

Расширение разметки TemplateBinding привязывает свойство элемента из шаблона ControlTemplate к общему свойству, которое определено шаблонным пользовательским элементом управления или шаблонной страницей. Благодаря использованию расширения TemplateBinding свойства элемента управления могут действовать в качестве параметров шаблона. Таким образом, если присвоено свойство шаблонного пользовательского элемента управления или шаблонной страницы, значение передается в элемент с расширением разметки TemplateBinding.

Внимание

Выражение разметки TemplateBinding позволяет удалить привязку RelativeSource из предыдущего шаблона элемента управления и заменяет выражения Binding.

Расширение разметки TemplateBinding определяет следующие свойства:

  • Path с типом string — путь к свойству.
  • Mode с типом BindingMode — направление распространения изменений между источником и целью.
  • Converter с типом IValueConverter — преобразователь значений привязки.
  • ConverterParameter с типом object — параметр для преобразователя значений привязки.
  • StringFormat с типом string — формат строки для привязки.

Path — это свойство ContentProperty для расширения разметки TemplateBinding. Если путь является первым элементом в выражении TemplateBinding, часть Path= расширения разметки можно опустить. Дополнительные сведения об использовании этих свойств в выражении привязки см. в статье Привязка данных Xamarin.Forms.

Предупреждение

Расширение разметки TemplateBinding следует использовать только в шаблоне ControlTemplate. Однако попытка использовать выражение TemplateBinding за пределами шаблона ControlTemplate не приведет к ошибке сборки или возникновению исключения.

В следующем примере кода XAML показан шаблон ControlTemplate для объектов CardView, использующих расширение разметки TemplateBinding:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             ...>
    <ContentPage.Resources>
        <ControlTemplate x:Key="CardViewControlTemplate">
            <Frame BackgroundColor="{TemplateBinding CardColor}"
                   BorderColor="{TemplateBinding BorderColor}"
                   CornerRadius="5"
                   HasShadow="True"
                   Padding="8"
                   HorizontalOptions="Center"
                   VerticalOptions="Center">
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="75" />
                        <RowDefinition Height="4" />
                        <RowDefinition Height="Auto" />
                    </Grid.RowDefinitions>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="75" />
                        <ColumnDefinition Width="200" />
                    </Grid.ColumnDefinitions>
                    <Frame IsClippedToBounds="True"
                           BorderColor="{TemplateBinding BorderColor}"
                           BackgroundColor="{TemplateBinding IconBackgroundColor}"
                           CornerRadius="38"
                           HeightRequest="60"
                           WidthRequest="60"
                           HorizontalOptions="Center"
                           VerticalOptions="Center">
                        <Image Source="{TemplateBinding IconImageSource}"
                               Margin="-20"
                               WidthRequest="100"
                               HeightRequest="100"
                               Aspect="AspectFill" />
                    </Frame>
                    <Label Grid.Column="1"
                           Text="{TemplateBinding CardTitle}"
                           FontAttributes="Bold"
                           FontSize="Large"
                           VerticalTextAlignment="Center"
                           HorizontalTextAlignment="Start" />
                    <BoxView Grid.Row="1"
                             Grid.ColumnSpan="2"
                             BackgroundColor="{TemplateBinding BorderColor}"
                             HeightRequest="2"
                             HorizontalOptions="Fill" />
                    <Label Grid.Row="2"
                           Grid.ColumnSpan="2"
                           Text="{TemplateBinding CardDescription}"
                           VerticalTextAlignment="Start"
                           VerticalOptions="Fill"
                           HorizontalOptions="Fill" />
                </Grid>
            </Frame>
        </ControlTemplate>
    </ContentPage.Resources>
    ...
</ContentPage>

В этом примере расширение разметки TemplateBinding разрешает выражения привязки для свойств каждого объекта CardView. На приведенных ниже снимках экрана показан шаблон CardViewControlTemplate, применяемый к трем объектам CardView:

Screenshots of templated CardView objects

Внимание

Использование расширения разметки TemplateBinding эквивалентно присвоению для свойства BindingContext корневого элемента шаблона значения шаблонного родительского элемента с помощью расширения разметки RelativeSource и разрешению привязки дочерних объектов с помощью расширения разметки Binding. На самом деле расширение разметки TemplateBinding создает Binding с Source в качестве RelativeBindingSource.TemplatedParent.

Применение шаблона ControlTemplate со стилем

Шаблоны элементов управления также можно применить со стилями. Для этого необходимо создать неявный или явный стиль, в котором используется ControlTemplate.

В следующем примере кода XAML показан неявный стиль, в котором используется CardViewControlTemplate:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             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" />
        <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"/>
        <controls:CardView BorderColor="DarkGray"
                           CardTitle="Xamarin Monkey"
                           CardDescription="Aliquam sagittis, odio lacinia fermentum dictum, mi erat scelerisque erat, quis aliquet arcu."
                           IconBackgroundColor="SlateGray"
                           IconImageSource="user.png" />
    </StackLayout>
</ContentPage>

В этом примере неявноеStyle автоматически применяется к каждому CardView объекту и задает ControlTemplate для каждого CardView свойства значение CardViewControlTemplate.

Дополнительные сведения о стилях см. в статье Стили Xamarin.Forms.

Переопределение пользовательского интерфейса элемента управления

Когда шаблон ControlTemplate создается и присваивается свойству ControlTemplate производного пользовательского элемента управления ContentView или производной страницы ContentPage, визуальная структура, определенная для пользовательского элемента управления или страницы, заменяется визуальной структурой, определенной в ControlTemplate.

Например, пользовательский элемент управления CardViewUI определяет пользовательский интерфейс, используя следующий код XAML:

<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
             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}"
           CornerRadius="5"
           HasShadow="True"
           Padding="8"
           HorizontalOptions="Center"
           VerticalOptions="Center">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="75" />
                <RowDefinition Height="4" />
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="75" />
                <ColumnDefinition Width="200" />
            </Grid.ColumnDefinitions>
            <Frame IsClippedToBounds="True"
                   BorderColor="{Binding BorderColor, FallbackValue='Black'}"
                   BackgroundColor="{Binding IconBackgroundColor, FallbackValue='Gray'}"
                   CornerRadius="38"
                   HeightRequest="60"
                   WidthRequest="60"
                   HorizontalOptions="Center"
                   VerticalOptions="Center">
                <Image Source="{Binding IconImageSource}"
                       Margin="-20"
                       WidthRequest="100"
                       HeightRequest="100"
                       Aspect="AspectFill" />
            </Frame>
            <Label Grid.Column="1"
                   Text="{Binding CardTitle, FallbackValue='Card title'}"
                   FontAttributes="Bold"
                   FontSize="Large"
                   VerticalTextAlignment="Center"
                   HorizontalTextAlignment="Start" />
            <BoxView Grid.Row="1"
                     Grid.ColumnSpan="2"
                     BackgroundColor="{Binding BorderColor, FallbackValue='Black'}"
                     HeightRequest="2"
                     HorizontalOptions="Fill" />
            <Label Grid.Row="2"
                   Grid.ColumnSpan="2"
                   Text="{Binding CardDescription, FallbackValue='Card description'}"
                   VerticalTextAlignment="Start"
                   VerticalOptions="Fill"
                   HorizontalOptions="Fill" />
        </Grid>
    </Frame>
</ContentView>

Однако элементы управления, которые входят в пользовательский интерфейс, можно заменить. Для этого необходимо определить новую визуальную структуру в ControlTemplate и присвоить ее свойству ControlTemplate объекта CardViewUI:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             ...>
    <ContentPage.Resources>
        <ControlTemplate x:Key="CardViewCompressed">
            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition Height="100" />
                </Grid.RowDefinitions>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="100" />
                    <ColumnDefinition Width="*" />
                </Grid.ColumnDefinitions>
                <Image Source="{TemplateBinding IconImageSource}"
                        BackgroundColor="{TemplateBinding IconBackgroundColor}"
                        WidthRequest="100"
                        HeightRequest="100"
                        Aspect="AspectFill"
                        HorizontalOptions="Center"
                        VerticalOptions="Center" />
                <StackLayout Grid.Column="1">
                    <Label Text="{TemplateBinding CardTitle}"
                           FontAttributes="Bold" />
                    <Label Text="{TemplateBinding CardDescription}" />
                </StackLayout>
            </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}" />
        <controls:CardViewUI 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 CardViewCompressed}" />
        <controls:CardViewUI BorderColor="DarkGray"
                             CardTitle="Xamarin Monkey"
                             CardDescription="Aliquam sagittis, odio lacinia fermentum dictum, mi erat scelerisque erat, quis aliquet arcu."
                             IconBackgroundColor="SlateGray"
                             IconImageSource="user.png"
                             ControlTemplate="{StaticResource CardViewCompressed}" />
    </StackLayout>
</ContentPage>

В этом примере визуальная структура объекта CardViewUI переопределяется в шаблоне ControlTemplate, который предоставляет более компактную визуальную структуру, пригодную для сокращенного списка:

Screenshots of templated CardViewUI objects, on iOS and Android

Преобразование содержимого в ContentPresenter

ContentPresenter можно поместить в шаблон элемента управления, чтобы указать, где будет отображаться содержимое шаблонного пользовательского элемента управления или шаблонной страницы. Затем пользовательский элемент управления или пользовательская страница, которые используют шаблон элемента управления, определяют содержимое, отображаемое объектом ContentPresenter. На следующей схеме показан шаблон ControlTemplate для страницы, содержащий несколько элементов управления, в том числе элемент ContentPresenter, обозначенный синим прямоугольником:

Control Template for a ContentPage

В следующем коде XAML показан шаблон элемента управления TealTemplate, содержащий ContentPresenter в своей визуальной структуре:

<ControlTemplate x:Key="TealTemplate">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="0.1*" />
            <RowDefinition Height="0.8*" />
            <RowDefinition Height="0.1*" />
        </Grid.RowDefinitions>
        <BoxView Color="Teal" />
        <Label Margin="20,0,0,0"
               Text="{TemplateBinding HeaderText}"
               TextColor="White"
               FontSize="Title"
               VerticalOptions="Center" />
        <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"
               TextColor="White"
               HorizontalOptions="Start"
               VerticalOptions="Center">
            <Label.GestureRecognizers>
                <TapGestureRecognizer Tapped="OnChangeThemeLabelTapped" />
            </Label.GestureRecognizers>
        </Label>
        <controls:HyperlinkLabel Grid.Row="2"
                                 Margin="0,0,20,0"
                                 Text="Help"
                                 TextColor="White"
                                 Url="https://learn.microsoft.com/xamarin/xamarin-forms/"
                                 HorizontalOptions="End"
                                 VerticalOptions="Center" />
    </Grid>
</ControlTemplate>

В следующем примере показан шаблон TealTemplate, назначенный свойству ControlTemplate производной страницы ContentPage:

<controls:HeaderFooterPage xmlns="http://xamarin.com/schemas/2014/forms"
                           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>

Когда во время выполнения шаблон TealTemplate применяется к странице, ее содержимое подставляется в класс ContentPresenter, определенный в шаблоне элемента управления:

Screenshots of templated page object, on iOS and Android

Извлечение именованного элемента из шаблона

Именованные элементы в шаблоне элемента управления можно извлечь из шаблонного пользовательского элемента управления или шаблонной страницы. Это можно сделать с помощью метода GetTemplateChild, который возвращает именованный элемент в созданном экземпляре визуального дерева ControlTemplate, если он найден. В противном случае возвращается значение null.

Когда экземпляр шаблона элемента управления будет создан, вызовется метод шаблона OnApplyTemplate. Поэтому метод GetTemplateChild следует вызывать из переопределения OnApplyTemplate в шаблонном элементе управления или шаблонной странице.

Внимание

Метод GetTemplateChild должен вызываться только после вызова метода OnApplyTemplate.

В следующем коде XAML показан шаблон элемента управления TealTemplate, который можно применить к производным страницам ContentPage:

<ControlTemplate x:Key="TealTemplate">
    <Grid>
        ...
        <Label x:Name="changeThemeLabel"
               Grid.Row="2"
               Margin="20,0,0,0"
               Text="Change Theme"
               TextColor="White"
               HorizontalOptions="Start"
               VerticalOptions="Center">
            <Label.GestureRecognizers>
                <TapGestureRecognizer Tapped="OnChangeThemeLabelTapped" />
            </Label.GestureRecognizers>
        </Label>
        ...
    </Grid>
</ControlTemplate>

В этом примере элемент Label именованный, поэтому его можно извлечь из кода шаблонной страницы. Для этого нужно вызвать метод GetTemplateChild из переопределения OnApplyTemplate для шаблонной страницы:

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";
    }
}

В этом примере объект Label с именем changeThemeLabel извлекается после создания экземпляра ControlTemplate. После этого объект changeThemeLabel станет доступным для использования классом AccessTemplateElementPage. На приведенных ниже снимках экрана показано, что текст, отображаемый объектом Label, изменен:

Screenshots of templated page object

Привязка к ViewModel

ControlTemplate может выполнять привязку данных к ViewModel, даже если шаблон ControlTemplate привязывается к шаблонному родительскому элементу (экземпляру объекта среды выполнения, к которому применяется шаблон).

В следующем примере кода XAML показана страница, которая использует ViewModel с именем PeopleViewModel:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             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>

В этом примере элемент BindingContext страницы содержит экземпляр PeopleViewModel. ViewModel предоставляет коллекцию People и ICommand с именем DeletePersonCommand. Класс StackLayout на странице использует привязываемый макет, чтобы привязать данные к коллекции People, а для ресурса PersonTemplate шаблон ItemTemplate привязываемого макета. Этот шаблон DataTemplate указывает, что каждый элемент коллекции People будет отображаться с помощью объекта CardView. Визуальная структура объекта CardView определяется с помощью шаблона ControlTemplate с именем CardViewControlTemplate:

<ControlTemplate x:Key="CardViewControlTemplate">
    <Frame BindingContext="{Binding Source={RelativeSource TemplatedParent}}"
           BackgroundColor="{Binding CardColor}"
           BorderColor="{Binding BorderColor}"
           CornerRadius="5"
           HasShadow="True"
           Padding="8"
           HorizontalOptions="Center"
           VerticalOptions="Center">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="75" />
                <RowDefinition Height="4" />
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>
            <Label Text="{Binding CardTitle}"
                   FontAttributes="Bold"
                   FontSize="Large"
                   VerticalTextAlignment="Center"
                   HorizontalTextAlignment="Start" />
            <BoxView Grid.Row="1"
                     BackgroundColor="{Binding BorderColor}"
                     HeightRequest="2"
                     HorizontalOptions="Fill" />
            <Label Grid.Row="2"
                   Text="{Binding CardDescription}"
                   VerticalTextAlignment="Start"
                   VerticalOptions="Fill"
                   HorizontalOptions="Fill" />
            <Button Text="Delete"
                    Command="{Binding Source={RelativeSource AncestorType={x:Type local:PeopleViewModel}}, Path=DeletePersonCommand}"
                    CommandParameter="{Binding CardTitle}"
                    HorizontalOptions="End" />
        </Grid>
    </Frame>
</ControlTemplate>

В этом примере корневым элементом шаблона ControlTemplate является объект Frame. Объект Frame использует расширение разметки RelativeSource, чтобы задать для BindingContext значение шаблонного родительского элемента. Выражения привязки объекта Frame и его дочерних элементов разрешаются для свойств CardView из-за наследования BindingContext от корневого элемента Frame. На следующих снимках экрана показана страница с коллекцией People, состоящей из трех элементов:

Screenshots of three templated CardView objects

Хотя объекты в шаблоне ControlTemplate привязываются к свойствам родительского элемента, Button в шаблоне элемента управления привязывается к шаблонному родительскому элементу и к DeletePersonCommand в объекте ViewModel. Это связано с тем, что свойство Button.Command переопределяет источник привязки как контекст привязки предка с типом PeopleViewModel, то есть StackLayout. Затем часть Path выражений привязки может разрешить свойство DeletePersonCommand. Однако свойство Button.CommandParameter не изменяет источник привязки, а наследует его от родительского элемента в ControlTemplate. Таким образом, свойство CommandParameter привязывается к свойству CardTitleCardView.

Общим эффектом привязок Button является то, что при нажатии Button в классе PeopleViewModel выполняется DeletePersonCommand со значением свойства CardName, которое передается в DeletePersonCommand. В результате заданный объект CardView удаляется из привязываемого макета:

Screenshots of two templated CardView objects

Дополнительные сведения об относительных привязках см. в статье Относительные привязки Xamarin.Forms.