Astuces DotNet (Sébastien Courtois)

15/04/2010

[Silverlight] Sortie de Silverlight 4 RTW

Filed under: .NET, Débutant, Silverlight, Silverlight 3, Silverlight 4 — Étiquettes : , , , , — sebastiencourtois @ 23:40

Ca y est : Silverlight 4 est disponible à la page suivante : http://www.silverlight.net/getstarted/

Beaucoup de nouveautés (drag/drop, impression,clic droit …) dont certains ont déja été décrit sur ce blog :

D’autres posts sur le sujets arriveront bientôt.

EDIT : Silverlight 4 Tools NE FONCTIONNE PAS SUR VS 2008…. Il faut obligatoirement VS 2010 pour développer en SL4… De plus les outils de développement sont en RC pour le moment car il fallait que Silverlight 4 soit complètement terminé pour finaliser les outils de développement (ils ont du finir sur le fil SL4 :)).

01/02/2010

[Silverlight 3/4] Comment faire des contrôles avec deux faces différentes ?

Filed under: Intermediaire, Silverlight, Silverlight 3, Silverlight 4, XAML — Étiquettes : , , , , — sebastiencourtois @ 21:23

Au commencement Microsoft créa Silverlight. Silverlight, la technologie RIA nouvelle génération pour créer des applications vectorielles entièrement en code managé. Microsoft vit que Silverlight était bon… mais qu’il lui manquait un petit truc pour rivaliser avec son rival. Alors Microsoft dit : “Que la 3D soit” et la 3D fut intégrée dans Silverlight … enfin pas tout à fait.

Tout d’abord, la « 3D » dans Silverlight n’est qu’un moteur de perspective qui ne permet que de faire des rotations/translations de contrôles. Point de moteur ou de monde 3D ici. On parle uniquement ici que de pouvoir faire des jolis effets en alliant animations et perspectives.

Ensuite cette perspective a une feature (« not a bug ») assez ennuyeuse à mon sens : Si on prend l’exemple d’un contrôle contenant une image, lorsque l’on retourne ce contrôle, on se retrouve avec … la même image inversée.

Cap3

Personnellement j’aurais aimé avoir accès à une deuxième face pour pouvoir ajouter des nouveaux contrôles et, comme on n’est jamais mieux servi que par soi-même, j’ai donc cherché un moyen de réaliser ce contrôle. J’ai appelé ce contrôle : FacesPanel.

Fonctionnement de base du contrôle FacesPanel

En parcourant les forums, la pratique général est d’avoir deux contrôles sur un même conteneur (un Grid par exemple) et de rendre visible l’un ou l’autre selon la propriété RotationX/RotationY d’un PlaneProjection. L’algorithme est le suivant :

double Roty = Math.Abs(((PlaneProjection)this.Projection).RotationY);
double Rotx = Math.Abs(((PlaneProjection)this.Projection).RotationX);
if ((Roty % 360 > 90 && Roty % 360 < 270) || (Rotx % 360 > 90 && Rotx % 360 < 270))
{
    //Back Face
}
else
{
    //Front Face
}

Remarque : Vous vous demander peut être pourquoi on ne trouve pas de RotationZ. La raison est qu’une Rotation sur l’axe Z se fera toujours “face” à l’utilisateur. Par conséquent, aucun cas de changement de face. Une autre interrogation pourrait être au sujet des Math.Abs et des %. Cela est simplement dû au fait que les rotations peuvent être négatives comme supérieure à 360°.

Cette méthode a deux inconvénients qu’il va falloir gérer :

1°) Lors du retournement du contrôle, le fait de changer la visibilité des contrôles ne résout pas le problème de l’inversion graphique car c’est le container qui tourne donc le contrôles interne suit le mouvement. Pour gérer ce problème, on utilise un ScaleTransform afin de remettre le contrôle dans le bon sens.

