Xamarin.Forms modèles de contrôle

Télécharger l’exemple Télécharger l’exemple

Xamarin.Forms les modèles de contrôle vous permettent de définir la structure visuelle des ContentView contrôles personnalisés dérivés et ContentPage des pages dérivées. Les modèles de contrôle séparent l’interface utilisateur d’un contrôle personnalisé ou d’une page, de la logique qui implémente le contrôle ou la page. Du contenu supplémentaire peut également être inséré dans le contrôle personnalisé basé sur un modèle, ou dans la page basée sur un modèle, à un emplacement prédéfini.

Par exemple, vous pouvez créer un modèle de contrôle qui redéfinit l’interface utilisateur fournie par un contrôle personnalisé. Le modèle de contrôle peut ensuite être consommé par l’instance de contrôle personnalisé requise. Vous pouvez également créer un modèle de contrôle qui définit toute interface utilisateur commune qui sera utilisée par plusieurs pages dans une application. Le modèle de contrôle peut ensuite être consommé par plusieurs pages, chacune d’elles affichant toujours son contenu unique.

Créer un ControlTemplate

L’exemple suivant montre le code pour un contrôle personnalisé 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);
    }
    // ...
}

La classe CardView, qui dérive de la classe ContentView, représente un contrôle personnalisé qui affiche des données dans une disposition de type carte. La classe contient des propriétés, qui sont secondées par des propriétés pouvant être liées, pour les données qu’elle affiche. Toutefois, la classe CardView ne définit pas d’interface utilisateur. Au lieu de cela, l’interface utilisateur sera définie avec un modèle de contrôle. Pour plus d’informations sur la création de ContentView contrôles personnalisés dérivés, consultez Xamarin.Forms ContentView.

Un modèle de contrôle est créé avec le type ControlTemplate. Quand vous créez un ControlTemplate, vous combinez des objets View pour générer l’interface utilisateur d’un contrôle personnalisé ou d’une page. Un ControlTemplate doit avoir une seule View comme élément racine. Toutefois, l’élément racine contient généralement d’autres objets View. La combinaison des objets forme la structure visuelle du contrôle.

Même si un ControlTemplate peut être défini inline, l’approche classique consiste à déclarer un ControlTemplate en tant que ressource dans un dictionnaire de ressources. Les modèles de contrôle étant des ressources, ils obéissent aux mêmes règles de portée que celles qui s’appliquent à toutes les ressources. Par exemple, si vous déclarez un modèle de contrôle dans l’élément racine de votre fichier XAML de définition d’application, le modèle peut être utilisé n’importe où dans votre application. Si vous définissez le modèle dans une page, seule cette page peut utiliser le modèle de contrôle. Pour plus d’informations sur les ressources, consultez Xamarin.Forms Dictionnaires de ressources.

L’exemple de code XAML suivant montre un ControlTemplate pour des objets 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>

Quand un ControlTemplate est déclaré en tant que ressource, il doit avoir une clé spécifiée avec l’attribut x:Key afin de pouvoir être identifié dans le dictionnaire de ressources. Dans cet exemple, l’élément racine du CardViewControlTemplate est un objet Frame. L’objet Frame utilise l’extension de balisage RelativeSource pour définir comme BindingContext l’instance de l’objet runtime à laquelle le modèle sera appliqué, connu sous le nom de parent basé sur un modèle. L’objet Frame utilise une combinaison d’objets Grid, Frame, Image, Labelet BoxView pour définir la structure visuelle d’un CardView objet. Les expressions de liaison de ces objets sont résolues par rapport à des propriétés CardView, en raison de l’héritage du BindingContext à partir de l’élément Frame racine. Pour plus d’informations sur l’extension de RelativeSource balisage, consultez Xamarin.Forms Liaisons relatives.

Consommer un ControlTemplate

Un ControlTemplate peut être appliqué à un contrôle personnalisé dérivé de ContentView en affectant comme valeur de sa propriété ControlTemplate l’objet de modèle de contrôle. De même, un ControlTemplate peut être appliqué à une page dérivée de ContentPage en affectant comme valeur de sa propriété ControlTemplate l’objet de modèle de contrôle. Au moment de l’exécution, quand un ControlTemplate est appliqué, tous les contrôles définis dans le ControlTemplate sont ajoutés à l’arborescence d’éléments visuels du contrôle personnalisé basé sur un modèle ou de la page basée sur un modèle.

L’exemple suivant montre l’affectation du CardViewControlTemplate à la propriété ControlTemplate de chaque objet 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>

Dans cet exemple, les contrôles du CardViewControlTemplate deviennent partie intégrante de l’arborescence d’éléments visuels pour chaque objet CardView. Étant donné que l’objet Frame racine pour le modèle de contrôle définit son BindingContext sur le parent basé sur un modèle, le Frame et ses enfants résolvent leurs expressions de liaison par rapport aux propriétés de chaque objet CardView.

