Contrôles personnalisés et templates en Silverlight
31 mars 2011 Laisser un commentaire
Lorsque l’on débute en Silverlight, on a tendance à utiliser des UserControl (littéralement contrôles personnalisés) pour personnaliser ses contrôles ; c’est vrai que le nom UserControl laisse à penser que c’est le cas d’utilisation parfait. Cependant, la classe UserControl permet de créer un regroupement de contrôles et pas n’est pas forcément idéale pour un contrôle générique, réutilisable et pouvant être stylisé.
En revanche, il existe une technique pour créer un contrôle qui possède tous ces attributs : le mécanisme de Template de Silverlight et le fichier generic.xaml. Dans cet article je vous propose de voir en quelques étapes comment créer un tel contrôle, en prenant l’exemple du contrôle HeaderedContentControl, que l’on peut trouver dans le Silverlight Toolkit, mais qui est absent de la version Windows Phone.
L’exemple
Le contrôle HeaderedContentControl est un contrôle qui sert de conteneur à un contenu (propriété Content) et qui est caractérisé par un entête (Header). Bien que l’implémentation visuelle du HeaderedContentControl pourrait être faite de nombreuses manières différentes, j’ai fait le choix de m’inspirer des écrans de paramétrage de Windows Phone OS, dans lesquels on voit pour chaque option un titre. Comme on le verra également, redéfinir l’aspect visuel d’un contrôle templaté est une tâche particulièrement aisée et succincte.
Etape 1 : choisir la classe de base
Lors de la création d’un contrôle templaté, il est possible de partir de n’importe quelle classe non sellée (sealed) du Framework et de l’étendre. Pour un contrôle qui n’aurait rien en commun avec les autres contrôles, on pourrait partir de la classe Control.
Néanmoins, certaines classes sont là pour encapsuler une partie du comportement désiré par notre contrôle étendu – pas besoin de réinventer la roue donc ; ici on a besoin d’afficher du contenu ; il existe justement une classe nommée ContentControl, qui prévoit justement l’utilisation d’un contenu. Si on avait voulu afficher des éléments, on serait parti de la classe ItemsControl, et ainsi de suite.
Pour commencer il suffit donc de créer une nouvelle classe dans votre projet Silverlight / Windows Phone, puis de la faire dériver de la classe de base qui va bien, ici ContentControl.
Etape 2 : ajouter des propriétés
Une fois la classe créée, on peut à présent ajouter des Dependency Properties, qui vont constituer la valeur ajoutée par votre contrôle. Une Dependency Property est une propriété qui en plus d’être définie dans la classe, nécessite d’être enregistrée afin de pouvoir être utilisée depuis du Binding.
Dans notre cas, on veut ajouter l’entête du conteneur. On pourrait créer cette propriété sous forme de chaîne de caractères, mais ce qui paraît encore plus intéressant est de la définir en tant qu’Object ; ainsi il sera possible de mettre n’importe quel objet en tant qu’entête et ainsi de l’afficher par le biais d’un convertisseur ou du HeaderTemplate, une autre propriété de type DataTemplate que nous allons exposer pour personnaliser l’affichage de l’entête.
Il est également possible d’ajouter des évènements ou des méthodes, et de s’inscrire aux évènements déjà présents dans la classe ContentControl, afin d’ajouter du comportement à notre classe, mais nous n’exploreront pas cette possibilité dans cet article.
Etape 3 : définir le template par défaut
Maintenant que toutes les propriétés sont prêtes à être utilisées, il nous faut encore décrire le template par défaut, utilisé quand aucun template personnalisé n’est là pour le surcharger. C’est logiquement du code XAML que nous allons écrire pour définir ce template car il s’agit de la description de l’interface. Le code XAML sera mis dans un fichier nommé generic.xaml situé dans le répertoire Themes. Attention, l’emplacement exact du fichier est important, car c’est un fichier spécial, utilisé uniquement dans le cas de contrôles templatés.
Pour définir le contrôle, nous allons tout d’abord ajouter le namespace XML dans lequel il est défini :
xmlns:controls=”clr-namespace:Controls”
Nous pouvons ensuite créer un style dont le type ciblé est notre classe HeaderedContentControl. A l’intérieur de ce style, nous allons définir les valeurs par défaut de propriétés telles que Background, ForeGround, Horizontal/VerticalContentAlignment, etc. qui seront utilisé si ces propriétés ne sont pas changées explicitement.
Enfin nous allons définir, par le même biais, la propriété ControlTemplate qui représente l’aspect visuel du contrôle. Ici cette partie est extrêmement simple, une Grid comportement 2 lignes, l’une pour l’entête et l’autre pour le contenu, mais il pourrait bien plus complexe, avec par exemple l’utilisation du VisualStateManager, qui permet de faire des contrôle templatés à états (par exemple l’état sélectionné ou non d’une CheckBox) et animés.
Ce qu’il faut tout de même remarquer, est l’utilisation du TemplateBinding pour faire la liaison avec le contenu, l’entête et leur ContentTemplate respectifs. Le TemplateBinding est en fait un raccourci permettant de faire du Binding sur le contrôle templaté ; c’est l’équivalent de :
Content=”{Binding Header, RelativeSource={RelativeSource TemplatedParent}}
Etape 4 : changer la DefaultStyleKey
Il ne reste plus qu’à préciser dans le constructeur de notre contrôle que le style par défaut est celui défini dans generic.xaml ; pour cela Silverlight passe par la propriété DefaultStyleKey. En lui donnant le type du contrôle comme clé, il va retrouver la bonne ressource et associé le code C# au code XAML.
Utilisation et surcharge du template
Il suffit maintenant pour utiliser notre contrôle flambant neuf d’ajouter le namespace XML et de remplir sa propriété Header et Content. C’est d’ailleurs ce même contrôle que j’avais déjà utilisé dans mon article précédent : Ajouter des éléments spéciaux dans une collection liée via binding
L’avantage de cette technique est que le Template est du coup très facile à changer, en particulier à l’aide de Microsoft Expression Blend : certain parlent par conséquent du caractère « blendable » du contrôle.
Dans Blend, en faisant un clic-droit sur le contrôle en question et en choisissant « Edit Template » puis « Edit a Copy… », l’outil crée une copie du Template que l’on peut éditer.
Une fois le Template créé, on peut facilement reporter la modification sur toutes les occurrences du contrôle, en utilisant la commande « Apply Resource ». On peut donc en quelque clics obtenir le résultat suivant :
Pour terminer on peut donc dire que la création d’un contrôle templaté, à priori moins immédiat que celle d’un UserControl, apporte plus de finesse dans le contrôle de l’apparence et est donc à prescrire pour les contrôles pensés pour être génériques et réutilisables.