Dot.Blog

C#, XAML, Xamarin, UWP/Android/iOS

Les extensions de balises Xaml de Silverlight 5, vous connaissez ?

[new:30/09/2012]Vous avez certainement du lire quelque chose à propos des “custom markup extensions” de Silverlight 5... si, en cherchant bien... Mais que vous en est-il resté ? Sauriez-vous utiliser cette possibilité (qu’on retrouve dans WPF) pour faire quelque chose d’utile, pour vous sortir d’une impasse ? Il y a de grandes chances que la réponse soit non. Alors suivez-moi, vous allez voir, c’est une feature vraiment intéressante..

Custom Markup Extensions

Une ligne parmi les nouveautés de Silverlight 5, une ligne oubliée pour une version éclipsée par les problèmes de communications de Microsoft autour de Silverlight... Lire à ce propos “Silverlight 5 : Hommage à un oublié...” que j’avais écrit début juillet.

Forcément ça fait beaucoup d’oublis pour une pauvre feature.

Et pourtant ! Les “custom markup extensions” sont fantastiques !

Il s’agit ni plus ni moins de pouvoir créer ses propres extensions à Xaml.

C’est à dire faire des choses qui étaient impossibles avant ou bien très complexes et réclamant beaucoup de code ou d’astuces difficilement maintenable.

Les possibilités sont infinies. On peut s’en servir pour gérer un système de localisation complet (concernant toute ressource et pas seulement du texte), on peut s’en servir pour automatiser des réglages, pour exécuter du code en Xaml, récupérer des valeurs qu’un Binding ne sait pas retrouver, etc...

C’est vraiment dommage que ces extensions personnalisées de Xaml n’aient pas rencontré le succès qu’elles méritent.

Heureusement il y Dot.Blog Sourire

Les bases

Nous utilisons souvent les extensions de balisage fournies avec Xaml comme celles concernant le Binding, l’accès aux ressources dynamiques, le Template binding etc. On utilise ces extensions de balisage avec les accolades dites “américaines” {}. Avec Silverlight 5, nous pouvons maintenant écrire nos propres Extensions de balisage personnalisées, et c’est une avancée de taille, même si, comme je le disais, elle est passée un peu inaperçue.

Pour créer une extension personnalisée il faut :

  • Créer une classe héritant de “MarkupExtension” (ou bien implémenter l’interface IMarkupExtension).
  • Le nom de la classe doit avoir le suffixe “Extension”
  • IMarkupExtension expose une méthode, ProvideValue. C’est cette méthode qu’il faut fournir pour retourner une valeur, celle qui sera exploitée par Xaml.
  • Il est tout à fait possible (et souvent souhaitable) de passer des paramètres à notre classe d’extension qui ont pour reflet les propriétés publiques de cette dernière.
  • On peut obtenir aussi le nom et les propriétés du contrôle qui est lié grâce à ServiceProvider, passé par défaut en paramètre à la méthode ProvideValue.

Comme on le comprend ici, il ne s’agit pas de pouvoir ajouter des balises à Xaml mais de pouvoir personnaliser une valeur de propriété en lui attribuant un équivalent de la syntaxe du Binding.

En première approximation, on peut dire que les extensions personnalisées sont une “sorte de” Binding avec une syntaxe similaire. Mais “une sorte de” seulement. Le Binding effectue des opérations bien spécifiques (liaison entre deux propriétés de deux instances), les extensions personnalisées permettent tout ce qui possible dans la limite de la syntaxe imposée. Exécuter du code, récupérer des valeurs, activer des fonctions, traduire des mots, des ressources, bref, tout ce qu’on peut vouloir faire en générant une valeur qui sera attribuée à une propriété en Xaml.

Mise en œuvre simplifiée

Afin de concrétiser tout cela sans se lancer dans un code trop ambitieux, je vais prendre un exemple minimaliste, donc un peu stupide, mais c’est le mécanisme général que je souhaite d’abord vous montrer afin d’effacer les craintes éventuelles sur la complexité de cette nouvelle fonctionnalité.

Je vais créer une extensions qui sait concaténer deux chaines de caractères et qui supporte l’indication d’un séparateur.

Voici le code de cette extension :

using System;
using System.Windows.Markup; // nécessaire pour les extensions

namespace markupA
{
public class ConcatenationExtension : MarkupExtension
{
public string Str1 { get; set;}
public string Str2 { get; set;}
public string Separator { get; set;}

public ConcatenationExtension()
{
Separator = " ";
}

public override object ProvideValue(IServiceProvider serviceProvider)
{
var a = Str1 ?? string.Empty;
var b = Str2 ?? string.Empty;
var s = string.IsNullOrWhiteSpace(Str1) || string.IsNullOrWhiteSpace(Str2)
? string.Empty
: Separator;
return Convert.ToString(a + s + b);

}
}
}

