Partager via


Qu’est-ce que les styles et les modèles ?

Le style et la création de modèles Windows Presentation Foundation (WPF) font référence à une suite de fonctionnalités qui permettent aux développeurs et aux concepteurs de créer des effets visuellement attrayants et une apparence cohérente pour leur produit. Lors de la personnalisation de l’apparence d’une application, vous souhaitez un modèle de style et de création de modèles fort qui permet la maintenance et le partage de l’apparence au sein et entre les applications. WPF fournit ce modèle.

Une autre fonctionnalité du modèle de style WPF est la séparation de la présentation et de la logique. Les concepteurs peuvent travailler sur l’apparence d’une application à l’aide uniquement de XAML en même temps que les développeurs travaillent sur la logique de programmation à l’aide de C# ou de Visual Basic.

Cette vue d’ensemble se concentre sur les aspects de style et de création de modèles de l’application et ne traite pas des concepts de liaison de données. Pour plus d’informations sur la liaison de données, consultez Vue d’ensemble de la liaison de données.

Il est important de comprendre les ressources, qui permettent de réutiliser les styles et les modèles. Pour plus d’informations sur les ressources, consultez Vue d’ensemble des ressources XAML.

Échantillon

L’exemple de code fourni dans cette vue d’ensemble est basé sur une application de navigation de photos simple illustrée dans l’illustration suivante.

ListView stylisé

Cet exemple de photo simple utilise le style et la création de modèles pour créer une expérience utilisateur visuellement attrayante. L’exemple comporte deux éléments TextBlock et un contrôle ListBox lié à une liste d’images.

Pour accéder à l’exemple complet, consultez la page Présentation d’un exemple de création de style et de modèle.

Styles

Vous pouvez considérer un Style comme un moyen pratique d’appliquer un ensemble de valeurs de propriété à plusieurs éléments. Vous pouvez utiliser un style sur n’importe quel élément dérivé de FrameworkElement ou de FrameworkContentElement tel qu’un Window ou un Button.

La façon la plus courante de déclarer un style est une ressource dans la section Resources dans un fichier XAML. Étant donné que les styles sont des ressources, ils obéissent aux mêmes règles d’étendue que celles qui s’appliquent à toutes les ressources. En d’autres termes, lorsque vous déclarez un style affecte l’endroit où le style peut être appliqué. Par exemple, si vous déclarez le style dans l’élément racine de votre fichier XAML de définition d’application, le style peut être utilisé n’importe où dans votre application.

Par exemple, le code XAML suivant déclare deux styles pour une TextBlock, une application automatique à tous les éléments TextBlock et une autre qui doit être explicitement référencée.

<Window.Resources>
    <!-- .... other resources .... -->

    <!--A Style that affects all TextBlocks-->
    <Style TargetType="TextBlock">
        <Setter Property="HorizontalAlignment" Value="Center" />
        <Setter Property="FontFamily" Value="Comic Sans MS"/>
        <Setter Property="FontSize" Value="14"/>
    </Style>
    
    <!--A Style that extends the previous TextBlock Style with an x:Key of TitleText-->
    <Style BasedOn="{StaticResource {x:Type TextBlock}}"
           TargetType="TextBlock"
           x:Key="TitleText">
        <Setter Property="FontSize" Value="26"/>
        <Setter Property="Foreground">
            <Setter.Value>
                <LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">
                    <LinearGradientBrush.GradientStops>
                        <GradientStop Offset="0.0" Color="#90DDDD" />
                        <GradientStop Offset="1.0" Color="#5BFFFF" />
                    </LinearGradientBrush.GradientStops>
                </LinearGradientBrush>
            </Setter.Value>
        </Setter>
    </Style>
</Window.Resources>

Voici un exemple des styles déclarés ci-dessus utilisés.

<StackPanel>
    <TextBlock Style="{StaticResource TitleText}" Name="textblock1">My Pictures</TextBlock>
    <TextBlock>Check out my new pictures!</TextBlock>
</StackPanel>

Blocs de texte avec style

Pour plus d’informations, consultez Créer un style pour un contrôle.

ControlTemplates

