Templates avancés en Silverlight
Date de publication : 05/05/2008 , Date de mise à jour : 17/06/2008
Par
Benjamin Roux (Retour aux articles)
Dans cet article nous allons voir une utilisation avancée des templates en Silverlight.
1. Template de contrôle avancé
1-1. Introduction
1-2. Template de bouton
1-2-1. Le visuel
1-2-2. Les animations
1-2-3. Conclusion
2. Création d'un contrôle templatisable
2-1. La classe
2-2. Eléments templatisables
2-3. Propriétés de binding
2-4. Template de test
2-5. Les états (ouvert/fermé)
2-6. Template final
2-7. Test online
2-8. Conclusion
3. Conclusion
4. Remerciements
1. Template de contrôle avancé
1-1. Introduction
Silverlight 2 fait apparaître avec lui de nombreuses nouveautés (introduites ici :
Introduction à Silverlight 2) et avec ces dernières la notion de
Template
pour les contrôles.
Mais comment personnaliser un contrôle de A à Z ? C'est ce que nous allons voir immédiatement.
Depuis la beta 2 de Silverlight, la personnalisation des contrôles a été simplifiée grâce à Expression
Blend, cet article s'adresse donc aux développeurs, qui, comme moi qui ne savent utiliser qu'une
seule application de graphisme : Paint.
1-2. Template de bouton
Pour illustrer ce concept, nous allons personnaliser un contrôle de type bouton.
Pour tout ce qui est réalisation de template, votre meilleure amie est sans nul doute la MSDN. En effet
vous trouverez sur cette dernière le nom des différents états (par exemple MouseOver pour un bouton),
ainsi que des exemples.
Voici le lien répertoriant les différentes pages qui sont utiles :
http://msdn2.microsoft.com/en-us/library/cc278075(VS.95).aspx
On apprend le nom des différents états d'un bouton ainsi que le groupe auxquels ils appartiennent.
|
Etat
|
Groupe
|
Description
|
|
Normal
|
CommonStates
|
L'état normal du bouton
|
|
MouseOver
|
CommonStates
|
L'état du bouton lorsque l'utilisateur passe la souris dessus
|
|
Pressed
|
CommonStates
|
L'état du bouton que l'utilisation clique dessus
|
|
Disabled
|
CommonStates
|
L'état du bouton lorsqu'il est désactivé
|
|
Focused
|
FocusStates
|
L'état du bouton lorsqu'il prend le focus
|
|
Unfocused
|
FocusStates
|
L'état du bouton lorsqu'il perd le focus
|
Nous avons tout ce dont nous avons besoin pour réaliser le template de notre contrôle.
Pour cet article, nous allons rendre notre contrôle semblable visuellement aux boutons présents dans
la suite Office 2007 (bleu et orange).
1-2-1. Le visuel
Nous allons commencer par le visuel de notre bouton. Notre élément racine se nommera RootElement.
<Grid x:Name="RootElement">
</Grid>
|
Maintenant nous allons mettre un peu de couleur, à savoir une bordure bleue et un background en bleu
dégradé.
<Grid x:Name="RootElement">
<Border x:Name="VisualElement" Width="{TemplateBinding Width}" Height="{TemplateBinding Height}" CornerRadius="4" BorderThickness="1">
<Border.BorderBrush>
<SolidColorBrush Color="#FF9BB7E0"/>
</Border.BorderBrush>
<Border.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop x:Name="Color1" Color="#FFC8DBEE" Offset="0.260"/>
<GradientStop x:Name="Color2" Color="#FF84AFE6" Offset="0.530"/>
<GradientStop x:Name="Color3" Color="#FFC8DBEE" Offset="0.80"/>
</LinearGradientBrush>
</Border.Background>
</Border>
</Grid>
|
Voici le résultat de notre bouton avec ce code pour l'instant
<Button Width="155" Height="50" Content="Bouton Style Word 2007"
Style="{StaticResource ButtonRectangle}" VerticalAlignment="Center"
FontFamily="Verdana" FontSize="12" />
|
On voit que le contenu de notre bouton (ici du texte) n'est pas visible.
Nous allons simplement rajouter un objet de type ContentPresenter, dont le contenu sera bindé
sur le contenu de notre bouton, autrement dit, tout ce qui sera dans la propriété Content
(du texte, un contrôle...) de notre bouton sera représenté dans notre ContentPresenter.
<Grid x:Name="RootElement">
<Border x:Name="VisualElement" Width="{TemplateBinding Width}" Height="{TemplateBinding Height}" CornerRadius="4" BorderThickness="1">
<Border.BorderBrush>
<SolidColorBrush Color="#FF9BB7E0"/>
</Border.BorderBrush>
<Border.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop x:Name="Color1" Color="#FFC8DBEE" Offset="0.260"/>
<GradientStop x:Name="Color2" Color="#FF84AFE6" Offset="0.530"/>
<GradientStop x:Name="Color3" Color="#FFC8DBEE" Offset="0.80"/>
</LinearGradientBrush>
</Border.Background>
<ContentPresenter Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
FontFamily="{TemplateBinding FontFamily}"
FontSize="{TemplateBinding FontSize}"
FontStretch="{TemplateBinding FontStretch}"
FontStyle="{TemplateBinding FontStyle}"
FontWeight="{TemplateBinding FontWeight}"
Foreground="Black"
Padding="{TemplateBinding Padding}"
TextAlignment="{TemplateBinding TextAlignment}"
TextDecorations="{TemplateBinding TextDecorations}"
TextWrapping="{TemplateBinding TextWrapping}"
VerticalContentAlignment="Center"
HorizontalContentAlignment="Center"
Margin="0" />
</Border>
</Grid>
|
Et maintenant voici le résultat avec le même code que précédemment.
Il reste un problème : quand on passe la souris dessus ou même que l'on clique dessus il ne se passe
rien du tout.
Pour ça nous allons maintenant spécifier ses comportements.
1-2-2. Les animations
Le gestion des états avec les templates, se fait grâce au VisualStateManager, il nous faut donc
l'utiliser.
On commencer par rajouter le namespace
xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows"
|
On rajoute maintenant les balises nécessaires :
<Grid x:Name="RootElement">
<vsm:VisualStateManager.VisualStateGroups>
<vsm:VisualStateGroup x:Name="CommonStates">
</vsm:VisualStateGroup>
</vsm:VisualStateManager.VisualStateGroups>
[...]
</Grid>
|
 |
