Astuces DotNet (Sébastien Courtois)

18/03/2009

[Silverlight 2] Les converters typés

Filed under: Débutant, Intermediaire, Silverlight, WPF — Étiquettes : , , , — sebastiencourtois @ 15:46

 

Ceux qui ont déjà touchés à Silverlight 2 ou à WPF ont été amené à utilisé les converters. En travaillant, je me suis aperçu qu’une partie du code que j’écrivais dans un converter était principalement de la vérification de type d’entrée et de sortie. La cause : Les converters ne sont pas typés. Cela est, je pense, une fonctionnalité voulue par Microsoft afin de rendre ce système le plus flexible possible. Toutefois, j’aime bien les choses typés et ce principe me dérangeait d’autant que, la plupart du temps, je voulais un type d’entrée spécifique et un type de sortie spécifique.

Il existe un attribut en WPF pour “gérer” ce problème : ValueConversionAttribute. Toutefois, l’utilisation de cet attribut est de l’ordre informatif et il ne réalise aucune vérification. Le texte qui suit est donc valable pour WPF et Silverlight.

Partons d’un converter classique : Le BoolToVisibilityConverter

Le principe de ce converter est de fournir un booléen en entrée qui va définir un System.Windows.Visibility en sortie.

Voici le code du converter classique : 

Blog7-1

Comme vous pouvez le remarquer, 14 lignes de code pour écrire ce converter et 8 ne concerne pas la conversion en elle même mais plus les vérifications de types. J’ai ainsi factoriser ce code dans une classe abstraite générique afin n’avoir plus QUE de la conversion de type au sein de mes converters.

Code de la classe générique abstraite mère :  (VType représente le type d’entrée. TType représente le type de sortie.)

Blog7-2

Par héritage, on obtient le BoolToVisibiltyConverter suivant :

Blog7-3

Le développeur n’a plus qu’à remplir les méthodes abstraites héritées de TypedConverter. Ces méthode seront correctement typés (que ce soit dans leur paramètres et dans leur valeur de retour). De plus, la vérification de type est garanti par la classe mère. Au sein de BoolToVisibilityConverter, on ne retrouve que le code de conversion.

Certains pourront dire que ce code ne sert à rien car il fait perdre la flexibilité de Silverlight 2 (qui est un de ses grands atouts). Même si cela est vrai, 90 % des cas d’utilisations des converters se réalise avec un type d’entrée bien défini et un type de sortie bien défini. Autant essayé de gagner de la lisibilité dans le code sur ces cas là. Pour des cas nécessitant plus de flexibilité, rien n’empêche d’utiliser le bon vieux IValueConverter.

EDIT 18/03/09 : Suite à des tests, j’ai découvert un petit bug sur le code du TypedConverter. En effet, l’héritage au niveau du TargetType n’était pas pris en compte.

Exemple : Si on crée un TypedConverter<object,object> (i.e un converter standard), et qu’on lui fournit un targetType de Visibility, on obtient l’exception : “le type de sortie doit être …”. En effet, Visibility n’est pas object. Toutefois, vu que Visibility hérite de object, on aimerait pouvoir passer ce type de donnée pour qu’il soit valider “par héritage”. Or le test recherche une différence entre deux System.Type sans regarder la hiérarchie des classes. J’ai donc modifié le code du TypedConverter :

public abstract class TypedConverter<VType,TType>  : IValueConverter
{
	public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
	{
		//Vérification du type d'entrée
		if (!(value is VType))
			throw new InvalidOperationException("La paramètre d'entrée doit être de type "+ typeof(VType));
		//Vérification du type de sortie
		if(!this.IsTargetType(targetType,typeof(TType)))
			throw new InvalidOperationException("La type de sortie doit être de type " + typeof(TType));
		return this.Convert((VType)value, culture);
	}

	public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
	{
		//Vérification du type d'entrée
		if (!(value is TType))
			throw new InvalidOperationException("La paramètre d'entrée doit être de type " + typeof(VType));
		//Vérification du type de sortie
		if (!this.IsTargetType(targetType, typeof(VType)))
			throw new InvalidOperationException("La type de sortie doit être de type " + typeof(TType));
		return this.ConvertBack((TType)value, culture);
	}

	protected abstract TType Convert(VType p, CultureInfo culture);
	protected abstract VType ConvertBack(TType p, CultureInfo culture);

	//Méthode recherchant un type correspondant dans la hierarchie des classes
	private bool IsTargetType(Type targetType, Type type)
	{
		while(targetType != null)
		{
			if (targetType == type)
				return true;
			targetType = targetType.BaseType;
		}
		return false;
	}
}

L’héritage est ainsi gérer par l’utilisation de la méthode IsTargetType qui remonte l’arborescence des classes de targetType jusqu’à trouver une classe correspondant au type demandé. Si ce n’est pas le cas, une exception est lancée.

Créez un site Web ou un blog gratuitement sur WordPress.com.