Dans WPF, la ControlTemplate d’un contrôle définit l’apparence du contrôle. Vous pouvez modifier la structure et l’apparence d’un contrôle en définissant une nouvelle ControlTemplate et en l’affectant à un contrôle. Dans de nombreux cas, les modèles vous offrent suffisamment de flexibilité pour ne pas avoir à écrire vos propres contrôles personnalisés.

Chaque contrôle a un modèle par défaut affecté à la propriété Control.Template. Le modèle connecte la présentation visuelle du contrôle avec les fonctionnalités du contrôle. Étant donné que vous définissez un modèle en XAML, vous pouvez modifier l’apparence du contrôle sans écrire de code. Chaque modèle est conçu pour un contrôle spécifique, tel qu’un Button.

Généralement, vous déclarez un modèle en tant que ressource dans la section Resources d’un fichier XAML. Comme pour toutes les ressources, les règles d’étendue s’appliquent.

Les modèles de contrôle sont beaucoup plus impliqués qu’un style. Cela est dû au fait que le modèle de contrôle réécrit l’apparence visuelle de l’ensemble du contrôle, tandis qu’un style applique simplement les modifications de propriété au contrôle existant. Toutefois, étant donné que le modèle d’un contrôle est appliqué en définissant la propriété Control.Template, vous pouvez utiliser un style pour définir ou appliquer un modèle.

Les concepteurs vous permettent généralement de créer une copie d’un modèle existant et de le modifier. Par exemple, dans le concepteur WPF Visual Studio, sélectionnez un contrôle CheckBox, puis cliquez avec le bouton droit et sélectionnez Modifier le modèle>Créer une copie. Cette commande génère un style qui définit un modèle.

<Style x:Key="CheckBoxStyle1" TargetType="{x:Type CheckBox}">
    <Setter Property="FocusVisualStyle" Value="{StaticResource FocusVisual1}"/>
    <Setter Property="Background" Value="{StaticResource OptionMark.Static.Background1}"/>
    <Setter Property="BorderBrush" Value="{StaticResource OptionMark.Static.Border1}"/>
    <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
    <Setter Property="BorderThickness" Value="1"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type CheckBox}">
                <Grid x:Name="templateRoot" Background="Transparent" SnapsToDevicePixels="True">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto"/>
                        <ColumnDefinition Width="*"/>
                    </Grid.ColumnDefinitions>
                    <Border x:Name="checkBoxBorder" Background="{TemplateBinding Background}" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="1" VerticalAlignment="{TemplateBinding VerticalContentAlignment}">
                        <Grid x:Name="markGrid">
                            <Path x:Name="optionMark" Data="F1 M 9.97498,1.22334L 4.6983,9.09834L 4.52164,9.09834L 0,5.19331L 1.27664,3.52165L 4.255,6.08833L 8.33331,1.52588e-005L 9.97498,1.22334 Z " Fill="{StaticResource OptionMark.Static.Glyph1}" Margin="1" Opacity="0" Stretch="None"/>
                            <Rectangle x:Name="indeterminateMark" Fill="{StaticResource OptionMark.Static.Glyph1}" Margin="2" Opacity="0"/>
                        </Grid>
                    </Border>
                    <ContentPresenter x:Name="contentPresenter" Grid.Column="1" Focusable="False" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
                </Grid>
                <ControlTemplate.Triggers>
                    <Trigger Property="HasContent" Value="true">
                        <Setter Property="FocusVisualStyle" Value="{StaticResource OptionMarkFocusVisual1}"/>
                        <Setter Property="Padding" Value="4,-1,0,0"/>

... content removed to save space ...

La modification d’une copie d’un modèle est un excellent moyen d’apprendre comment fonctionnent les modèles. Au lieu de créer un modèle vide, il est plus facile de modifier une copie et de modifier quelques aspects de la présentation visuelle.

Pour obtenir un exemple, consultez Créer un modèle pour un contrôle.

TemplateBinding

Vous avez peut-être remarqué que la ressource de modèle définie dans la section précédente utilise l’extension de balisage TemplateBinding. Un TemplateBinding est une forme optimisée d’une liaison pour les scénarios de modèle, analogue à une liaison construite avec {Binding RelativeSource={RelativeSource TemplatedParent}}. TemplateBinding est utile pour lier des parties du modèle aux propriétés du contrôle. Par exemple, chaque contrôle a une propriété BorderThickness. Utilisez un TemplateBinding pour gérer l’élément du modèle affecté par ce paramètre de contrôle.

