[Silverlight] Création d’un contrôle Silverlight pas-à-pas
Il y a quelque temps, je publiais un billet expliquant comment définir le style d’un contrôle avec Expression Blend tout en manipulant les différents états visuels d’un contrôle.
Aujourd’hui, je vais tâcher de vous expliquer comment contrôler ces états visuels dans la création de vos propres contrôles.
Les contrôles Silverlight sont dits lookless, c’est à dire qu’ils ne définissent pas – de base – d’apparence comme c’est pas exemple le cas pour des contrôles Winforms. En revanche ceux-ci définissent des états et des comportements. Les états sont les différentes “vues” que l’on va pouvoir avoir pour un contrôle, selon certains éléments qui vont dépendre de l’environnement du contrôle : a-t-il le focus ? l’utilisateur a-t-il cliqué sur le contrôle ? etc…
Les comportements sont tout simplement les différents évènements qui peuvent être levés par un contrôle.
Dans ce post, nous allons créer un contrôle assez simple puisqu’il s’agira d’un contrôle affichant une ligne de texte dans son état normal et une combobox contenant des checkbox lorsque l’utilisateur passera sa souris dessus.
Il possèdera donc deux état :
C’est la classe VisualStateManager qui va nous permettre de contrôler les différents états que possède un contrôle.
La première chose à faire, avant même de pense aux éléments qui composent le contrôle, est de regrouper et créer les états. Ici, nous ne possèderons qu’un seul groupe, que nous allons appeler “CommonStates” et qui contiendra nos deux états vus ci-dessus.
La déclaration d’état ce fait par du code XAML directement dans le contrôle. Commencez par créer un projet Silverlight et lui ajouter un item de type “Contrôle Utilisateur” :
Vous pouvez ensuite déclarer les différents états :
<Grid x:Name="LayoutRoot" Background="White">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="MouseOver" />
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
Il est également possible de définir des transitions au sein d’un VisualStateGroup afin de définir une animation à jouer pour passer d’un état à un autre. Pour cela, il faut utiliser la balise “VisualTransition” qui va prendre la durée de transition ainsi que l’état vers lequel la transition s’effectue en paramètre :
<VisualStateGroup.Transitions>
<VisualTransition GeneratedDuration="00:00:01"
To="MouseOver">
<Storyboard />
</VisualTransition>
</VisualStateGroup.Transitions>
Nous reviendrons sur la transition un peu plus tard.
A présent, nous devons définir les éléments qui composent notre contrôle.
Pour l’état normal, nous n’avons besoin que d’un TextBlock qui affichera un élément texte (défini en fin d’article) et d’un path représentant une flèche vers le bas. Nous allons placer ces deux éléments dans une grille nommé “normalStateGrid” :
<Grid x:Name="normalStateGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="15" />
</Grid.ColumnDefinitions>
<TextBlock x:Name="TXT_Header" Margin="5" Grid.Column="0" />
<Path x:Name="BtnArrow" Stretch="Uniform" Height="6" HorizontalAlignment="Center" Margin="0,0,6,0" Width="11"
Data="F1 M 301.14,-189.041L 311.57,-189.041L 306.355,-182.942L 301.14,-189.041 Z " Grid.Column="1" >
<Path.Fill>
<SolidColorBrush Color="#FF333333"/>
</Path.Fill>
</Path>
</Grid>
Pour l’état MouseOver, nous avons besoin d’une combobox, que nous allons masquer par défaut :
<ComboBox x:Name="mouseOverComboBox" Visibility="Collapsed" />
Nous allons à présent devoir définir ce qu’il se passe lors du passage à l’état MouseOver. Ici, il suffira de changer la visibilité de la grille “normalStateGrid” pour la masquer et de changer la visibilité de la combobox pour l’afficher :
<VisualState x:Name="MouseOver">
<Storyboard>
<ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetName="mouseOverComboBox"
Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetName="normalStateGrid"
Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Collapsed</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
Nous allons pouvoir revenir sur la VisualTransition que nous avons créez précédemment et faire varier l’opacité de la combobox de 0 à 1 pendant le changement d’état :
<VisualTransition GeneratedDuration="00:00:02"
To="MouseOver">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="mouseOverComboBox"
Storyboard.TargetProperty="Opacity"
From="0"
To="1" />
</Storyboard>
</VisualTransition>
A présent, il faut faire en sorte que l’état du contrôle change. Pour cela, je vous propose de vous abonner à l’évènement MouseEnter sur le Path représentant la flèche dans l’état normal et à l’évènement DropDownClosed de la ComboBox.
Dans ces deux gestionnaires d’évnèvement, nous allons faire appel à la classe VisualStateManager et à sa méthode GoToState pour modifier l’état visuel du contrôle :
private void BtnArrow_MouseEnter(object sender, MouseEventArgs e)
{
VisualStateManager.GoToState(this, "MouseOver", true);
}
private void mouseOverComboBox_DropDownClosed(object sender, EventArgs e)
{
VisualStateManager.GoToState(this, "Normal", true);
}
Nous allons à présent redéfinir le DataTemplate des items de la combobox afin d’y afficher des CheckBox :
<ComboBox x:Name="mouseOverComboBox" Visibility="Collapsed"
DropDownClosed="mouseOverComboBox_DropDownClosed">
<ComboBox.ItemTemplate>
<DataTemplate x:Key="CheckBoxComboTemplate">
<CheckBox Content="{Binding}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Puis définir deux DependencyProperty : HeaderTextProperty (à afficher dans le contrôle) et ItemsSourceProperty (un IEnumerable à passer en source de la combobox) :
public string HeaderText
{
get
{
return GetValue(HeaderTextProperty).ToString();
}
set
{
SetValue(HeaderTextProperty, value);
}
}
public IEnumerable ItemsSource
{
get
{
return (IEnumerable)GetValue(ItemsSourceProperty);
}
set
{
SetValue(ItemsSourceProperty, value);
}
}
public static DependencyProperty HeaderTextProperty = DependencyProperty.Register(
"HeaderText",
typeof(string),
typeof(CustomComboBox),
new PropertyMetadata(
new PropertyChangedCallback(OnHeaderTextPropertyChanged)
)
);
private static void OnHeaderTextPropertyChanged(DependencyObject dpObj,
DependencyPropertyChangedEventArgs e)
{
if (dpObj is CustomComboBox)
{
((CustomComboBox)dpObj).TXT_Header.Text =
e.NewValue.ToString();
}
}
public static DependencyProperty ItemsSourceProperty = DependencyProperty.Register(
"ItemsSource",
typeof(IEnumerable),
typeof(CustomComboBox),
new PropertyMetadata(
new PropertyChangedCallback(OnItemsSourcePropertyChanged)
)
);
private static void OnItemsSourcePropertyChanged(DependencyObject dpObj,
DependencyPropertyChangedEventArgs e)
{
if (dpObj is CustomComboBox)
{
((CustomComboBox)dpObj).mouseOverComboBox.ItemsSource =
(IEnumerable)e.NewValue;
}
}
Il ne vous reste plus qu’à utiliser votre contrôle :
<UserControl x:Class="SilverlightApplication3.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:meUI="clr-namespace:SilverlightApplication3"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480">
<Grid x:Name="LayoutRoot">
<Grid.RowDefinitions>
<RowDefinition Height="50" />
</Grid.RowDefinitions>
<meUI:CustomComboBox x:Name="customCbo" HeaderText="MonText" Grid.Row="0" Width="400"/>
</Grid>
</UserControl>
Et voilà le travail :
Au cours de cet article j’ai essayé de vous montrer à quel point il était simple de créer ses propres contrôles personnalisés en Silverlight et ce en utilisant les états visuels.
A bientôt 
Sources : SilverlightApplication3.zip (72,03 kb)