Maîtriser les Templates XAML dans WPF : ControlTemplate, DataTemplate et plus

Cet article explore les mécanismes fondamentaux de WPF à travers l'utilisation des templates XAML, en partant de concepts simples pour aboutir à des structures plus complexes.

Le Défi du Bouton : Personnalisation Visuelle

Modifier l'apparence par défaut d'un contrôle comme Button peut s'avérer complexe. La méthode privilégiée pour remodeler un contrôle consiste à définir son ControlTemplate via un Style. Bien que le Style ne soit pas strictement nécessaire pour cette personnalisation, il facilite la réutilisation.


<Style x:Key="CustomButtonStyle" TargetType="Button">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="Button">
                <Grid Width="100" Height="40">
                    <Border Background="Aqua" />
                    <TextBlock Text="Salut, WPF !" HorizontalAlignment="Center" VerticalAlignment="Center" />
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Il est possible d'appliquer un ControlTemplate directement via la propriété Button.Template, mais l'utilisation d'un Style offre une meilleure gestion de la réutilisabilité.

Interaction et Propriétés Dépendances : Le Lien Vital

Un bouton dont l'apparence est définie par un ControlTemplate devient statique ; il ne répond plus aux propriétés comme Background définies lors de sa déclaration. Ce comportement s'apparente à une fonction dont les paramètres ne sont jamais utilisés.


private void PerformAction(string data)
{
    Console.WriteLine("Operation executed.");
    // Le paramètre 'data' est déclaré mais potentiellement non utilisé.
}

Pour pallier cette limitation, WPF introduit le concept de Propriétés Dépendances (DependencyProperty). Ces propriétés agissent comme des paramètres pour les éléments au sein d'un ControlTemplate, leur conférant une signification et permettant l'interaction avec les propriétés du contrôle hôte.

Pour lier une propriété du contrôle à un élément de son ControlTemplate, on utilise la syntaxe {TemplateBinding PropertyName}. Par exemple, pour lier la propriété Background du bouton à celle du Border dans le template :


<Style x:Key="CustomButtonStyle" TargetType="Button">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="Button">
                <Grid>
                    <Border Background="{TemplateBinding Background}" CornerRadius="5" />
                    <TextBlock Text="Salut, WPF !" HorizontalAlignment="Center" VerticalAlignment="Center" />
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Une alternative plus générique, bien que moins concise, utilise {Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=PropertyName}. Cette syntaxe peut s'avérer utile dans des scénarios plus complexes.


<Style x:Key="CustomButtonStyle" TargetType="Button">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="Button">
                <Grid>
                    <Border Background="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=Background}" CornerRadius="5" />
                    <TextBlock Text="Salut, WPF !" HorizontalAlignment="Center" VerticalAlignment="Center" />
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Paramétrage du Contenu

La propriété Content d'un bouton peut être liée à des éléments internes. Si l'IntelliSense de Visual Studio ne suggère pas immédiatement la liaison du Content à la propriété Text d'un TextBlock, cela est dû à la nature de Content (type object) et Text (type string). Bien que la liaison directe fonctionne souvent par conversion implicite, la compréhension de cette différence est clé.


<Style x:Key="CustomButtonStyle" TargetType="Button">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="Button">
                <Grid>
                    <Border Background="{TemplateBinding Background}" CornerRadius="5" />
                    <TextBlock
                        HorizontalAlignment="Center"
                        VerticalAlignment="Center"
                        Text="{TemplateBinding Content}" />
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

<Button
    Width="100"
    Height="40"
    Background="LightGreen"
    Content="Cliquez ici !"
    Style="{StaticResource CustomButtonStyle}" />

Le Rôle de ContentPresenter

Pour une gestion plus robuste du contenu, notamment pourr permettre l'imbrication de contrôles et de types d'objets variés, le ContentPresenter est essentiel. Il agit comme un conteneur qui affiche le contenu délégué par la propriété Content du contrôle parent. La propriété Content d'un bouton pointe ultimement vers le Content du ContentPresenter au sein de son ControlTemplate.


<Style x:Key="CustomButtonStyle" TargetType="Button">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="Button">
                <Grid>
                    <Border Background="{TemplateBinding Background}" CornerRadius="5" />
                    <ContentPresenter
                        HorizontalAlignment="Center"
                        VerticalAlignment="Center"
                        Content="{TemplateBinding Content}" />
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

<Button
    Width="100"
    Height="100"
    Background="LightGreen"
    Style="{StaticResource CustomButtonStyle}">
    <TextBlock Text="Mon Bouton" />
</Button>

Gestion des Objets Personnalisés et DataTemplate