ContentControl et ItemsControl

Si un ContentPresenter est déclaré dans la ControlTemplate d’un ContentControl, le ContentPresenter est automatiquement lié aux propriétés ContentTemplate et Content. De même, une ItemsPresenter qui se trouve dans la ControlTemplate d’un ItemsControl est automatiquement liée aux propriétés ItemTemplate et Items.

Modèles de données

Dans cet exemple d’application, il existe un contrôle ListBox lié à une liste de photos.

<ListBox ItemsSource="{Binding Source={StaticResource MyPhotos}}"
         Background="Silver" Width="600" Margin="10" SelectedIndex="0"/>

Actuellement, cette ListBox ressemble à ce qui suit.

ListBox avant d’appliquer un modèle

La plupart des contrôles ont un certain type de contenu et ce contenu provient souvent de données auxquelles vous vous liez. Dans cet exemple, les données sont la liste des photos. Dans WPF, vous utilisez un DataTemplate pour définir la représentation visuelle des données. En fait, ce que vous placez dans un DataTemplate détermine à quoi ressemblent les données dans l’application rendue.

Dans notre exemple d’application, chaque objet Photo personnalisé a une propriété Source de type chaîne qui spécifie le chemin d’accès du fichier de l’image. Actuellement, les objets photo apparaissent sous forme de chemins d’accès aux fichiers.

public class Photo
{
    public Photo(string path)
    {
        Source = path;
    }

    public string Source { get; }

    public override string ToString() => Source;
}
Public Class Photo
    Sub New(ByVal path As String)
        Source = path
    End Sub

    Public ReadOnly Property Source As String

    Public Overrides Function ToString() As String
        Return Source
    End Function
End Class

Pour que les photos apparaissent sous forme d’images, vous créez un DataTemplate en tant que ressource.

<Window.Resources>
    <!-- .... other resources .... -->

    <!--DataTemplate to display Photos as images
    instead of text strings of Paths-->
    <DataTemplate DataType="{x:Type local:Photo}">
        <Border Margin="3">
            <Image Source="{Binding Source}"/>
        </Border>
    </DataTemplate>
</Window.Resources>

Notez que la propriété DataType est similaire à la propriété TargetType du Style. Si votre DataTemplate se trouve dans la section ressources, lorsque vous spécifiez la propriété DataType à un type et omettez un x:Key, le DataTemplate est appliqué chaque fois que ce type apparaît. Vous avez toujours la possibilité d’affecter le DataTemplate avec un x:Key, puis de le définir comme StaticResource pour les propriétés qui acceptent des types DataTemplate, telles que la propriété ItemTemplate ou la propriété ContentTemplate.

Essentiellement, le DataTemplate dans l’exemple ci-dessus définit que chaque fois qu’il existe un objet Photo, il doit apparaître comme un Image dans un Border. Avec cette DataTemplate, notre application ressemble maintenant à ceci.

Image photo

Le modèle de création de modèles de données fournit d’autres fonctionnalités. Par exemple, si vous affichez des données de collecte qui contiennent d’autres collections à l’aide d’un type HeaderedItemsControl tel qu’un Menu ou un TreeView, il existe le HierarchicalDataTemplate. Une autre fonctionnalité de création de modèles de données est la DataTemplateSelector, qui vous permet de choisir une DataTemplate à utiliser en fonction de la logique personnalisée. Pour plus d’informations, consultez Vue d’ensemble de la création de modèles de données, qui fournit une discussion plus approfondie sur les différentes fonctionnalités de création de modèles de données.

Déclencheurs

Un déclencheur définit des propriétés ou démarre des actions, telles qu’une animation, lorsqu’une valeur de propriété change ou lorsqu’un événement est déclenché. Style, ControlTemplateet DataTemplate ont toutes une propriété Triggers qui peut contenir un ensemble de déclencheurs. Il existe plusieurs types de déclencheurs.

PropertyTriggers

Une Trigger qui définit des valeurs de propriété ou démarre des actions en fonction de la valeur d’une propriété est appelée déclencheur de propriété.

