I. Traduction▲
Cet article est la traduction la plus fidèle possible de l'article original de Josh Smith, A Guided Tour of WPF - Part 5 (Styles).
II. Introduction▲
Cet article est le cinquième d'une série d'introduction à Windows Presentation Foundation. Dans l'article précédent, nous avons étudié les modèles de données et les déclencheurs pour voir la façon dont le rendu des objets de données peut être décrit en XAML. Dans cet article, nous examinerons les styles et la manière dont ils sont utilisés dans l'application de démonstration WPF Horse Race (disponible en téléchargement dans le premier article de la série).
Tout comme les autres articles de la série, cet article ne couvre son sujet de manière exhaustive. Au lieu de cela, nous examinerons suffisamment les bases pour que nous puissions voir comment ces fonctionnalités sont mises à profit dans l'application de démonstration. Si vous voulez en savoir plus sur les façons dont les styles peuvent être utilisés, reportez-vous à la section Liens externes pour plus d'informations.
III. Contexte▲
WPF emprunte de nombreux concepts aux mondes de la programmation Web et desktop. La séparation de la disposition de l'interface utilisateur du comportement (c'est-à-dire XML vs code-behind) est un coup de chapeau à ASP.NET. Le vaste ensemble d'API évènementielles pour un contrôle détaillé sur l'interaction utilisateur n'est pas sans rappeler le modèle de programmation Windows Forms. WPF peut être considéré comme un condensé des meilleures fonctionnalités de diverses plates-formes d'interfaces utilisateur, ainsi qu'une large gamme de nouvelles fonctionnalités.
Une contribution majeure à WPF par le monde du développement Web a été le concept de styles. L'application de styles dans WPF est un peu similaire à la façon dont le Cascading Style Sheets (CSS) est utilisé par les développeurs Web. L'idée de base de l'application de style en WPF, c'est que vous pouvez définir un objet Style qui est appliqué à un élément de type spécifique dans l'interface utilisateur, tel qu'un Button ou TextBox. Ce style peut être appliqué à chaque instance du type d'élément, ou vous pouvez choisir de l'appliquer à certaines instances. L'application de style dans WPF constitue un simple moyen pratique d'appliquer les paramètres de propriété à un ou plusieurs éléments visuels.
IV. Style vs thème▲
Avant de commencer à rentrer dans les styles, il est important d'établir une distinction entre les styles et les thèmes. Les systèmes d'exploitation ont des «thèmes», les applications WPF ont des «styles». Les thèmes sont un concept au niveau du système d'exploitation : tels que les thèmes bleu, vert et argent vus dans Windows XP, ou le thème Aero dans Vista. Les applications WPF détectent automatiquement le thème actuel du système d'exploitation et utilisent cette palette de couleurs partout où c'est possible, et il est même possible de choisir par programmation quel thème utiliser.
Un style n'existe que dans une application WPF, ou simplement une fenêtre dans une application WPF, ou même, d'ailleurs, seulement une partie d'une fenêtre. Les styles ne peuvent être appliqués à rien en dehors de l'application WPF dans laquelle ils existent, ni ne peuvent ils être appliqués à tous les contrôles Windows Forms hébergés au sein d'une fenêtre WPF.
Pour plus d'informations sur le support de thème dans WPF, consultez la section Liens externes à la fin de cet article.
V. La classe Style▲
Toute l'infrastructure d'application de style dans WPF est basée sur la classe Style. Il a un groupe relativement restreint de membres public, ce qui rend facile la compréhension du fonctionnement de l'application de style. Les instances de la classe Style sont presque toujours créées en XAML, mais il est possible de les créer par le code si nécessaire.
Voici quelques-unes des propriétés de Style dont nous verrons l'utilisation ultérieurement :
- Resources - est un ResourceDictionary où vous pouvez mettre des objets utilisés que dans le Style, tels que des pinceaux, des convertisseurs de valeur, etc. ;
- Setters - une collection d'objets Setter et EventSetter qui appliquent des valeurs aux propriétés, ou attribuent des gestionnaires d'événements. Ceci est la propriété de contenu de la classe Style qui la rend très facile à utiliser en XAML ;
- TargetType - indique le type de l'élément sur lequel le Style sera appliqué, tel qu'un TreeView ou Button.
Les styles créés dans l'application de démo WPF Horse Race sont très simples. Il existe d'autres propriétés communes de la classe Style qu'ils n'utilisent pas, tels que :
- BasedOn - permet l'héritage du style. Vous pouvez dériver un Style d'un autre Style pour créer des personnalisations sans dupliquer le XAML du Style de base ;
- Triggers - tout comme dans les DataTemplates vus dans l'article précédent de cette série, cette propriété contient une collection de déclencheurs qui peuvent être utilisés de façon conditionnelle pour appliquer les valeurs aux propriétés.
Un Style peut être appliqué à n'importe quel objet qui dérive de FrameworkElement ou FrameworkContentElement, les deux exposant une propriété publique nommée Style.
VI. Sans styles▲
Si WPF ne fournissait pas un moyen pour styliser les éléments, vous auriez à vous creuser la tête afin seulement d'imaginer un look & feel distinctif et permettant d'identifier instantanément votre marque pour l'interface utilisateur d'une application. Créer une cohérence visuelle pour toutes les fenêtres d'une application se transformerait rapidement en un cauchemar de maintenance, en particulier une fois qu'il deviendrait nécessaire d'apporter des changements à certains aspects du style visuel.
Par exemple, considérez le code XAML suivant :
<Border
BorderBrush
=
"Black"
BorderThickness
=
"2"
>
<StackPanel>
<TextBlock
Background
=
"Tan"
Margin
=
"2,4"
>
Bike</TextBlock>
<TextBlock
Background
=
"Tan"
Margin
=
"2,4"
>
Car</TextBlock>
<TextBlock
Background
=
"Tan"
Margin
=
"2,4"
>
Truck</TextBlock>
</StackPanel>
</Border>
Ce balisage simple se traduit en quelque chose qui ressemble à ceci :
Supposons que l'application que nous construisons a l'habitude d'appliquer une règle qui dit que les TextBlocks doivent toujours avoir une couleur marron sur fond beige, mais qu'un jour un gros bonnet de notre société fictive décide que le marron sur fond beige n'est plus une bonne couleur. Désormais, tous les TextBlocks devraient avoir une couleur de fond gris clair.
Si notre application n'avait que les trois TextBlocks vus dans l'extrait ci-dessus, cela ne poserait pas trop de problèmes. Tout ce que nous aurions à faire serait de mettre à jour ces trois paramètres de propriété :
<Border
BorderBrush
=
"Black"
BorderThickness
=
"2"
Margin
=
"10"
>
<StackPanel>
<TextBlock
Background
=
"LightGray"
Margin
=
"2,4"
>
Bike</TextBlock>
<TextBlock
Background
=
"LightGray"
Margin
=
"2,4"
>
Car</TextBlock>
<TextBlock
Background
=
"LightGray"
Margin
=
"2,4"
>
Truck</TextBlock>
</StackPanel>
</Border>
Mais, si notre application contenait des centaines voire des milliers de TextBlocks, alors nous serions en difficulté. Tout à coup, nous serions désireux qu'il y eût un moyen facile de définir la propriété Background de chaque TextBlock à gris clair.
VII. Avec styles▲
Heureusement, il existe un moyen facile de définir des propriétés sur un certain nombre d'éléments : « styles ». Voyons comment un Style peut être utilisé pour résoudre le problème décrit dans la section précédente. Voici une façon de le faire :
<Border
BorderBrush
=
"Black"
BorderThickness
=
"2"
>
<StackPanel>
<StackPanel.Resources>
<Style
x
:
Key
=
"TxtBlkStyle"
TargetType
=
"{x:Type TextBlock}"
>
<Setter
Property
=
"Background"
Value
=
"LightGray"
/>
<Setter
Property
=
"Margin"
Value
=
"2,4"
/>
</Style>
</StackPanel.Resources>
<TextBlock
Style
=
"{StaticResource TxtBlkStyle}"
>
Bike</TextBlock>
<TextBlock
Style
=
"{StaticResource TxtBlkStyle}"
>
Car</TextBlock>
<TextBlock
Style
=
"{StaticResource TxtBlkStyle}"
>
Truck</TextBlock>
</StackPanel>
</Border>
Le XAML vu ci-dessus crée un Style dans la collection Resources du StackPanel. Ce Style cible des éléments de type TextBlock, et définit leurs propriétés Background et Margin. Notez que le Style a une clé, 'TxtBlkStyle'. La propriété Style sur chaque TextBlock est alors explicitement définie pour faire référence à ce Style. Il en résulte l'interface utilisateur suivant :
Il s'avère qu'il existe un moyen encore plus facile d'accomplir cette tâche. Si vous ne donnez pas une clé à un Style, il sera automatiquement appliqué à tous les éléments dont le type correspond au TargetType du Style. Voici un exemple :
<Border
BorderBrush
=
"Black"
BorderThickness
=
"2"
>
<StackPanel>
<StackPanel.Resources>
<Style
TargetType
=
"{x:Type TextBlock}"
>
<Setter
Property
=
"Background"
Value
=
"LightGray"
/>
<Setter
Property
=
"Margin"
Value
=
"2,4"
/>
</Style>
</StackPanel.Resources>
<TextBlock>
Bike</TextBlock>
<TextBlock>
Car</TextBlock>
<TextBlock>
Truck</TextBlock>
</StackPanel>
</Border>
Les solutions vues jusqu'à présent ne résolvent pas vraiment le problème dans son ensemble. Si nous voulons que l'ensemble de l'application contienne des TextBlocks gris clair, nous avons besoin de déplacer notre Style à un endroit plus élevé dans l'arborescence des ressources, tel que les ressources de l'Application (en savoir plus à ce sujet ici et ici). De cette façon, tous les TextBlocks dans l'application seront en mesure d'utiliser le Style que nous avons créé. Voici un exemple de cette technique :
<!-- App.xaml -->
<Application
x
:
Class
=
"WPF_Test.MyApp"
xmlns
=
"http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns
:
x
=
"http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri
=
"Window1.xaml"
>
<Application.Resources>
<Style
TargetType
=
"{x:Type TextBlock}"
>
<Setter
Property
=
"Background"
Value
=
"LightGray"
/>
<Setter
Property
=
"Margin"
Value
=
"2,4"
/>
</Style>
</Application.Resources>
</Application>
<!--Somewhere inside of SomeWindow.xaml -->
<Border
BorderBrush
=
"Black"
BorderThickness
=
"2"
>
<StackPanel>
<TextBlock>
Bike</TextBlock>
<TextBlock>
Car</TextBlock>
<TextBlock>
Truck</TextBlock>
</StackPanel>
</Border>
Comme la collection des ressources de l'Application est à l'endroit le plus visible pour mettre un style, tous les TextBlocks dans chaque fenêtre de l'application vont utiliser ce style. Il est possible de remplacer ce Style dans n'importe quelle fenêtre ou n'importe où dans l'arborescence des éléments d'une fenêtre, mais par défaut, ce Style sera utilisé par tous les TextBlocks dans l'application.
VIII. Comment le WPF Horse Race utilise les styles▲
L'application de démo WPF Horse crée deux Styles. L'un d'eux est utilisé pour affecter l'élément Border dans le modèle de données pour la classe RaceHorse (comme vu dans l'article précédent de cette série). L'autre fournit des valeurs communes pour les propriétés de TextBlocks dans la zone « CommandStrip » de l'application, vers le bas de la fenêtre.
VIII-A. Style de bordure du « race pit »▲
Si l'élément racine Border du modèle de données de RaceHorse n'avait pas un Style qui lui était appliqué, l'interface utilisateur ressemblerait à ceci :
Avec le Style appliqué, elle ressemble à ceci :
Dans le fichier RacePitBorderStyle.xaml, vous trouverez un ResourceDictionary qui contient un Style dont la clé est 'RacePitBorderStyle'. Ce fichier contient le code XAML suivant :
<!-- This resource dictionary contains the Style applied to
each horse's race pit. -->
<ResourceDictionary
xmlns
=
"http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns
:
x
=
"http://schemas.microsoft.com/winfx/2006/xaml"
>
<Style
x
:
Key
=
"RacePitBorderStyle"
TargetType
=
"Border"
>
<Style.Resources>
<LinearGradientBrush
x
:
Key
=
"BackBrush"
StartPoint
=
"0.5,0"
EndPoint
=
"0.5,1"
>
<GradientStop
Color
=
"#88000000"
Offset
=
"0.1"
/>
<GradientStop
Color
=
"#CC000000"
Offset
=
"0.9"
/>
</LinearGradientBrush>
<LinearGradientBrush
x
:
Key
=
"BorderBrush"
StartPoint
=
"0.5,0"
EndPoint
=
"0.5,1"
>
<GradientStop
Color
=
"#18000000"
Offset
=
"0.1"
/>
<GradientStop
Color
=
"#08000000"
Offset
=
"0.9"
/>
</LinearGradientBrush>
</Style.Resources>
<Setter
Property
=
"Background"
Value
=
"{StaticResource BackBrush}"
/>
<Setter
Property
=
"BorderBrush"
Value
=
"{StaticResource BorderBrush}"
/>
<Setter
Property
=
"BorderThickness"
Value
=
"1"
/>
<Setter
Property
=
"CornerRadius"
Value
=
"8"
/>
<Setter
Property
=
"Margin"
Value
=
"2,4"
/>
</Style>
</ResourceDictionary>
Ce Style est appliqué à l'élément racine Border dans le modèle de données de RaceHorse, comme on le voit ci-dessous :
<!-- RaceHorseDataTemplate.xaml -->
<ResourceDictionary
xmlns
=
"http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns
:
x
=
"http://schemas.microsoft.com/winfx/2006/xaml"
xmlns
:
local
=
"clr-namespace:WpfHorseRace"
>
<!-- Import the resource dictionary which contains the Style
applied to Border of each horse's "pit". -->
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary
Source
=
"RacePitBorderStyle.xaml"
/>
</ResourceDictionary.MergedDictionaries>
<DataTemplate
DataType
=
"{x:Type local:RaceHorse}"
>
<Border
x
:
Name
=
"racePit"
Style
=
"{StaticResource RacePitBorderStyle}"
>
<!-- Other elements omitted for clarity. -->
</Border>
</DataTemplate>
</ResourceDictionary>
VIII-B. Style de texte de la bande de commande▲
L'autre Style utilisé dans l'application de démonstration est implicitement appliqué à ses éléments cibles. Ce style peut être vu dans Window1.xaml, dans la section « Command Strip » du code XAML.
<StackPanel
Orientation
=
"Horizontal"
>
<StackPanel.Resources>
<!-- This Style is applied to all TextBlock
elements in the command strip area. -->
<Style
TargetType
=
"TextBlock"
>
<Setter
Property
=
"VerticalAlignment"
Value
=
"Center"
/>
<Setter
Property
=
"Foreground"
Value
=
"#EE000000"
/>
</Style>
</StackPanel.Resources>
<TextBlock>
Rotation: </TextBlock>
<Slider ... />
<TextBlock ... />
<TextBlock>
degrees</TextBlock>
</StackPanel>
Notez que le TargetType de ce Style est défini à « TextBlock ». Lorsque le TargetType est défini à un type qui fait partie du framework WPF, vous n'avez pas à utiliser la syntaxe plus lourde {x: Type TypeName} vue précédemment dans cet article. Comme le Style vu ci-dessus n'a pas une clé, il est automatiquement appliqué à tous les éléments TextBlock dans le StackPanel.
IX. Liens externes▲
Remerciements▲
Je tiens ici à remercier Josh Smith pour son aimable autorisation de traduire l'article, ainsi que Philippe Vialatte, tomlev et Djug pour leurs précieux conseils.
Je remercie également eusebe19 pour sa relecture et ses propositions.