Modèles de contrôles
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
:
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
:
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 :
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 :
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 :
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, null
est 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é :
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
:
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.