2°) Je n’ai pas réussi à récupérer les valeurs RotationX/RotationY au cours d’une animation lorsque je me binde sur ces propriétés. Cela est problématique pour avoir un comportement naturel au cours d’une animation (le changement de face se faisant uniquement à la fin de l’animation). Pour parer ce problème, j’ai utilisé une solution peu élégant/optimisé mais qui à le mérite de fonctionner (si vous avez mieux, n’hésitez pas à commenter). J’utilise un DispatcherTimer afin que, périodiquement, je regarde le valeurs de RotationX/RotationY pour voir si un changement de face est nécessaire.

Propriétés/Methodes de FacesPanel :

Voila pour la base du fonctionnement. Afin de rendre le contrôle exploitable, j’ai rajouté les propriétés suivantes :

  • FrontFace (type : UIElement) : Contrôle pour la face principale
  • BackFace (type : UIElement) : Contrôle pour la face caché
  • AnimationDirection (type: TurnDirection ) : Permet de définir le sens du retournement de la face. Se base sur l’énum TurnDirection défini dans le même fichier .cs. 5 valeurs (NONE,LEFT,RIGHT,UP,BOTTOM)
  • AnimationDuration (type : int) : Durée de l’animation de retournement
  • AnimationEasing (Type : IEasingFunction) : Animation Easing pour l’animation de  retournement.
  • GoToFrontFace() : Montre la face principale.
  • GoToBackFace() : Montre la face “caché”.
  • TurnFace() : Change de face

Cas d’utilisation : Photo Bucket

Afin de faire un cas d’utilisation, j’ai choisi de reprendre une démonstration Silverlight 4 sur glisser/déposer faite par Audrey Petit. Ce que je souhaitait faire était de pouvoir glisser déposer des images puis, par click de souris sur une image, la retourner pour marquer des commentaires au dos. J’ai donc utilisé l’exemple d’Audrey en rajoutant un contrôle spécifique pour gérer les images.

PictureControl.xaml :

<UserControl x:Class="PhotoBucket.PictureControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:PhotoBucket"
    mc:Ignorable="d" Width="150" Height="150">
    <local:FacesPanel x:Name="FacesPanelControl" AnimationDirection="LEFT" AnimationDuration="3" MouseLeftButtonDown="FacesPanelControl_MouseLeftButtonDown">
        <local:FacesPanel.AnimationEasing>
            <ElasticEase />
        </local:FacesPanel.AnimationEasing>
        <local:FacesPanel.FrontFace>
            <Border Background="White" BorderBrush="Black" BorderThickness="1">
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="120" />
                        <RowDefinition Height="30" />
                    </Grid.RowDefinitions>
                    <Image x:Name="ImageZone"  Margin="10,5,10,0" />
                    <TextBlock Text="{Binding ElementName=txtTitle,Path=Text}" FontSize="10" FontStyle="Italic" 
                               Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center" />
                </Grid>
            </Border>
        </local:FacesPanel.FrontFace>
        <local:FacesPanel.BackFace>
            <Border Background="White" Margin="5" BorderBrush="Black" BorderThickness="1">
                <StackPanel>
                    <TextBlock Text="Titre de la photo" Margin="2" />
                    <TextBox x:Name="txtTitle"  Text="Test" Margin="2" />
                </StackPanel>
            </Border>
        </local:FacesPanel.BackFace>
    </local:FacesPanel>
</UserControl>

Picture.xaml.cs :

    public partial class PictureControl : UserControl
    {
        public PictureControl()
        {
            InitializeComponent();
        }

        private void FacesPanelControl_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            ((FacesPanel)sender).TurnFace();
        }
    }

Et voici le résultat en image :

Cap1 Cap2

Et en code

  • Lien du source de la démo du contrôle sur VS 2008 / SL3 (pas d’application photo car pas de drag / drop en SL3)
  • Lien du source de la démo VS  (Appli Photo Bucket)

Si vous avez des commentaires pour améliorer ce contrôle, n’hésitez pas.

25/01/2010

[Silverlight 3/4] Introduction sur les Behaviors

Filed under: Débutant, Silverlight, Silverlight 3, Silverlight 4, WPF, XAML — Étiquettes : , , , , — sebastiencourtois @ 19:47

