Astuces DotNet (Sébastien Courtois)

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.

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.