Modification d'un DataTemplate au runtime

Après ce titre alléchant, une petite déception: la version actuelle de WPF ne permet pas de modifier un DataTemplate déclaré en Xaml depuis le code-behind. Deux workarounds simples sont communément utilisés:

  • Déclarer en Xaml autant de DataTemplates que de cas possibles
  • Utiliser la FrameworkElementFactory pour tout générer en code-behind

Ces deux approches sont à la fois peu élégantes et fastidieuses. Dans le premier cas il faudra créer des DataTemplates from scratch et couvrir tous les cas possibles. Dans le second cas il vous faudra manier les nombreux appels de méthodes et imbrications complexes que le Xaml nous épargne, et nous coupe également toute possibilité d'utilisation d'Expression Blend.

Je vais vous présenter une troisième manière de procéder. Celle-ci consiste en la déclaration en Xaml d'un DataTemplate « source » pouvant être modifié, généré puis appliqué à la volée. Le projet exemple comporte une classe Personne, le DataTemplate d'origine, et une fenêtre permettant de modifier les propriétés affichées par le DataTemplate en fonction de l’état de trois CheckBoxes.

Interface du projet

La déclaration du DataTemplate doit se faire dans un fichier à part, qui sera inclus dans le projet en tant que ressource. Il est important de noter que ce fichier étant défini avec une Build Action de Resource, il ne sera pas compilé en Baml, car il sera interprété par le programme uniquement au runtime.

Propriétés du fichier contenant le DataTemplate

Le fichier Xaml reste éditable depuis Expression Blend, mais il faudra faire attention à nommer ses éléments critiques à la manière des lookless controls WPF (PART_xxx). Il faudra également inclure tous les namespaces utilisés par le DataTemplate. En effet bien que le fichier soit inclus dans le projet, celui-ci aura besoin de résoudre toutes ses dépendances au runtime.

xmlns:local="clr-namespace:RuntimeDataTemplates;assembly=RuntimeDataTemplates"

<TextBlock Name="PART_Nom" Margin="5,2,5,2" Text="{Binding Path=Nom}"/>

Dans le code-behind, on accèdera au contenu de ce fichier comme à n’importe quelle ressource:

Uri uri = new Uri(" /PersonneBaseDataTemplate.xaml", UriKind.Relative);
StreamResourceInfo info = Application.GetResourceStream(uri);

Le fichier ressource étant du Xaml, donc du Xml, utilisons LINQ to XML pour accéder plus rapidement à ses éléments. Si la CheckBox ChkHorizOrient est cochée, le code suivant recherchera un élément nommé PART_panel, et modifiera son attribut Orientation pour qu’il prenne la valeur Horizontal.

#region Si nécessaire, modification du StackPanel

if (chkHorizOrient.IsChecked == true)

{
// Prend les panel se nommant "pnl"
var panel = (from xe in sourceXDoc.Descendants()
where (string)xe.Attribute("Name") == "PART_StackPanel"
select xe).First();

// Puis ajoute l'attribut Orientation="Horizontal" au premier
// (il ne doit y en avoir qu'un de toute maniŠre..)

panel.Add(new XAttribute("Orientation", "Horizontal"));

}
#endregion

 

Une fois le Xaml modifié, reste à l’appliquer à la fenêtre. Pour cela, il faut transformer le XDocument en Stream, que l’on chargera par le biais de XamlReader.Load().

byte[] bytes = System.Text.Encoding.ASCII.GetBytes(sourceXDoc.ToString());

MemoryStream sXamlModifie = new MemoryStream(bytes);

DataTemplate dt = XamlReader.Load(sXamlModifie) as DataTemplate;

lbx.ItemTemplate = dt; // Application du template

En résumé, la méthode proposée ici permet de pallier partiellement à l’impossibilité de modifier les DataTemplates déclarés en Xaml au runtime, en incluant ce code en tant que ressource qui sera chargé à la demande. Ses avantages principaux sont

  • La description de DataTemplates parents en Xaml, éditables via Expression Blend
  • La génération de DataTemplates à la volée, uniquement lorsque c'est nécessaire
  • L’utilisation éventuelle de LINQ to XML dans le code-behind ce qui permet d'éditer le Xaml de façon élégante

… mais ses inconvénients sont:

  • Une génération de templates, et non une modification de DataTemplates existants
  • Tout comme les lookless controls, elle requiert le respect d’une convention nommage des éléments

RuntimeDataTemplates.zip