Introduit dans la version 3 de Microsoft Expression Blend, les Behaviors sont un système permettant de créer des comportements génériques pour des contrôles. L’un des gros avantages est que ces comportements peuvent être utilisé directement en XAML ou par Blend.

Nous allons partir d’un exemple afin d’étudier comment marche les behaviors. Notre Behavior exemple sera un ResizeBehavior qui va doubler la taille du contrôle auquel il est associé lorsque l’on clique sur le bouton gauche et qui va diviser par deux sa taille quand on clique avec le bouton droit. Le but du jeu étant que ce behavior fonctionne pour un plus grand nombre de contrôles possibles.

  • Comment coder un behavior ?

Tout commence avec la classe Behavior<T>. Cette classe est la classe de base des Behaviors. Elle se trouve dans l’assembly System.Windows.Interactivity. Cet assembly ne se trouve pas dans la GAC et pour cause, il s’agit d’un ajout fait par Expression Blend.

Si vous n’avez pas Expression Blend, je vous encourage à télécharger la version Beta pour Silverlight 4 / .NET 4 (http://www.microsoft.com/downloads/details.aspx?FamilyID=6806e466-dd25-482b-a9b3-3f93d2599699&displaylang=en).

Une fois Blend installé, vous devriez trouver cet assembly dans le répertoire  :

C:\Program Files\Microsoft SDKs\Expression\Blend 3\Interactivity\Libraries\Silverlight

C:\Program Files\Microsoft SDKs\Expression\\Blend Preview for .NET 4\Interactivity\Libraries\Silverlight

Pour créer un nouveau behavior, il suffit de créer une classe dérivant de Behavior<T> en spécifiant, à la place du T, le type d’objet que va être appelé à manipuler le Behavior. Dans notre exemple, nous prendrons des contrôles de type FrameworkElement (afin d’avoir accès aux propriétés Width/Height).

    public class ResizeBehavior : Behavior<FrameworkElement>
    {
        public ResizeBehavior()    {}

        protected override void OnAttached()
        {
            base.OnAttached();
            //Code 
        }
    
        protected override void OnDetaching()
        {
            base.OnDetaching();
            //Code
        }
    }

Comme on peut le voir, la classe ResizeBehavior contient :

    • Un constructeur : Pour initialiser les données
    • Un méthode OnAttached : Méthode hérité de Behavior<T>. Cette méthode est appelé lorsque le behavior est “attaché” à l’objet. Nous verrons plus loin quand, exactement, à lieu l’attachement. Pour le moment,  on admettra que l’attachement se fait lors de la création du contrôle sur lequel va intervenir le behavior.
    • Une méthode OnDetaching : Méthode hérité de Behavior<T>. Cette méthode est appelé lorsque le behavior est “attaché” à l’objet. Le détachement est réalisé lorsque le contrôle sur lequel s’applique le behavior est détruit (Nettoyage de la mémoire, désabonnement des événements …).

On remarque aussi un appel aux méthodes OnAttached/OnDetaching parentes. Ces appels sont nécessaires au bon fonctionnement du behavior et doivent toujours être présente au début des méthodes correspondantes.

Comme il a été expliqué au début de cet article, un behavior est un comportement associé à un contrôle. Ce contrôle est disponible au sein du behavior sous la forme de la propriété AssociatedObject (défini dans Behavior<T>.). Vous pouvez ainsi manipuler l’objet comme si vous étiez dans le code behind du fichier XAML dont il dépend. Ainsi nous allons ajouter les évènements MouseLeftButtonDown/MouseRightButtonDown sur l’objet associé afin de modifier la taille de l’objet associé.

    public class ResizeBehavior : Behavior<FrameworkElement>
    {
        public ResizeBehavior()    {}

        protected override void OnAttached()
        {
            base.OnAttached();
            //Code 
            this.AssociatedObject.MouseLeftButtonDown += new MouseButtonEventHandler(AssociatedObject_MouseLeftButtonDown);
            this.AssociatedObject.MouseRightButtonDown += new MouseButtonEventHandler(AssociatedObject_MouseRightButtonDown);
        }
    
        protected override void OnDetaching()
        {
            base.OnDetaching();
            //Code
            this.AssociatedObject.MouseLeftButtonDown -= new MouseButtonEventHandler(AssociatedObject_MouseLeftButtonDown);
            this.AssociatedObject.MouseRightButtonDown -= new MouseButtonEventHandler(AssociatedObject_MouseRightButtonDown);
        }

        void AssociatedObject_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            this.AssociatedObject.Width *= 2;
            this.AssociatedObject.Height *= 2;
        }

        void AssociatedObject_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
        {
            this.AssociatedObject.Width /= 2;
            this.AssociatedObject.Height /= 2;
        }
    }
  • C’est bien beau tout ça, mais comment on utilise ça en XAML ?

