Partager via


Modèles de contrôles

Parcourez l’exemple. Parcourir l'exemple

Les modèles de contrôle .NET MAUI (.NET Multi-Platform App UI) vous permettent de définir la structure visuelle de contrôles personnalisés dérivés de ContentView et de pages dérivées de ContentPage. 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 l’interface utilisateur commune 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 contrôles personnalisés dérivés de ContentView, consultez 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 d’objets constitue 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 votre dictionnaire de ressources au niveau de l’application, vous pouvez utiliser le modèle 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 Dictionnaires de ressources.

L’exemple de code XAML suivant montre un ControlTemplate pour des objets CardView :

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

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 de contrôles pour définir la structure visuelle d’un objet CardView. 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 balisage RelativeSource, consultez 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 de CardViewControlTemplate à la propriété ControlTemplate de deux objets CardView :

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

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.

La capture d’écran suivante montre l’application de CardViewControlTemplate aux objets CardView :

Capture d’écran de deux objets CardView basés sur un modèle.

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 de balisage TemplateBinding permet de supprimer la liaison RelativeSource du modèle de contrôle précédent et remplace les expressions Binding.

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

Dans cet exemple, l’extension de balisage TemplateBinding résout les expressions de liaison par rapport aux propriétés de chaque objet CardView. La capture d’écran suivante montre l’application de CardViewControlTemplate aux objets CardView :

Capture d’écran d’objets CardView basés sur un modèle.

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

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

Dans cet exemple, le Style implicite est appliqué automatiquement à chaque objet CardView, et affecte CardViewControlTemplate comme propriété ControlTemplate de chaque CardView.

Pour plus d’informations sur les styles, consultez 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://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="ControlTemplateDemos.Controls.CardViewUI"
             x:Name="this">
    <Frame BindingContext="{x:Reference this}"
           BackgroundColor="{Binding CardColor}"
           BorderColor="{Binding BorderColor}"
           ...>
        <!-- UI objects that define the CardView visual structure -->           
    </Frame>
</ContentView>

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

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 :

Capture d’écran d’objets CardViewUI basés sur un modèle.

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 une ContentPage.

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 RowDefinitions="0.1*, 0.8*, 0.1*">
        <BoxView Color="Teal" />
        <Label Margin="20,0,0,0"
               Text="{TemplateBinding HeaderText}"
               ... />
        <ContentPresenter Grid.Row="1" />
        <BoxView Grid.Row="2"
                 Color="Teal" />
        <Label x:Name="changeThemeLabel"
               Grid.Row="2"
               Margin="20,0,0,0"
               Text="Change Theme"
               ...>
            <Label.GestureRecognizers>
                <TapGestureRecognizer Tapped="OnChangeThemeLabelTapped" />
            </Label.GestureRecognizers>
        </Label>
        <controls:HyperlinkLabel Grid.Row="2"
                                 Margin="0,0,20,0"
                                 Text="Help"
                                 Url="https://zcusa.951200.xyz/dotnet/maui/"
                                 ... />
    </Grid>
</ControlTemplate>

L’exemple suivant montre TealTemplate affecté à la propriété ControlTemplate d’une page dérivée de ContentPage :

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

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 :

Capture d’écran d’objet de page basé sur un modèle.

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"
               Text="Change Theme"
               ...>
            <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. La capture d’écran suivante montre que le texte affiché par Label a été modifié :

Capture d’écran d’objet de page basé sur un modèle ayant été modifié.

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

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

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

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}"
           ...>
        <!-- UI objects that define the CardView visual structure -->           
    </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. La capture d’écran suivante montre la page affichant la collection People :

Capture d’écran de trois objets CardView basés sur un modèle et liés à un viewmodel.

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. Le CardView spécifié est alors supprimé de la disposition pouvant être liée.

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