Lorsque le Content est un objet personnalisé, son affichage par défaut dépend de sa méthode ToString(). Pour personnaliser la manière dont ces objets sont rendus, on utilise un DataTemplate, souvent appliqué via la propriété ContentTemplate du contrôle parent (comme un Button) et géré par le ContentPresenter.

Définissons une classe Personne :


public class Personne
{
    public string Nom { get; set; }
    public int Age { get; set; }

    public override string ToString()
    {
        return $"{Nom} ({Age} ans)";
    }
}

Pour personnaliser l'affichage de Personne dans un bouton :


<!-- Assurez-vous que le Style ci-dessus utilise ContentPresenter -->

<Button
    Width="100"
    Height="100"
    Background="LightGreen"
    Loaded="Button_Loaded"
    Style="{StaticResource CustomButtonStyle}">

    <!-- Définition du DataTemplate pour Personne -->
    <Button.ContentTemplate>
        <DataTemplate DataType="{x:Type local:Personne}"> <!-- Remplacez 'local' par votre namespace -->
            <StackPanel>
                <TextBlock Text="{Binding Nom}" FontWeight="Bold" />
                <TextBlock Text="{Binding Age, StringFormat=\{0\} ans}" />
            </StackPanel>
        </DataTemplate>
    </Button.ContentTemplate>
</Button>

Dans le code-behind :


private void Button_Loaded(object sender, RoutedEventArgs e)
{
    var button = sender as Button;
    if (button != null)
    {
        button.Content = new Personne() { Nom = "Alice", Age = 30 };
    }
}

Le DataTemplate est fondamental pour la présentation des données. Il est utilisé non seulement pour le ContentTemplate mais aussi pour le ItemTemplate dans les contrôles de liste comme ItemsControl et ListBox.

Contrôles de Collection : ItemsControl et ListBox

ItemsControl et ListBox utilisent ItemTemplate pour définir l'apparence de chaque élément de la collection. Le ItemsPanel, quant à lui, spécifie le layout des éléments (par exemple, WrapPanel ou StackPanel).


public class ViewModelPrincipal
{
    public List<Personne> Personnes { get; set; } = new List<Personne>();

    public ViewModelPrincipal()
    {
        Personnes.Add(new Personne { Nom = "Bob", Age = 25 });
        Personnes.Add(new Personne { Nom = "Charlie", Age = 22 });
        Personnes.Add(new Personne { Nom = "David", Age = 28 });
    }
}


<!-- Assurez-vous que le DataContext est lié à ViewModelPrincipal -->

<ItemsControl ItemsSource="{Binding Personnes}">
    <ItemsControl.ItemTemplate>
        <DataTemplate DataType="{x:Type local:Personne}">
            <StackPanel Margin="5">
                <TextBlock Text="{Binding Nom}" />
                <TextBlock Text="{Binding Age}" FontStyle="Italic" />
            </StackPanel>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <WrapPanel Orientation="Horizontal" />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
</ItemsControl>

DataGrid et CellTemplate

Le DataGrid offre une flexibilité similaire pour la personnalisation des cellules via DataGridTemplateColumn et son CellTemplate.


<DataGrid ItemsSource="{Binding Personnes}" AutoGenerateColumns="False">
    <DataGrid.Columns>
        <DataGridTemplateColumn Header="Détails">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate DataType="{x:Type local:Personne}">
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="{Binding Nom, StringFormat=Nom: {0}}" Margin="0,0,5,0" />
                        <TextBlock Text="{Binding Age, StringFormat=Age: \{0\}}" />
                    </StackPanel>
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
    </DataGrid.Columns>
</DataGrid>

Ces templates permettent d'afficher des informations contextuelles, des indicateurs de statut, ou toute autre visualisation pertinente directement dans la grille.

Tableau Récapitulatif des Templates

Nom de la Propriété Type de Template Usage Principal
Template ControlTemplate Définit la structure visuelle et le comportement d'un contrôle. Fondamental pour la réutilisabilité et la personnalisation des contrôles.
ContentTemplate DataTemplate Appliqué au ContentPresenter pour définir comment le contenu d'un ContentControl (comme Button) doit être rendu.
ItemTemplate DataTemplate Utilisé par les contrôles de collection (ItemsControl, ListBox) pour spécifier l'apparence de chaque élément individuel.
ItemsPanel ItemsPanelTemplate Définit le conteneur de layout pour les éléments d'un contrôle de collection (par exemple, WrapPanel, StackPanel).
CellTemplate DataTemplate Spécifique au DataGrid, permet de personnaliser l'affichage du contenu d'une cellule via un DataGridTemplateColumn.

Étiquettes: WPF XAML ControlTemplate DataTemplate DependencyProperty

Publié le 4 juin à 06h38