Coté XAML, il faut tout d’abord ajouter deux références. Une pour avoir accès au namespace System.Windows.Interactivity et une pour avoir accès au behavior.

    xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
    xmlns:local="clr-namespace:BehaviorDemo"  >

Ensuite, on ajoute un contrôle de type FrameworkElement (un Rectangle par exemple).

        <Rectangle Fill="Red" Width="200" Height="100">
            <i:Interaction.Behaviors>
                <local:ResizeBehavior />
            </i:Interaction.Behaviors>
        </Rectangle>

Le namespace System.Windows.Interactivity ajoute une classe Interaction permettant d’associer des Behaviors (mais aussi des Triggers / Actions… qui seront l’objet d’autres posts de blogs :)) .

Lors de l’exécution de l’application, Silverlight va créer l’objet Rectangle puis le ResizeBehavior. Une fois les deux objets créés, la méthode OnAttached de Behavior<T> est appelée. Lors de la destruction de l’objet Rectangle, c’est la méthode OnDetaching qui est appelée.

  • Moi j’aime pas le XAML. Je veux du C# …

Le code suivant permet d’associer un behavior à un contrôle.

Rectangle b = new Rectangle();
b.Fill = new SolidColorBrush(Colors.Purple);
b.Width = 400;
b.Height = 200;
Interaction.GetBehaviors(b).Add(new ResizeBehavior());
this.LayoutRoot.Children.Add(b);

  • Pourquoi utiliser des behaviors ?

L’intérêt des behavior est multiple :

    • Réutilisation du code : Le code de comportement n’est pas lié à un contrôle défini mais à une famille de contrôle. Cela évite de réécrire le code à chaque fois.
    • Instanciation directement dans le XAML (donc utilisable dans Blend par un designer)
    • Intégration dans Blend : Blend 3/4 gère les behaviors nativement et permet d’ajouter facilement ses propres behaviors.
  • Saupoudrons avec un peu d’animations …  