Dans la beta 2 de Silverlight, Visual Studio ne reconnait pas VisualStateManager.VisualStateGroups,
mais cela fonctionne quand même.
|
Et maintenant nous allons mettre nos étas uns à uns.
Commençons par l'état MouseOver.
<Grid x:Name="RootElement">
<vsm:VisualStateManager.VisualStateGroups>
<vsm:VisualStateGroup x:Name="CommonStates">
<vsm:VisualState x:Name="MouseOver">
<Storyboard>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="Color1" Storyboard.TargetProperty="Color">
<DiscreteColorKeyFrame KeyTime="0:0:0" Value="#FFFDF2CA" />
</ColorAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="Color2" Storyboard.TargetProperty="Color">
<DiscreteColorKeyFrame KeyTime="0:0:0" Value="#FFFFD048" />
</ColorAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="Color3" Storyboard.TargetProperty="Color">
<DiscreteColorKeyFrame KeyTime="0:0:0" Value="#FFFDF2CA" />
</ColorAnimationUsingKeyFrames>
</Storyboard>
</vsm:VisualState>
<vsm:VisualState x:Name="Disabled" />
</vsm:VisualStateGroup>
</vsm:VisualStateManager.VisualStateGroups>
</Grid>
|
Comme vous pouvez le voir c'est extrêmement simple, il suffit de déclarer un VisualState et d'associer
à la propriété x:Name le nom de l'état auquel on veut qu'il soit associé. Puis il suffit
de mettre un Storyboard pour notre animation.
Et voilà, désormais lorsque l'utilisateur passera la souris sur le bouton le dégradé bleu en fond changera
en dégradé orange.
Rajoutons ensuite les autres états.
<Grid x:Name="RootElement">
<vsm:VisualStateManager.VisualStateGroups>
<vsm:VisualStateGroup x:Name="CommonStates">
<vsm:VisualState x:Name="Normal">
<Storyboard>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="Color1" Storyboard.TargetProperty="Color">
<LinearColorKeyFrame KeyTime="0:0:0.35" Value="#FFC8DBEE" />
</ColorAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="Color2" Storyboard.TargetProperty="Color">
<LinearColorKeyFrame KeyTime="0:0:0.35" Value="#FF84AFE6" />
</ColorAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="Color3" Storyboard.TargetProperty="Color">
<LinearColorKeyFrame KeyTime="0:0:0.35" Value="#FFC8DBEE" />
</ColorAnimationUsingKeyFrames>
</Storyboard>
</vsm:VisualState>
<vsm:VisualState x:Name="MouseOver">
<Storyboard>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="Color1" Storyboard.TargetProperty="Color">
<DiscreteColorKeyFrame KeyTime="0:0:0" Value="#FFFDF2CA" />
</ColorAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="Color2" Storyboard.TargetProperty="Color">
<DiscreteColorKeyFrame KeyTime="0:0:0" Value="#FFFFD048" />
</ColorAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="Color3" Storyboard.TargetProperty="Color">
<DiscreteColorKeyFrame KeyTime="0:0:0" Value="#FFFDF2CA" />
</ColorAnimationUsingKeyFrames>
</Storyboard>
</vsm:VisualState>
<vsm:VisualState x:Name="Pressed">
<Storyboard>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="Color1" Storyboard.TargetProperty="Color">
<DiscreteColorKeyFrame KeyTime="0:0:0" Value="#FFFCB16D" />
</ColorAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="Color2" Storyboard.TargetProperty="Color">
<DiscreteColorKeyFrame KeyTime="0:0:0" Value="#FFFF8D06" />
</ColorAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="Color3" Storyboard.TargetProperty="Color">
<DiscreteColorKeyFrame KeyTime="0:0:0" Value="#FFFCB16D" />
</ColorAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="VisualElement" Storyboard.TargetProperty="(Border.BorderBrush).Color">
<DiscreteColorKeyFrame KeyTime="0:0:0" Value="#FF7B6645" />
</ColorAnimationUsingKeyFrames>
</Storyboard>
</vsm:VisualState>
<vsm:VisualState x:Name="Disabled" />
</vsm:VisualStateGroup>
</vsm:VisualStateManager.VisualStateGroups>
</Grid>
|
Lorsque l'utilisateur cliquera sur le bouton la couleur de ce dernier passera en orange foncé (Discrete
animation), lorsque le bouton repassera dans l'état normal (MouseOut par exemple), la couleur reviendra
progressivement (Linear animation) au bleu et pour finir je n'ai pas mis d'animation pour
l'état désactivé.
Voici donc le code final.
<Grid x:Name="RootElement">
<vsm:VisualStateManager.VisualStateGroups>
<vsm:VisualStateGroup x:Name="CommonStates">
<vsm:VisualState x:Name="Normal">
<Storyboard>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="Color1" Storyboard.TargetProperty="Color">
<LinearColorKeyFrame KeyTime="0:0:0.35" Value="#FFC8DBEE" />
</ColorAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="Color2" Storyboard.TargetProperty="Color">
<LinearColorKeyFrame KeyTime="0:0:0.35" Value="#FF84AFE6" />
</ColorAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="Color3" Storyboard.TargetProperty="Color">
<LinearColorKeyFrame KeyTime="0:0:0.35" Value="#FFC8DBEE" />
</ColorAnimationUsingKeyFrames>
</Storyboard>
</vsm:VisualState>
<vsm:VisualState x:Name="MouseOver">
<Storyboard>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="Color1" Storyboard.TargetProperty="Color">
<DiscreteColorKeyFrame KeyTime="0:0:0" Value="#FFFDF2CA" />
</ColorAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="Color2" Storyboard.TargetProperty="Color">
<DiscreteColorKeyFrame KeyTime="0:0:0" Value="#FFFFD048" />
</ColorAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="Color3" Storyboard.TargetProperty="Color">
<DiscreteColorKeyFrame KeyTime="0:0:0" Value="#FFFDF2CA" />
</ColorAnimationUsingKeyFrames>
</Storyboard>
</vsm:VisualState>
<vsm:VisualState x:Name="Pressed">
<Storyboard>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="Color1" Storyboard.TargetProperty="Color">
<DiscreteColorKeyFrame KeyTime="0:0:0" Value="#FFFCB16D" />
</ColorAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="Color2" Storyboard.TargetProperty="Color">
<DiscreteColorKeyFrame KeyTime="0:0:0" Value="#FFFF8D06" />
</ColorAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="Color3" Storyboard.TargetProperty="Color">
<DiscreteColorKeyFrame KeyTime="0:0:0" Value="#FFFCB16D" />
</ColorAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="VisualElement" Storyboard.TargetProperty="(Border.BorderBrush).Color">
<DiscreteColorKeyFrame KeyTime="0:0:0" Value="#FF7B6645" />
</ColorAnimationUsingKeyFrames>
</Storyboard>
</vsm:VisualState>
<vsm:VisualState x:Name="Disabled" />
</vsm:VisualStateGroup>
</vsm:VisualStateManager.VisualStateGroups>
<Border x:Name="VisualElement" Width="{TemplateBinding Width}" Height="{TemplateBinding Height}" CornerRadius="4" BorderThickness="1">
<Border.BorderBrush>
<SolidColorBrush Color="#FF9BB7E0"/>
</Border.BorderBrush>
<Border.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop x:Name="Color1" Color="#FFC8DBEE" Offset="0.260"/>
<GradientStop x:Name="Color2" Color="#FF84AFE6" Offset="0.530"/>
<GradientStop x:Name="Color3" Color="#FFC8DBEE" Offset="0.80"/>
</LinearGradientBrush>
</Border.Background>
<ContentPresenter Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
FontFamily="{TemplateBinding FontFamily}"
FontSize="{TemplateBinding FontSize}"
FontStretch="{TemplateBinding FontStretch}"
FontStyle="{TemplateBinding FontStyle}"
FontWeight="{TemplateBinding FontWeight}"
Foreground="Black"
Padding="{TemplateBinding Padding}"
TextAlignment="{TemplateBinding TextAlignment}"
TextDecorations="{TemplateBinding TextDecorations}"
TextWrapping="{TemplateBinding TextWrapping}"
VerticalContentAlignment="Center"
HorizontalContentAlignment="Center"
Margin="0" />
</Border>
</Grid>
|
Vous pouvez aussi voir que je n'ai pas utilisé l'élément pour indiquer le focus sur le bouton (en général
une bordure en pointillés).
Voici enfin un lien vers un test online :