** Pour montrer comment utiliser des déclencheurs de propriété, vous pouvez rendre chaque ListBoxItem partiellement transparent, sauf s'il est sélectionné. Le style suivant définit la valeur Opacity d’un ListBoxItem à 0.5. Toutefois, lorsque la propriété IsSelected est true, la Opacity est définie sur 1.0.

<Window.Resources>
    <!-- .... other resources .... -->

    <Style TargetType="ListBoxItem">
        <Setter Property="Opacity" Value="0.5" />
        <Setter Property="MaxHeight" Value="75" />
        <Style.Triggers>
            <Trigger Property="IsSelected" Value="True">
                <Trigger.Setters>
                    <Setter Property="Opacity" Value="1.0" />
                </Trigger.Setters>
            </Trigger>
        </Style.Triggers>
    </Style>
</Window.Resources>

Cet exemple utilise un Trigger pour définir une valeur de propriété, mais notez que la classe Trigger a également les propriétés EnterActions et ExitActions qui permettent à un déclencheur d’effectuer des actions.

Notez que la propriété MaxHeight du ListBoxItem est définie sur 75. Dans l’illustration suivante, le troisième élément est l’élément sélectionné.

ListView stylisé

EventTriggers et Storyboards

Un autre type de déclencheur est le EventTrigger, qui démarre un ensemble d’actions en fonction de l’occurrence d’un événement. Par exemple, les objets EventTrigger suivants spécifient que lorsque le pointeur de la souris entre dans le ListBoxItem, la propriété MaxHeight s’anime à une valeur de 90 au cours d'une période de 0.2 secondes. Lorsque la souris s’éloigne de l’élément, la propriété retourne à la valeur d’origine sur une période de 1 seconde. Notez comment il n’est pas nécessaire de spécifier une valeur To pour l’animation MouseLeave. Cela est dû au fait que l’animation est en mesure de suivre la valeur d’origine.

<Style.Triggers>
    <Trigger Property="IsSelected" Value="True">
        <Trigger.Setters>
            <Setter Property="Opacity" Value="1.0" />
        </Trigger.Setters>
    </Trigger>
    <EventTrigger RoutedEvent="Mouse.MouseEnter">
        <EventTrigger.Actions>
            <BeginStoryboard>
                <Storyboard>
                    <DoubleAnimation
                        Duration="0:0:0.2"
                        Storyboard.TargetProperty="MaxHeight"
                        To="90"  />
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger.Actions>
    </EventTrigger>
    <EventTrigger RoutedEvent="Mouse.MouseLeave">
        <EventTrigger.Actions>
            <BeginStoryboard>
                <Storyboard>
                    <DoubleAnimation
                        Duration="0:0:1"
                        Storyboard.TargetProperty="MaxHeight"  />
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger.Actions>
    </EventTrigger>
</Style.Triggers>

Pour plus d'informations, consultez l'aperçu des Storyboards .

Dans l’illustration suivante, la souris pointe vers le troisième élément.

Capture d’écran de l’exemple de mise en forme

MultiTriggers, DataTriggers et MultiDataTriggers

Outre Trigger et EventTrigger, il existe d’autres types de déclencheurs. MultiTrigger vous permet de définir des valeurs de propriété en fonction de plusieurs conditions. Vous utilisez DataTrigger et MultiDataTrigger lorsque la propriété de votre condition est liée aux données.

États visuels

Les contrôles sont toujours dans un état spécifique. Par exemple, lorsque la souris se déplace sur la surface d’un élément de contrôle, le contrôleur est considéré comme étant dans un état commun désigné MouseOver. Un contrôle sans état spécifique est considéré comme étant dans l’état commun Normal. Les états sont divisés en groupes, et les états mentionnés précédemment font partie du groupe d’états CommonStates. La plupart des contrôles ont deux groupes d’états : CommonStates et FocusStates. Pour chaque groupe d'états appliqué à un contrôle, un contrôle se trouve toujours dans un état de chaque groupe, comme CommonStates.MouseOver et FocusStates.Unfocused. Toutefois, un contrôle ne peut pas se trouver dans deux états différents au sein du même groupe, tels que CommonStates.Normal et CommonStates.Disabled. Voici une table des états que la plupart des contrôles reconnaissent et utilisent.