C’est très court... Ca ne fait rien d’extraordinaire je sais, mais tout de même...

Dans la MainPage.xaml je vais poser un TextBlock et je vais utiliser mon extension pour attribuer à la valeur Text de ce dernier le résultat d’une concaténation de deux chaines en utilisant un séparateur fixé librement.

 

image

Comme on peut le voir, Visual Studio reconnait mon extension et l’Intellisense sait même me proposer le nom des propriétés de celle-ci, c’est pas magique ça ? !

Bon... il y a malgré tout quelques “bricoles” qui ne sont pas tout à fait au point sous VS 2010. Par exemple vous voyez sur la capture ci-dessus qu’il y a un souligné ondulé sous le nom de mon extension. VS me dit que j’utilise une classe comme une extension mais qu’elle devrait descendre de MarkupExtension. C’est bien ce qui est fait, le code un peu plus haut le prouve... Bug. Mais pas gênant.

Deuxième déception, VS semble incapable d’afficher la valeur au design. C’est dommage.

En revanche, quand on compile tout va bien et quand on exécute cela marche comme prévu, c’est déjà beaucoup !

image

Je n’ai pas testé sous Blend car Blend 5 n’est officiellement pas sorti et que la version bêta mise à disposition à la sortie de SL 5 est arrivée en fin de validité. Il y a peut-être des choses nouvelles que j’aurai pu utiliser mais si elles sont sous NDA je ne pourrais même pas vous dire qu’elles existent (difficile de dire sans dire tout en disant sans le dire).

Bref, ça marche comme prévu et c’est toujours comme ça avec la plateforme .NET, des bugs il y en a, mais ils ne sont bloquants que de façon rarissime. Quand on utilise beaucoup de logiciels, d’IDE et de langages différents, parfois couteux, on sait à quel point un tel niveau de qualité est exceptionnel en informatique, alors je ne m’attarderais pas sur ces petits dysfonctionnements.

A noter : La classe utilise le suffixe Extension, mais le lecteur attentif aura certainement remarqué qu’en Xaml ce suffixe disparait et ne doit pas être utilisé. C’est un peu comme pour les propriétés de dépendances, on créée ajoute le suffixe “Property” côté code C# mais on ne l’écrit pas en Xaml.

Un peu plus sophistiqué, simulé “'Static” de WPF

Comme vous le savez, sous Silverlight les ressources sont Dynamiques, on ne dispose pas du marqueur “Static” de WPF (ou alors j’ai loupé moi aussi une nouveauté de SL 5 !).

Quoi qu’il en soit cela fait un bon exercice pour créer une extension qui simule “Static” ! Et c’est ce que nous allons faire maintenant.

Regardons tout de suite le code, déjà un peu plus travaillé :

using System;
using System.Reflection;
using System.Windows.Markup;

namespace markupA
{
public class StaticExtension : MarkupExtension
{
public string TypeName { get; set; }
public string PropertyName { get; set; }

public override object ProvideValue(IServiceProvider serviceProvider)
{
object returnValue = null;
var t = Type.GetType(TypeName);

if (t != null)
{
var p = t.GetProperty(PropertyName, BindingFlags.Static | BindingFlags.Public);

var v = p.GetValue(null, null);
if (v is IConvertible)
{
var ipvt =
serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;

if (ipvt != null)
{
var pi = (PropertyInfo)ipvt.TargetProperty;

returnValue = Convert.ChangeType(v, pi.PropertyType, null);
}
}
else
{
returnValue = v;
}
}
return (returnValue);
}
}
}

Le principe reste bien entendu rigoureusement le même. Les paramètres sont des propriétés publiques de la classe qui dérive de MarkupExtension bien entendu.

Ici, le but du jeu est de passer le nom d’un type et celui d’une de ces propriétés pour générer la valeur.

On retrouve donc un peu de Réflexion dans ce code.

Comme ce n’est qu’un exemple, le code est largement perfectible, par exemple ne tester que IConvertible sur la propriété est vraiment insuffisant, mais ce n’est qu’un exemple.

Côté Xaml nous pouvons écrire quelque chose de ce type :

image

Bien entendu les petits soucis évoqués plus haut existent toujours mais ils n’empêchent pas Intellisense de fonctionner et encore moins l’application de faire ce qu’on attend d’elle :

image

Conclusion

Faire le tour des possibilités des extensions personnalisées est impossible, c’est infini...

L’idée était plutôt de vous rappeler que cela existe, et de vous montrer à quel point cela est simple à mettre en œuvre.

Je suis certains que vous trouverez mille façons de vous en servir.

Bon Dev,

Stay Tuned !

blog comments powered by Disqus