Xamarin.Forms шаблоны (Templates) элементов управления

Автор:

learn.microsoft.com

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

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

Создание шаблона элемента управления

В следующем примере показан код для пользовательского элемента управления 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 не определяет пользовательский интерфейс. Вместо этого пользовательский интерфейс будет задан с помощью шаблона элемента управления.

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

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

В следующем примере 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.

Использования шаблона 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:

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

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

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

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

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

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

Свойством ContentProperty для расширения разметки TemplateBinding является Path. Поэтому часть "Path=" в расширении разметки может быть опущена, если путь является первым элементом в выражении TemplateBinding.

Предупреждение. Расширение разметки 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:

Важно. Использование расширения разметки TemplateBinding эквивалентно установке BindingContext корневого элемента шаблона на его шаблонизированного родителя с помощью расширения разметки RelativeSource, а затем разрешению привязок дочерних объектов с помощью расширения разметки Binding. Фактически, расширение разметки TemplateBinding создает привязку, источником которой является 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>

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

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

Когда шаблон 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, который обеспечивает более компактную визуальную структуру, подходящую для сжатого списка:

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

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

В следующем 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, определенный в шаблоне элемента управления:

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

Именованные элементы в шаблоне элемента управления могут быть получены из шаблонизированного пользовательского элемента управления или шаблонизированной страницы. Для этого используется метод 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. На следующих скриншотах видно, что текст, отображаемый меткой, был изменен:

Привязка к модели представления

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

В следующем примере XAML показана страница, потребляющая модель представления с именем 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. Эта модель отображает коллекцию People и команду ICommand с именем DeletePersonCommand. StackLayout страницы использует привязываемый макет для привязки данных к коллекции People, а ItemTemplate привязываемого макета установлен на ресурс PersonTemplate. Этот 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, состоящая из трех элементов:

В то время как объекты шаблона ControlTemplate связываются со свойствами своего родителя-шаблона, кнопка в шаблоне элемента управления связывается как со своим родителем-шаблоном, так и с командой DeletePersonCommand в модели представления. Это происходит потому, что свойство Button.Command переопределяет свой источник привязки как контекст привязки предка, типом контекста привязки которого является PeopleViewModel, то есть StackLayout. После этого часть Path выражения привязки может разрешить свойство DeletePersonCommand. Однако свойство Button.CommandParameter не изменяет свой источник привязки, а наследует его от своего родителя в ControlTemplate. Поэтому свойство CommandParameter привязывается к свойству CardTitle окна CardView.

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

Оглавление

Материалы по теме