Nom de VisualState Nom de VisualStateGroup Descriptif
Normal CommonStates État par défaut.
MouseOver CommonStates Le pointeur de la souris est positionné sur le contrôleur.
Pressed CommonStates La commande est enfoncée.
Disabled CommonStates Le contrôle est désactivé.
Focused FocusStates Le contrôle a la priorité.
Unfocused FocusStates Le contrôle n’a pas la priorité.

En définissant un System.Windows.VisualStateManager sur l’élément racine d’un modèle de contrôle, vous pouvez déclencher des animations lorsqu’un contrôle entre dans un état spécifique. Le VisualStateManager déclare quelles combinaisons de VisualStateGroup et de VisualState observer. Lorsque le contrôleur entre dans un état surveillé, l’animation définie par le VisualStateManager est lancée.

Par exemple, le code XAML suivant surveille l’état CommonStates.MouseOver pour animer la couleur de remplissage de l’élément nommé backgroundElement. Lorsque le contrôle retourne à l’état CommonStates.Normal, la couleur de remplissage de l’élément nommé backgroundElement est restaurée.

<ControlTemplate x:Key="roundbutton" TargetType="Button">
    <Grid>
        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup Name="CommonStates">
                <VisualState Name="Normal">
                    <ColorAnimation Storyboard.TargetName="backgroundElement"
                                    Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
                                    To="{TemplateBinding Background}"
                                    Duration="0:0:0.3"/>
                </VisualState>
                <VisualState Name="MouseOver">
                    <ColorAnimation Storyboard.TargetName="backgroundElement"
                                    Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
                                    To="Yellow"
                                    Duration="0:0:0.3"/>
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>

        ...

Pour plus d’informations sur les storyboards, consultez Vue d’ensemble des storyboards.

Ressources et thèmes partagés

Une application WPF classique peut avoir plusieurs ressources d’interface utilisateur qui sont appliquées dans l’ensemble de l’application. Collectivement, cet ensemble de ressources peut être considéré comme le thème de l’application. WPF prend en charge l’empaquetage des ressources d’interface utilisateur en tant que thème à l’aide d’un dictionnaire de ressources encapsulé en tant que classe ResourceDictionary.

Les thèmes WPF sont définis à l’aide du mécanisme de style et de création de modèles que WPF expose pour personnaliser les visuels d’un élément.

Les ressources de thème WPF sont stockées dans des dictionnaires de ressources incorporés. Ces dictionnaires de ressources doivent être incorporés dans un assembly signé et peuvent être incorporés dans le même assembly que le code lui-même ou dans un assembly côte à côte. Pour PresentationFramework.dll, l'assembly qui contient des contrôles WPF, les ressources de thème se trouvent dans une série d’assemblages parallèles.

Le thème devient le dernier endroit à rechercher lors de la recherche du style d’un élément. En règle générale, la recherche commence par parcourir l’arborescence d’éléments qui recherche une ressource appropriée, puis recherche dans la collection de ressources de l’application, puis interroge le système. Cela permet aux développeurs d’applications de redéfinir le style d’un objet au niveau de l’arborescence ou de l’application avant d’atteindre le thème.

Vous pouvez définir des dictionnaires de ressources en tant que fichiers individuels qui vous permettent de réutiliser un thème sur plusieurs applications. Vous pouvez également créer des thèmes échangeables en définissant plusieurs dictionnaires de ressources qui fournissent les mêmes types de ressources, mais avec des valeurs différentes. La redéfinition de ces styles ou d’autres ressources au niveau de l’application est l’approche recommandée pour l’apparence d’une application.

Pour partager un ensemble de ressources, y compris des styles et des modèles, entre les applications, vous pouvez créer un fichier XAML et définir un ResourceDictionary qui inclut une référence à un fichier shared.xaml.

<ResourceDictionary.MergedDictionaries>
  <ResourceDictionary Source="Shared.xaml" />
</ResourceDictionary.MergedDictionaries>

Il s’agit du partage de shared.xaml, qui définit lui-même un ResourceDictionary qui contient un ensemble de ressources de style et de pinceau, qui permet aux contrôles d’une application d’avoir une apparence cohérente.

Pour plus d’informations, consultez Dictionnaires de ressources fusionnés.

Si vous créez un thème pour votre contrôle personnalisé, consultez la section Définition de ressources au niveau de l’élément de la page Vue d'ensemble de la création de contrôles.

Voir aussi