On modifie le ResizeBehavior afin d’avoir une animation un peu plus sympa.

    public class ResizeBehavior : Behavior<FrameworkElement>
    {
        public ResizeBehavior()    {}

        protected override void OnAttached()
        {
            base.OnAttached();
            //Code 
            this.AssociatedObject.MouseLeftButtonDown += new MouseButtonEventHandler(AssociatedObject_MouseLeftButtonDown);
            this.AssociatedObject.MouseRightButtonDown += new MouseButtonEventHandler(AssociatedObject_MouseRightButtonDown);
        }
    
        protected override void OnDetaching()
        {
            base.OnDetaching();
            //Code
            this.AssociatedObject.MouseLeftButtonDown -= new MouseButtonEventHandler(AssociatedObject_MouseLeftButtonDown);
            this.AssociatedObject.MouseRightButtonDown -= new MouseButtonEventHandler(AssociatedObject_MouseRightButtonDown);
        }

        void AssociatedObject_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            Storyboard sb = new Storyboard();
            DoubleAnimation da = new DoubleAnimation()
            {
                From = this.AssociatedObject.Width,
                To = this.AssociatedObject.Width * 1.5,
                Duration = new Duration(new TimeSpan(0, 0, 1)),
                EasingFunction = new ElasticEase()
            };
            Storyboard.SetTarget(da, this.AssociatedObject);
            Storyboard.SetTargetProperty(da, new PropertyPath("(Width)"));
            sb.Children.Add(da);

            DoubleAnimation da2 = new DoubleAnimation()
            {
                From = this.AssociatedObject.Height,
                To = this.AssociatedObject.Height * 1.5,
                Duration = new Duration(new TimeSpan(0, 0, 1)),
                EasingFunction = new ElasticEase()
            };
            Storyboard.SetTarget(da2, this.AssociatedObject);
            Storyboard.SetTargetProperty(da2, new PropertyPath("(Height)"));
            sb.Children.Add(da2);

            sb.Begin();
        }

        void AssociatedObject_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
        {
            Storyboard sb = new Storyboard();
            DoubleAnimation da = new DoubleAnimation()
            {
                From = this.AssociatedObject.Width,
                To = this.AssociatedObject.Width / 1.5,
                Duration = new Duration(new TimeSpan(0, 0, 1)),
                EasingFunction = new ElasticEase()
            };
            Storyboard.SetTarget(da, this.AssociatedObject);
            Storyboard.SetTargetProperty(da, new PropertyPath("(Width)"));
            sb.Children.Add(da);

            DoubleAnimation da2 = new DoubleAnimation()
            {
                From = this.AssociatedObject.Height,
                To = this.AssociatedObject.Height / 1.5,
                Duration = new Duration(new TimeSpan(0, 0, 1)),
                EasingFunction = new ElasticEase()
            };
            Storyboard.SetTarget(da2, this.AssociatedObject);
            Storyboard.SetTargetProperty(da2, new PropertyPath("(Height)"));
            sb.Children.Add(da2);

            sb.Begin();
        }
    }

Code XAML Associé :

<UserControl x:Class="BehaviorDemo.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
    xmlns:local="clr-namespace:BehaviorDemo"  >
    <StackPanel x:Name="LayoutRoot" Background="White">
        <Rectangle Fill="Red" Width="200" Height="100">
            <i:Interaction.Behaviors>
                <local:ResizeBehavior />
            </i:Interaction.Behaviors>
        </Rectangle>
        <Ellipse Fill="Yellow" Width="200" Height="100">
            <i:Interaction.Behaviors>
                <local:ResizeBehavior />
            </i:Interaction.Behaviors>
        </Ellipse>
    </StackPanel>
</UserControl>
  • … et voyons le résultat :

Voici des copies d”écrans du résultats :

behaviorAvant behaviorApres

Gauche : Après chargement / Droite : Après avoir cliqué avec le bouton gauche sur le rectangle rouge et l’ellipse jaune.

Ce post n’était qu’une introduction sur les behaviors. Je vais surement continuer sur ce sujet dans les prochains jours (notamment sur les behaviors/triggers et leurs applications plus business).

Si vous avez des questions et/ou remarques et/ou sujet de futur post, n’hésitez pas à poster un commentaire …

Remarque : Afin d’anticiper certains commentaires, je précise qu’il aurait été possible de créer l’animation dans le OnAttached et non en créer une nouvelle à chaque click :). Je referais peut être l’exemple afin de le rendre plus propre. Toutefois, le but de cet article est de montrer le principe des behaviors.

18/03/2009

Silverlight 3 Beta SDK Released ….

Filed under: Silverlight — Étiquettes : , , , — sebastiencourtois @ 16:06

Le Mix 2009 se tient actuellement à Las Vegas et, pour commencer cette série de conférence, Scott Guthrie a annoncé la sortie du SDK de Silverlight 3 Beta 1.

D’après des sites que j’ai pu lire les SDK et même la MSDN serait déja disponible :).

Au programme de Silverlight 3 : … pas beaucoup d’informations. Apparemment accélération graphique 3D ( mais pas comme WPF… juste pour des petits effets 3D type carrousels), Pixel Shader, Haute Définition pour les vidéos … je pense aussi des nouveaux contrôles et autres.