Les captures d’écran suivantes montrent le CardViewControlTemplate appliqué aux trois objets CardView :

Captures d’écran d’objets CardView avec modèle, sur des iOS et Android

Important

Le moment auquel un ControlTemplate est appliqué à une instance de contrôle peut être détecté en substituant la méthode OnApplyTemplate dans le contrôle personnalisé basé sur un modèle ou la page basée sur un modèle. Pour plus d’informations, consultez Obtenir un élément nommé à partir d’un modèle.

Passer des paramètres avec TemplateBinding

L’extension de balisage TemplateBinding lie une propriété d’un élément qui se trouve dans un ControlTemplate à une propriété publique définie par la page basée sur un modèle ou le contrôle personnalisé basé sur un modèle. Quand vous utilisez un TemplateBinding, vous permettez aux propriétés sur le contrôle de faire office de paramètres du modèle. Ainsi, quand une propriété sur un contrôle personnalisé basé sur un modèle ou une page basée sur un modèle est définie, cette valeur est passée à l’élément sur lequel se trouve le TemplateBinding.

Important

L’expression TemplateBinding de balisage permet de supprimer la RelativeSource liaison du modèle de contrôle précédent et de remplacer les Binding expressions.

L’extension de balisage TemplateBinding définit les propriétés suivantes :

  • Path, de type string, le chemin de la propriété.
  • Mode, de type BindingMode, la direction dans laquelle les modifications se propagent entre la source et la cible.
  • Converter, de type IValueConverter, le convertisseur de valeur de liaison.
  • ConverterParameter, de type object, le paramètre du convertisseur de valeur de liaison.
  • StringFormat, de type string, le format de chaîne pour la liaison.

ContentProperty pour l’extension de balisage TemplateBinding est Path. Par conséquent, la partie « Path= » de l’extension de balisage peut être omise si le chemin est le premier élément de l’expression TemplateBinding. Pour plus d’informations sur l’utilisation de ces propriétés dans une expression de liaison, consultez Xamarin.Forms Liaison de données.

Avertissement

L’extension de balisage TemplateBinding doit être utilisée uniquement dans un ControlTemplate. Toutefois, si vous tentez d’utiliser une expression TemplateBinding en dehors d’un ControlTemplate, une erreur de génération ou une exception est levée.

L’exemple de code XAML suivant montre un ControlTemplate pour des objets CardView, qui utilise l’extension de balisage 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>

Dans cet exemple, l’extension de balisage TemplateBinding résout les expressions de liaison par rapport aux propriétés de chaque objet CardView. Les captures d’écran suivantes montrent le CardViewControlTemplate appliqué aux trois objets CardView :

Captures d’écran d’objets CardView modèles

Important

L’utilisation de l’extension de balisage TemplateBinding équivaut à affecter comme BindingContext de l’élément racine du modèle son parent basé sur un modèle avec l’extension de balisage RelativeSource, puis à résoudre les liaisons des objets enfants avec l’extension de balisage Binding. En fait, l’extension de balisage TemplateBinding crée un Binding dont la Source est RelativeBindingSource.TemplatedParent.

Appliquer un ControlTemplate avec un style

Les modèles de contrôle peuvent également être appliqués avec des styles. Pour ce faire, vous devez créer un style implicite ou explicite qui consomme le ControlTemplate.

L’exemple de code XAML suivant montre un style implicite qui consomme le 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>

Dans cet exemple, l’impliciteStyle est automatiquement appliqué à chaque CardView objet et définit la ControlTemplate propriété de chacun CardView sur CardViewControlTemplate.

Pour plus d’informations sur les styles, consultez Xamarin.Forms Styles.

Redéfinir l’interface utilisateur d’un contrôle

Quand un ControlTemplate est instancié et affecté à la propriété ControlTemplate d’un contrôle personnalisé dérivé de ContentView ou d’une page dérivée de ContentPage, la structure visuelle définie pour le contrôle ou la page personnalisé est remplacée par la structure visuelle définie dans le ControlTemplate.

Par exemple, le contrôle personnalisé CardViewUI définit son interface utilisateur à l’aide du code XAML suivant :

<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>

Toutefois, les contrôles qui composent cette interface utilisateur peuvent être remplacés en définissant une nouvelle structure visuelle dans un ControlTemplate et en l’affectant à la propriété ControlTemplate d’un objet 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>

Dans cet exemple, la structure visuelle de l’objet CardViewUI est redéfinie dans un ControlTemplate qui fournit une structure visuelle plus compacte adaptée à une liste condensée :

modèle Captures d’écran d’objets CardViewUI avec modèle, sur des objets CardViewUI iOS et Android

Remplacer le contenu dans un ContentPresenter

Un ContentPresenter peut être placé dans un modèle de contrôle afin de marquer l’emplacement où apparaîtra le contenu devant être affiché par le contrôle personnalisé basé sur un modèle ou la page basée sur un modèle. Le contrôle ou la page personnalisé qui consomme le modèle de contrôle définira ensuite le contenu devant être affiché par le ContentPresenter. Le diagramme suivant illustre un ControlTemplate pour une page qui contient plusieurs contrôles, notamment un ContentPresenter marqué par un rectangle bleu :

