Qu’est-ce que les styles et les modèles ? (WPF .NET)

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.

Exemple

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

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

Pour accéder à l’exemple complet, consultez la page Introduction to Styling and Templating Sample (Présentation d’un exemple de création de style et de modèle).

Styles

Vous pouvez considérer un Style 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é ou FrameworkElement FrameworkContentElement tel qu’un Window ou un Button.

La façon la plus courante de déclarer un style est une ressource dans la Resources section d’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 un TextBlock, un automatiquement appliqué à tous les TextBlock éléments, et un autre qui doit être référencé explicitement.

<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, le ControlTemplate contrôle définit l’apparence du contrôle. Vous pouvez modifier la structure et l’apparence d’un contrôle en définissant un nouveau ControlTemplate contrôle 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 Resources section 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 définir 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 CheckBox contrôle, 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. Il TemplateBinding s’agit d’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 BorderThickness propriété. Utilisez un TemplateBinding pour gérer l’élément dans le modèle affecté par ce paramètre de contrôle.

ContentControl et ItemsControl

Si une ContentPresenter valeur est déclarée dans l’un ControlTemplate d’entre ellesContentControl, elle ContentPresenter est automatiquement liée aux propriétés et Content aux ContentTemplate propriétés. De même, un ItemsPresenter élément qui se trouve dans l’une ControlTemplate d’elles ItemsControl est automatiquement lié aux propriétés et Items aux ItemTemplate propriétés.

DataTemplates

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

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

Ceci ListBox ressemble actuellement à ce qui suit.

ListBox avant d’appliquer un modèle

La plupart des contrôles ont un type de contenu, et ce contenu provient souvent des données que vous liez à ces contrôles. Dans cet exemple, les données sont la liste de 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 personnalisé Photo a une Source propriété 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.

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 une DataTemplate 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 DataType propriété est similaire à la TargetType propriété du Style. Si vous DataTemplate êtes dans la section ressources, lorsque vous spécifiez la DataType propriété à un type et omettez un x:Key, il DataTemplate est appliqué chaque fois que ce type apparaît. Vous avez toujours la possibilité d’affecter l’objet DataTemplate avec un x:Key objet, puis de le définir comme étant une StaticResource propriété qui prend DataTemplate des types, comme la ItemTemplate propriété ou la ContentTemplate propriété.

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

Image photo

Le modèle de création de modèles fournit d’autres fonctionnalités. Par exemple, si vous affichez des données de collection qui contiennent d’autres collections à l’aide d’un HeaderedItemsControl type tel qu’un Menu ou un TreeView, il existe le HierarchicalDataTemplate. Une autre fonctionnalité de création de modèles de données est celle DataTemplateSelectorqui vous permet de choisir une DataTemplate utilisation basée sur une logique personnalisée. Pour plus d’informations, consultez l’article Vue d'ensemble des 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, par exemple une animation, lorsqu’une valeur de propriété change ou lorsqu’un événement est déclenché. Style, ControlTemplateet DataTemplate tous ont une Triggers propriété qui peut contenir un ensemble de déclencheurs. Il existe plusieurs types de déclencheurs.

PropertyTriggers

Un 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 élément partiellement transparent, sauf s’il est sélectionné. Le style suivant définit la Opacity valeur d’un ListBoxItem à 0.5. Toutefois, lorsque la IsSelected propriété est truedéfinie 1.0sur .Opacity

<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 une Trigger valeur de propriété pour définir une valeur de propriété, mais notez que la Trigger classe a également les propriétés et ExitActions les EnterActions propriétés qui permettent à un déclencheur d’effectuer des actions.

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

ListView styleté

EventTriggers et tables de montage séquentiel

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 suivants EventTrigger spécifient que lorsque le pointeur de la souris entre dans le ListBoxItem, la MaxHeight propriété s’anime sur une valeur de plus d’une 90 0.2 seconde période. Lorsque la souris s’éloigne de l’élément, la propriété retourne à sa valeur d’origine pendant une période de 1 seconde. Notez comment il n’est pas nécessaire de spécifier une To valeur pour l’animation MouseLeave . L’animation est en effet capable d’effectuer le suivi de 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 la vue d’ensemble des storyboards.

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

Capture d’écran de l’exemple de style

MultiTriggers, DataTriggers et MultiDataTriggers

En plus Trigger de 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 contrôle, le contrôle est considéré comme étant dans un état commun de 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 CommonStatesd’états. La plupart des contrôles ont deux groupes d’états : CommonStates et FocusStates. De chaque groupe d’états appliqué à un contrôle, un contrôle est toujours dans un état de chaque groupe, tel que 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 VisualState Nom VisualStateGroup Description
Normal CommonStates État par défaut.
MouseOver CommonStates Le pointeur de souris est positionné sur le contrôle.
Pressed CommonStates Le contrôle est enfoncé.
Disabled CommonStates Le contrôle est désactivé.
Focused FocusStates Le contrôle a le focus.
Unfocused FocusStates Le contrôle n’a pas le focus.

En définissant un System.Windows.VisualStateManager é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 et VisualStateGroup VisualState regarder. Lorsque le contrôle entre dans un état espion, l’animation définie par le VisualStateManager contrôle est démarré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 ResourceDictionary classe.

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 bien dans un assembly parallèle. Pour PresentationFramework.dll, l’assembly qui contient des contrôles WPF, les ressources de thème se trouvent dans une série d’assemblys côte à côte.

Le thème est alors le dernier emplacement à consulter 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 permutables 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, notamment des styles et des modèles, entre les applications, vous pouvez créer un fichier XAML et définir une ResourceDictionary référence à un shared.xaml fichier.

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

Il s’agit du partage de shared.xaml, qui définit lui-même un ResourceDictionary 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 des ressources au niveau du thème de la vue d’ensemble de la création du contrôle.

Voir aussi