Les liens que j’ai trouvé sur le sujet (contient les liens pour télécharger la bête) :

http://arstechnica.com/microsoft/news/2009/03/silverlight-3-related-bits-start-to-arrive-early.ars

http://www.neowin.net/news/main/09/03/18/microsofts-silverlight-3-beta-sdks-released

Je n’ai pas encore eu le temps d’y toucher mais je m’y mets dès ce soir 🙂 …

[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.

04/03/2009

[Silverlight 2] Insertion d’un Tooltip dans l’en-tête du Datagrid

Filed under: Intermediaire, Silverlight, XAML — Étiquettes : , , , , — sebastiencourtois @ 15:26

Arrivé avec la RTW de Silverlight 2, le contrôle Datagrid permet d’afficher des données de façon structurées sous la forme d’une grille. Sa simplicité d’utilisation et sa personnalisation assez complète en XAML en font un objet indispensable pour toutes les interfaces Silverlight métiers (i.e autres que du jeu ou de la vidéo :)). Toutefois, cette personnalisation peut s’avérer complexe lorsqu’on souhaite modifier certaines parties qui n’ont pas été conçu pour être modifié … facilement.

J’ai une grille de données avec des en-têtes de colonnes se nommant “Header 1”,”Header 2” …. Pour chaque en-tête, je souhaite avoir un message lorsque je passe ma souris dessus.

Blog1

(Si vous êtes pressé, la solution se trouve à la fin du post)

Processus de recherche / Explications :

Je décide de rajouter ToolTipService.Tooltip=”test”  dans la balise d’un des DataGridTextColumn car cette propriété m’est proposée par l’IntelliSense.

Blog3-2J’obtiens une exception à l’exécution m’indiquant que le ToolTipService.Tooltip est un attribut inconnu pour l’élément DataGridTextColumn. (Texte exact : "XAML Parse Exception : Unknown attribute ToolTipService.ToolTip on element DataGridTextColumn.“)

ToolTipService est une propriété statique dans la classe FrameworkElement.

Blog3-5 Blog3-4

En analysant les hiérarchies ci dessus, on s’aperçoit que DataGridTextColumn ne dépend pas de FrameworkElement. Il faut donc utiliser une classe dérivant de FrameworkElement afin d’avoir accès à cette propriété. L’idée est donc de remplacer le texte de l’en-tête par un FrameworkElement de son choix (dans notre cas, un TextBlock) et de renseigner sa propriété ToolTipService.Tooltip.

Lorsqu’on regarde les propriétés disponibles pour les en-têtes pour le DataGridTextColumn, on s’aperçoit que les choix sont limités

Blog3-3La propriété Header ne prend que des chaines de caractères permettant de changer le titre. La propriété HeaderStyle permet de définir un Style pour l’en-tête. Pas de HeaderTemplate ou de propriétés de Template pour l’en-tête de colonne directement accessible par DataGridTextColumn.

L’astuce suivante a été trouvé par une collègue de travail: Simon Ferquel (MVP Silverlight).

En analysant la classe DataGridColumn dans Reflector, on peut voir que une propriété HeaderCell de visibilité internal. Cette propriété est de type DataGridColumnHeader dans le namespace System.Windows.Controls.Primitives. DataGridColumnHeader étant un ContentControl, on peut utiliser son ContentTemplate pour mettre tous les FrameworkElement que l’on souhaite :). Ne pouvant pas passer directement par la propriété HeaderCell à cause de sa visibilité, on utilise la propriété HeaderStyle pour faire la modification de l’en-tête.

Solution :

On utilise la propriété HeaderStyle de DataGridTextColumn pour redéfinir son HeaderCell. En XAML, cela donne :

Blog3-8Attention à ne pas oublier la ligne d’ajout du namespace au début du fichier XAML :

Blog3-9

Il est noté que cette exemple fonctionne avec toutes les classes héritant de DataGridColumn et pas uniquement pour DataGridTextColumn. 

Résultat final (le curseur de la souris a disparu lors de la capture) : Blog3-10

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