Modèle de contrôle pour un

Le code XAML suivant montre un modèle de contrôle nommé TealTemplate qui contient un ContentPresenter dans sa structure visuelle :

<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>

L’exemple suivant montre TealTemplate affecté à la propriété ControlTemplate d’une page dérivée de 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>

Au moment de l’exécution, quand TealTemplate est appliqué à la page, le contenu de la page est remplacé dans le ContentPresenter défini dans le modèle de contrôle :

Captures d’écran d’un objet de page modèle, sur iOS et Android

Obtenir un élément nommé à partir d’un modèle

Les éléments nommés dans un modèle de contrôle peuvent être récupérés à partir du contrôle personnalisé basé sur un modèle ou de la page basée sur un modèle. Ceci est possible avec la méthode GetTemplateChild, qui retourne l’élément nommé dans l’arborescence d’éléments visuels instancié ControlTemplate, s’il est trouvé. Sinon, nullest retourné.

Après qu’un modèle de contrôle a été instancié, la méthode du modèle OnApplyTemplate est appelée. La méthode GetTemplateChild doit donc être appelée à partir d’une substitution OnApplyTemplate dans la page ou le contrôle basé sur un modèle.

Important

La méthode GetTemplateChild doit uniquement être appelée après que la méthode OnApplyTemplate a été appelée.

Le code XAML suivant montre un modèle de contrôle nommé TealTemplate qui peut être appliqué à des pages dérivées de 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>

Dans cet exemple, l’élément Label est nommé et peut être récupéré dans le code de la page basée sur un modèle. Cela s’effectue en appelant la méthode GetTemplateChild à partir de la substitution OnApplyTemplate pour la page basée sur un modèle :

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

Dans cet exemple, l’objet Label nommé changeThemeLabel est récupéré une fois que le ControlTemplate a été instancié. changeThemeLabel est alors accessible et peut ensuite être manipulé par la classe AccessTemplateElementPage. Les captures d’écran suivantes montrent que le texte affiché par le Label a été modifié :

Captures d’écran de ContentPage avec modèle d’objet de page

Lier à un viewmodel

Un ControlTemplate peut lier des données à un viewmodel, même quand le ControlTemplate est lié au parent basé sur un modèle (l’instance de l’objet d’exécution à laquelle le modèle est appliqué).

L’exemple de code XAML suivant montre une page qui utilise un viewmodel nommé 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>

Dans cet exemple, le BindingContext de la page est défini sur une instance de PeopleViewModel. Ce viewmodel expose une collection People et un ICommand nommé DeletePersonCommand. Le StackLayout sur la page utilise une disposition pouvant être liée afin de lier aux données dans la collection People, et le ItemTemplate de la disposition pouvant être liée est définie sur la ressource PersonTemplate. Ce DataTemplate spécifie que chaque élément de la collection People sera affiché à l’aide d’un objet CardView. La structure visuelle de l’objet CardView est définie à l’aide d’un ControlTemplate nommé 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>

Dans cet exemple, l’élément racine du ControlTemplate est un objet Frame. L’objet Frame utilise l’extension de balisage RelativeSource pour affecter le parent basé sur un modèle comme BindingContext. Les expressions de liaison de l’objet Frame et ses enfants sont résolues par rapport à des propriétés CardView, en raison de l’héritage du BindingContext à partir de l’élément Frame racine. Les captures d’écran suivantes montrent la page qui affiche la collection People, qui se compose de trois éléments :

modèle Captures d’écran de trois objets CardView avec modèle

Alors que les objets du ControlTemplate lient aux propriétés sur son parent basé sur un modèle, le Button dans le modèle de contrôle lie à son parent basé sur un modèle et au DeletePersonCommand dans le viewmodel. Cela est dû au fait que la propriété Button.Command redéfinit sa source de liaison pour qu’elle corresponde au contexte de liaison de l’ancêtre dont le type de contexte de liaison est PeopleViewModel, qui est le StackLayout. La partie Path des expressions de liaison peut ensuite résoudre la propriété DeletePersonCommand. Toutefois, la propriété Button.CommandParameter ne modifie pas sa source de liaison ; au lieu de cela, elle l’hérite de son parent dans le ControlTemplate. Ainsi, la propriété CommandParameter est liée à la propriété CardTitle du CardView.

L’effet global des liaisons Button est que quand le Button est sollicité, le DeletePersonCommand de la classe PeopleViewModel est exécuté, la valeur de la propriété CardName étant passée au DeletePersonCommand. Cela entraîne la suppression du CardView spécifié de la disposition pouvant être liée :

Captures d’écran de deux objets CardView avec modèle

Pour plus d’informations sur les liaisons relatives, consultez Xamarin.Forms Liaisons relatives.