Dot.Blog

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

Article ! Le Binding Xaml, sa syntaxe, éviter ses pièges… (WPF/Silverlight)

imagePromis dans mon dernier billet, le voici enfin ! Ce nouvel article dédié au binding Xaml pèse 77 pages (le record était tenu jusqu’à lors par mon article M-V-VM avec Silverlight avec 70 pages) et se présente sous la forme d’un PDF et de 9 projets exemples.

Le binding cet inconnu … J’en parlais dans mon billet Le retour du spaghetti vengeur, le binding avec sa syntaxe pleine d’accolades et de chaînes de caractères non contrôlées à la compilation est un piège à bugs et la porte ouverte au code spaghetti.

Plutôt que de me lamenter et vous laisser vous embourber dans la sauce de ces spaghetti là, j’ai pris ma plume, et voici le résultat : 77 pages sur le binding Xaml, son fonctionnement, ses différentes syntaxes le tout illustré par des exemples clairs. L’article aborde aussi le débogue du binding assez délicat puisque Xaml échappe au débogueur de Visual Studio, et que le binding n’est que partiellement couvert par Intellisense.

Des conseils, du vécu, beaucoup d’exemples, voilà ce que vous trouverez dans cet article à télécharger en suivant le lien suivant :

Le Binding Xaml – Maîtriser sa syntaxe et éviter ses pièges (WPF/Silverlight)

(Attention, avant de cliquer sur le bouton “télécharger” attendez que la fiche détail de l’article soit bien affichée, sinon le site pensera que vous tentez un accès à une ressource non publique et vous demandera un login… Je reçois tous les jours des demandes d’ouverture de compte provenant de lecteurs trop impatients qui cliquent trop vite sur le bouton ! La prochaine version du site en Silverlight est en préparation et corrigera ce petit problème, mais d’ici là : slow down cowboy !).

Pour vous aider à vous faire une idée du contenu de l’article voici son sommaire :

Sommaire

  • Références    5
  • Code Source    6
  • Préambule    7
  • Le Binding Xaml : Ange ou Démon ?    7
    • Le Binding    8
    • Définition    8
    • Utilisations    9
    • Schéma du principe    9
    • Déclaration    10
      • Par code    10
      • En Xaml    11
  • Les modes de binding    13
    • Le mode OneTime    13
    • Le mode OneWay    13
    • Le mode TwoWay    14
      • Gestion du timing    14
    • Le mode Default    16
    • Le mode OneWayToSource    16
  • Hiérarchie de valeur    18
    • Règles de précédences    19
  • La notion de  DataContext    20
  • Les convertisseurs de valeur    21
    • Définition    21
    • Scénario    21
    • Implémentation    22
    • Utilisation    23
      • Instanciation    23
      • Invocation    23
      • Bonnes pratiques    24
      • Pour résumer    26
  • Les dangers du Binding    27
    • Des chaînes non contrôlées    27
    • Un langage dans le langage    28
  • On fait quoi ?    28
  • Déboguer le Binding    28
    • Vigilance    28
    • Une code Xaml court    29
    • Refactoring sous contrôle    29
    • Utiliser des outils intelligents    29
    • Utiliser Expression Blend    29
    • Vérifier les erreurs de binding dans la fenêtre de sortie    30
    • Créer un fichier des erreurs de Binding    30
    • La feinte du convertisseur inutile    32
    • Quelques outils supplémentaires    33
      • Spy++    33
      • ManagedSpy    33
      • Snoop    34
      • Mole    34
      • Reflector    34
      • Vos neurones    34
  • Les syntaxes du Binding    34
    • Le binding simple    34
      • Binding direct    35
      • Binding sur une propriété    35
      • Binding sur une sous-propriété du contexte    36
      • L’Element Binding    37
      • Convertisseurs de valeur    37
        • Paramètres de conversion    38
      • StringFormat    40
        • Injection de culture    41
        • Le ContentStringFormat    42
        • Gérer les nuls    43
    • Le Binding Multiple    44
      • La classe Personne    44
      • Le code du multi convertisseur    45
      • Le code Xaml    46
    • Le Binding XML    47
      • Binding sur une source Web    47
      • Binding sur une source XML en ressource    48
      • Binding sur une requête Linq To XML    49
    • Le Binding Relatif    52
      • Binding to Self    52
      • Le TemplatedParent Binding    53
      • Le Binding sur recherche d’ancêtre    54
      • Le Binding PreviousData    56
        • La source de données    58
        • La visibilité des flèches : la magie de PreviousData et du Binding Multiple    58
        • Le contenu des textes    61
      • Le Template binding    61
        • Utilité    61
      • Le Collection Binding    67
      • Le Priority Binding    69
  • Les propriétés de tous les bindings    74
  • Conclusion    76

Avril est froid et pluvieux mais les soirées rallongent, c’est le printemps même si cela n’y ressemble pas encore beaucoup, grâce à moi vous savez maintenant comment occuper vos soirées et vos weekend au lieu de faire l’idiot dans les embouteillages des vacances : Lisez cet article !

Et Stay Tuned !

Quelques conseils de design (UserControl, Blend, Visual State manager, Silverlight / WPF)

L’une des avancées les plus intéressantes introduite dans Silverlight 2 (puis reprise sous WPF et naturellement sous Silverlight 3) est très certainement le Visual State Manager. Gestionnaire des états visuels simplifiant la conception visuelle des contrôles (UserControl). Bien utiliser le VSM, outre de rendre plus simple la représentation des états visuels d’un composant, apporte aussi une clarification essentielle à la gestion des transitions entre ces derniers.

Etats et transitions

Un contrôle peut être ou non visuel. Un Timer n’est pas visuel. Une Combobox l’est. Nous parlerons bien entendu ici uniquement des contrôles qui possèdent un visuel.

Un Etat visuel peut être compris comme une allégorie d’un ou plusieurs états logiques du contrôle. Les états logiques sont ceux définis dans le code fonctionnel, comme par exemple IsEnabled ou IsChecked. Il ne faut pas confondre état et propriété. Les états sont le plus souvent représentés par des propriétés (ils peuvent simplement être des champs internes ou le résultat d’un calcul) mais toute propriété ne représente pas un état (la couleur Foreground ou le type de curseur en sont des exemples).

je parle d’allégorie car comme je l’évoque dans le billet “Le défit des nouvelles interfaces Silverlight et WPF – La cassure conceptuelle”, la représentation visuelle d’un contrôle (et de ses états) est l’aboutissement d’une démarche intellectuelle et conceptuelle qui a justement pour but de créer un univers dans lequel les acteurs (les contrôles) doivent chacun avoir leur place et leur comportement. Entre visuel et fonctionnel il n’y a pas de relation d’identité (au sens d’un schéma conceptuel de données - relation de type 1-1). Le visuel créé une identité pour l’acteur, ce qui est différent et ne s’entend pas dans le même sens.

Je disais aussi qu’un état visuel représente un ou plusieurs états logiques. Dans certains cas deux (ou plus) états logiques peuvent être “résumés” par un seul état visuel. Cette combinaison prend son sens ponctuellement, au cas par cas, et ne peut pas être généralisée en une règle absolue. Néanmoins, lorsqu’on créé le visuel d’un contrôle, il faut garder à l’esprit cette possibilité. L’œil peut discerner de nombreuses subtilités dans les formes et les couleurs alors qu’il lui faut plus de temps pour interpréter des séries de données chiffrées ou textuelles…

Les transitions ont aussi leur importance. On ne passe pas d’un état visuel à un autre de façon abrupte, sauf si cela est volontairement assumé. Les transitions fluidifient l’interface, créent une continuité visuelle. Si les transitions ne portent pas de sens en elle-mêmes, à la différence des états, elles jouent un rôle important dans l’expérience utilisateur (UX).

Trois phases

La conception des états visuels et des transitions peut se découper en trois phases différentes.

Phase statique

Cette phase de la conception consiste à créer des états dans le VSM et à définir l’aspect du contrôle dans chacun d’eux. On en profite pour identifier les groupes d’états.

Au sein de chaque groupe les états sont mutuellement exclusifs, les états de différents groupes étant indépendants et simultanés (une Checkbox peut à la fois avoir le focus et être cochée ou décochée par exemple. IsChecked appartient à une groupe d’états différent de celui définissant l’aspect visuel pour le focus).

Lors de la phase de conception statique on se préoccupe de l’aspect que prend le contrôle dans chacun des états. On vérifie aussi la compatibilité visuelle entre les états des différents groupes d’états. Si cette étape est qualifiée de statique c’est pour souligner qu’on ne se soucie pas encore des transitions (la dynamique visuelle), mieux vaut rester concentré sur les groupes d’états et les états eux-mêmes.

On veillera à ne pas manipuler une même propriété du contrôle dans plusieurs groupes d’états pour des raisons de cohérence. D’ailleurs si on commet cette imprudence Blend le signalera par un petit symbole de danger (triangle assorti d’un message de type tooltip). On peut assumer une telle situation si on en mesure toutes les conséquences, c’est pourquoi Blend signale le problème mais n’interdit pas la situation. Mais en règle générale cette alerte trahit une mauvaise conception !

Etat “Base”

Vous l’avez peut-être remarqué, dès qu’un groupe d’états existe le VSM ajoute systématiquement à la liste un état spécial appelé Base. Cet état permet de visualiser le contrôle hors de tous les états visuels définis. Toute modification effectuée dans l’état Base se propage à tous les états définis et n’est enregistré dans aucune time-line.

Etat par défaut

Un contrôle bien conçu devrait posséder pour chaque groupe d’états un état par défaut. En effet l’état Base n’existe pas en tant qu’état visuel, il s’agit juste d’un mode d’édition spécial du VSM. Lorsqu’une instance du contrôle est créée il est forcément dans un état donné (par exemple IsChecked=False pour une case à cocher). Il se trouve même souvent dans plusieurs états précis qui seront représentés par plusieurs groupes d’états. De fait il ne faut pas se reposer sur l’état Base mais plutôt créer un état par défaut pour chaque groupe qui traduit l’aspect visuel du contrôle lorsqu’il vient d’être initialisé (ou “remis à zéro” si une telle fonction est disponible dans le contrôle). Dans les contrôles existants vous pouvez remarquer que de tels états par défaut existent. Par exemple dans le groupe CommonStates de la classe Button on trouve un état Normal, dans le groupe CheckedStates (d’une case à cocher) on trouve UnChecked, etc.

L’état par défaut de chaque groupe ne doit pas forcément porter un même nom (ce qui poserait un problème pour le VSM de toute façon, il n’autorise pas que deux états portent le même nom). Au contraire, cet état par défaut de chaque groupe doit, comme dans les exemples donnés ci-dessus, porter un nom ayant un sens en rapport avec le groupe.

Il est parfaitement valide de créer un état par défaut (ou non) qui ne fait que recopier la situation de l’état Base. Il suffit de créer l’état et de ne rien modifier… Cela permet souvent de clarifier les choses. Prenons l’exemple de la Checkbox, la croix est ajoutée dans l’état Base mais est cachée. En revanche cela ne lève pas l’obligation de créer un état Unchecked dans le groupe CheckedStates. Cet état est juste créé mais non modifié puisqu’il reprend l’aspect de l’état Base (croix cachée).

Validation des états

Arrivé ici il faut tester tous les états. Cela peut se faire sous Blend mais il ne faut pas hésiter à créer une fiche de test, y placer le contrôle, et ajouter des boutons, sliders, et autres éléments d’interface pour tester au runtime le changement de chaque état, la cohérence entre les groupes, etc.

Dans certains cas très simples la conception des états visuels peut s’arrêter là. On peut vouloir des transitions franches et immédiates et il n’y a alors plus rien à ajouter.

Dans d’autres cas il s’avère essentiel d’aborder la seconde phase.

Phase des transitions

La version simple consiste à utiliser le réglage par défaut que le VSM propose pour chaque groupe d’états. Dans de nombreux cas cela peut être suffisant. Il suffit alors de définir un temps pour l’ensemble des transitions. Le VSM calculera à l’exécution les animations correspondantes.

La version plus évoluée consiste à utiliser les options du VSM pour chaque état afin de régler les transitions d’entrées et de sorties de celui-ci. Il peut en effet s’avérer nécessaire d’avoir des timings différents selon le sens de la transition et l’état précédent. Par exemple il est courant que les états visuels de type clic souris soient instantanés pour ne pas donner l’impression à l’utilisateur que le logiciel est “mou”, lent à répondre, alors que le relâchement de la souris peut au contraire accepter un temps assez long, donnant une sensation de douceur, d’amorti “luxueux”.

Encore une fois les choses peuvent s’arrêter là. Bien entendu après avoir testé toutes les transitions. N’oubliez pas que Blend 3 propose désormais une option permettant de visualiser en conception l’effet des transitions lorsque le VSM est actif. Cela fonctionne très bien, sauf pour les transitions utilisant des storyboards.

Phase des transitions dynamiques

Justement le troisième niveaux de personnalisation consiste à créer des storyboards au lieu de se contenter des timings réglés dans le VSM. Dans ce cas on créé une animation complète pour la transition en utilisant les fonctions des storyboards (time line, boucle for-ever, autoreverse…).

Pouvant s’utiliser dans les storyboards ou sur les animations par défaut créées par le VSM, les fonctions de ease in et ease out permettent d’ajouter une touche “organique”, plus naturelle, aux animations. Silverlight 3 propose de nombreux modes (comme le rebondissement par exemple) qui, bien utilisés, finissent le visuel et le rende plus “pro”.

Relation avec le code

Il y a toujours eu deux aspects à la création d’un contrôle personnalisé, même sous Win32 avec les MFC ou sous Delphi avec la VCL. La création du visuel d’un côté et celle du code fonctionnel de l’autre. Tout a changé mais pas cette séparation qui s’est, au contraire, renforcée.

Le designer (l’infographiste) conçoit le visuel d’un contrôle, il prévoit comment le contrôle se comportera pour l’utilisateur, comment il s’animera, comment ses différents états seront visualisés.

Pour l’informaticien le point de vue est différent. Il ne doit pas se soucier de l’aspect visuel mais du comportement du contrôle, de sa logique sous-jacente.

Cela implique de prendre en compte tout ce qui peut modifier les états internes du contrôle, la cohérence de la machine logique lors de son fonctionnement. Que les changements d’état soient le fait de l’utilisateur, d’une animation créée par le designer ou de tout autre acteur, peu importe.

Il faut oublier le visuel qui d’ailleurs n’existe pas forcément (la séparation du travail design / codage est telle qu’on peut aujourd’hui commencer un projet par une réflexion purement conceptuelle et graphique avec un designer plutôt que d’écrire le cahier des charges avec un informaticien…).

Repérer les ilots d’états (qui deviendront ou non des groupes d’états dans la partie visuelle), le ou les graphes de changement d’état, vérifier quels sont les graphes d’états interconnectés (et qui forment un groupe ou des sous-groupes) de ceux qui sont totalement indépendants, tout cela est essentiel.

L’initialisation

On retrouve ici une préoccupation déjà évoquée quand nous parlions de la partie visuelle. Pour le designer il s’agissait de ne pas confondre l’état Base avec les états par défaut qu’il faut créer pour chaque groupe d’états.

Côté code il est indispensable que toute nouvelle instance d’un contrôle se “positionne” correctement. Normalement son initialisation comporte d’une façon ou d’une autre des valeurs par défaut. Sous C# il est possible d’atteindre cet objectif de multiples façons : soit par le biais de champs initialisés lors de leur déclaration, soit par le biais de valeurs par défaut mises en place dans les métadonnées des propriétés de dépendance, soit par code dans le constructeur de la classe, soit par code dans le gestionnaire de l’événement Loaded du contrôle (ou de l’une de ses sous-parties), soit encore par code XAML.

Le fait que ces différentes solutions puissent être utilisées simultanément dans un même contrôle n’aide pas forcément à rendre les choses claires… C#, comme tout langage, n’interdit pas le code spaghetti, hélas !

Mais ici en dehors de la pure stylistique, de l’académisme, voire même des bonnes pratiques, c’est aussi la stabilité visuelle qui risque d’être compromise si le contrôle ne s’initialise pas clairement dans une suite d’états bien déterminés (généralement les états par défaut).

Un contrôle a ainsi la charge lors de son initialisation de se positionner sur la case “départ” de son graphe d’états. Cela semble évident mais parfois les portes ouvertes sont celles qui méritent le plus d’être enfoncées. Tout le monde peut voir qu’une porte est fermée. Il faut être très perspicace pour s’apercevoir que l’absence d’un obstacle ne signifie pas qu’il n’y a rien à faire…

Car si rien n’est fait, le contrôle va être frappé d’une sorte de schizophrénie : il peut être doté d’un code qui s’initialise correctement et d’une interface qui en fait tout autant, hélas les deux personnalités existent simultanément et ne se connaissent pas forcément. Les cas de personnalités multiples sont passionnants en psychanalyse (je vous conseille d’ailleurs un excellent vieux livre écrit par une patiente atteinte de ce syndrome : Joan, autobiographie d’une personnalité multiple) et même s’il est plus facile de déboguer un programme qu’un cerveau humain, ce type de désordre du comportement ruine totalement les efforts pour créer une interface riche et cohérente… Alors autant y penser ! Cela signifie dans la pratique que vous devez centraliser l’initialisation logique de votre contrôle (l’ensemble de ses états) et l’initialisation visuelle. Cette dernière s’effectue en général en appelant GotoState avec le paramètre d’animation à false (il s’agit d’une bonne pratique. On évite d’animer les contrôles lorsqu’ils s’initialisent). Ainsi, code et visuel sont initialisés conjointement et sont en phase.

Encore une fois ne prenez pas Base pour un état. Notamment il n’existe aucune transition gérée par le VSM entre Base et les autres états. Si votre contrôle n’est pas volontairement initialisé dans ses états par défaut aucune animation ne sera jouée lors du premier changement car Base vers un état ne génère aucune transition.

Conclusion

La création d’un contrôle est une opération longue et minutieuse car elle doit être réfléchie. Et plutôt deux fois qu’une : à la fois sous l’angle visuel et sous l’angle du code. Ces deux aspects bien séparés aujourd’hui introduisent la nécessité de penser à leur indispensable synchronisation pour garantir la cohérence du contrôle.

Mais avec un peu d’habitude on s’aperçoit bien vite qu’il est mille fois plus facile de créer un nouveau contrôle visuel avec Blend (que cela soit pour Silverlight ou WPF) qu’avec les technologies précédentes. Alors cela vaut bien un peu d’attention au départ !

Bonne conception !

N’oubliez pas que seul Blend (qui hélas n’existe pas en version express gratuite) permet sérieusement de travailler sous Silverlight et WPF. N’hésitez pas à acheter ce complément indispensable ou à vous le faire offrir (après tout, un Blend 3 pour Noël c’est mieux qu’une cravate ou une bague – pas de sexisme sur Dot.Blog! – pensez-y ! )

Et Stay Tuned !

Blend survey : donnez votre avis pour le prochain Blend 4 !

Microsoft prépare déjà la prochaine version de Blend qui, avec la version 3, est déjà un outil d'une grande maturité. Mais on peut toujours faire mieux... Alors au lieu de grogner dans votre coin, dites ce que vous aimez ou pas, proposez ce qui vous semble manquer, bref répondez au sondage lancé par l'équipe Blend : https://connect.microsoft.com/Expression/Survey/Survey.aspx?SurveyID=9391

Attention, ce sondage est assez long (pas énorme mais bien plus que trois cases à cocher) prévoyez un petit quart d'heure, et, of course, totalement en anglais avec pas mal de zones de commentaires qu'il faudra remplir dans la même langue. N'oubliez pas que google traduction est votre ami et qu'il vaut mieux donner votre avis que de vous taire, même si certains commentaires sont traduits par une machine (demandez à un collègue plus anglophone que vous de relire quand même :-) ).

A la clé, un Zune à gagner par tirage au sort. Mais surtout, un Blend 4 plus proche de vos besoins.

Alors exprimez-vous !

AnimatableSoundPlayer. Ou comment synchroniser du son sur une animation Silverlight

Hier je vous parlais d'animation et de sons et je vous présentais une petite démonstration (l'oscilloscope) dont le but était de montrer qu'on pouvait rapidement obtenir un effet visuel assez complexe sans programmation pour peu qu'on se donne la peine d'utiliser Blend et ses neurones... Pour synchroniser le bip avec le spot lumineux j'avais alors utilisé une ruse en indiquant qu'hélas Silverlight ne permettait pas de synchroniser du son dans une animation.

Je vous proposais alors une idée de solution en promettant une implémentation le temps de vous laisser réfléchir.

Regarder d'abord la démo ci-dessous. En cliquant sur "Start" vous lancerez une animation (StoryBoard) dont le but premier est de déplacer la boule verte un peu comme dans un billard. Elle rebondit sur des taquets pour finir dans un gobelet. Bien entendu tout cela est assez moche, les beaux dessins ne font pas partie de la question :-) Mais, en revanche, et si votre carte son fonctionne, vous noterez que plusieurs sons peuvent être entendus en parfaite synchronisation avec les "chocs" de la boule sur les taquets ou dans le gobelet final. Je vous laisse essayer et on en reparle :

[silverlight:source=/SLSamples/SyncSound/SyncSound.xap;width=251;height=242]

Bon. Vous avez vu et aussi entendu ?

Comment est-ce possible (sachant que pour l'utilisateur Blend qui créé l'animation tout cela ne réclame aucun code) ?

Forcément il y a du code... En fait un petit UserControl dont le principe est fort simple.

AnimatableSoundPlayer

C'est son petit nom. Un joueur de sons animable. Joueur de sons car ce UserControl n'a pas de visuel. On pourrait en réalité jouer des vidéos de la même façon il suffirait de relooker le contrôle, mais ce n'était pas le but. Joueur de son animable car ce qui manque au MediaElement c'est bien d'avoir des propriétés "animables" pouvant être modifiées sur une timeline.

Le UserControl AnimatableSoundPlayer est ainsi une coquille presque vide, il ne contient qu'un MediaElement. Tout le reste est du code, fort peu en réalité.

Dans un premier temps j'ai ajouté plusieurs propriétés de dépendance qui relaient les propriétés du MediaElement : la Source (une Uri), le Volume, etc. Ensuite j'ai fait de même pour les principales méthodes (Play, Stop...).

Deux choses ont été ajoutées. Côté méthodes j'ai créer PlayFromStart(). Elle ne fait que faire "Stop(); Play();". C'est tout bête mais très souvent on a besoin d'enchaîner ces deux méthodes pour s'assurer qu'un son est bien rejouer depuis le début, il faut rembobiner la bande avec Stop() pour réécouter un son.

La seconde chose ajoutée est la plus importante. Il s'agit de la propriété de dépendance AnimatablePlay de type double. Pourquoi double ? Simplement parce Silverlight sait animer des doubles et par forcément autrechose... Le mécanisme intéressant se trouve dans la méthode AnimatablePlayChanged, le callback de modification de la valeur initialisé lors de la création de la propriété de dépendance. A l'intérieur de ce callback nous trouvons la logique de cette propriété :

   1:  #region AnimatablePlay property
   2:          /// <summary>
   3:          /// Gets or sets the animatable play.
   4:          /// Value = 0 means <see cref="Stop"/>
   5:          /// Value sup.to 0 means <see cref="PlayFromStart"/>
   6:          /// Value inf.to 0 means <see cref="Play"/> / <see cref="Pause"/>
   7:          /// </summary>
   8:          /// <value>The animatable play.</value>
   9:          [Category("Media")]
  10:          public double AnimatablePlay
  11:          {
  12:              get { return (double)GetValue(AnimatablePlayProperty); }
  13:              set { SetValue(AnimatablePlayProperty, value); }
  14:          }
  15:   
  16:          public static readonly DependencyProperty AnimatablePlayProperty =
  17:              DependencyProperty.Register("AnimatablePlay", typeof(double),
  18:              typeof(SynchedSoundPlayer), new PropertyMetadata(0.0d, AnimatablePlayChanged));
  19:   
  20:          private static void AnimatablePlayChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  21:          {
  22:              var mp = ((SynchedSoundPlayer)d);
  23:              if ((double)e.NewValue > 0d) mp.PlayFromStart();
  24:              if ((double)e.NewValue == 0d) mp.Stop();
  25:              if ((double)e.NewValue < 0d)
  26:              {
  27:                  if (mp.internalMP.CurrentState == MediaElementState.Playing) mp.Pause();
  28:                  else mp.Play();
  29:              }
  30:          }
  31:   
  32:          #endregion

Simple et efficace : toute valeur positive déclenche un "PlayFromStart", d'où l'utilité de cette méthode qui nous assure que le son est bien rejoué depuis le début. Toute valeur nulle appelle "Stop" et toute valeur négative entraîne un cycle Play/Pause selon l'état actuel du contrôle.

Le UserControl relaie aussi l'événement MediaFailed si on désire être prévenu en cas de difficulté rencontrée par le MediaElement pour charger le son (qui peut être dans le Xap comme dans la présente démo ou sur un serveur distant).

Grâce à cette convention il devient très simple de synchroniser des sons avec une animation !

Si on suit l'exemple live proposé plus haut : Trois sons sont joués, un numéro est indiqué sur chaque "obstacle" pour mieux se repérer. Sur ces trois sons il y en a un qui utilisé deux fois. Pour synchroniser ces trois sons on reprend la timeline de la boule verte et à chaque fois qu'on désire qu'un son soit joué on incrémente sa propriété AnimatablePlay. Simple. Vu la taille des valeurs maximales d'un double, on ne risque pas de tomber sur une limite... surtout qu'il n'est pas interdit de faire un retour à zéro quand on le veut.

Un petit détail à savoir : quand on créé une animation, par défaut Silverlight effectue une interpolation des valeurs fixées dans les keyframes qui se suivent. Si vous tapez la valeur 1 à la frame A et que vous tapez 2 à la frame B, durant le temps qui sépare A de B la valeur augmentera progressivement de 1 vers 2. C'est le comportement par défaut, ce qui est bien utile puisque justement le plus souvent on désire avoir une continuité dans le changement des valeurs (déplacements notamment). En revanche il y a des cas où on préfère que la valeur change brutalement, une "anti animation" en quelque sorte. Cela est parfois utile. Pour utiliser AnimatableSoundPlayer ce n'est pas une option, c'est une obligation. Il faut que les valeurs ne changent que lorsqu'une nouvelle keyframe est rencontrée. Sinon le son sera rejoué sans cesse depuis le début à toutes les valeurs intermédiaires, ce qui n'est pas du tout l'effet recherché.

Pour arriver à ce résultat il suffit de ne pas oublier de paramétrer les keyframes servant à animer le son afin qu'à la place d'une EasingDoubleKeyFrame le type devienne DiscreteDoubleKeyFrame. Sous Blend il suffit de cliquer sur la keyframe en question et de la passer en mode "Hold in". C'est la seule contrainte du composant.

Et voilà ! Avec un peu d'imagination on peut parfaitement créer un composant réutilisable qui ne prend que quelques lignes de code et qui permet de synchroniser du son dans une animation Silverlight. C'est pas magique ?

Je suis certain que vous trouverez des tas d'améliorations à porter au composant, alors n'hésitez surtout pas à m'en faire part je pourrais même diffuser vos versions modifiées si vous le voulez.

Le code du projet fourni contient l'exemple complet ainsi que le code du composant et les sons utilisés.

Quelques restrictions : Si vous modifiez le code obligez vous à publier le code source de votre version gratuitement. Si vous publier un article ou un billet de blog, soyez sympa et indiquez le lien vers mon billet. Si vous utilisez mon code dans un projet, commercial ou non, dites le moi ça me fera plaisir.
Si vous respectez ce petit deal, alors faites ce que voulez du code. Dans la négative que les foudres du Grand Bug Céleste s'abattent sur vous et chaque octet que vous coderez jusqu'à la fin de vos jours (c'est bien horrible ça non ? Laughing).

Enfin bon, dans tous les cas, si vous en voulez encore du Silverlight, vous connaissez le refrain : Stay Tuned !!!

Le code du projet : SyncSound.zip (209,05 kb)

Animations, Sons, Synchronisation, Ease in/out sous Blend

Effets visuels et sonores font partie des nouveaux éléments avec lesquels le concepteur de logiciel doit apprendre à jouer pour créer des interfaces attrayantes. Les réflexes du développeur le porte à se jeter sur son clavier, tout comme ceux du graphiste le pousse à sortir son bloc et son crayon. Comme tout réflexe il faut savoir s'en méfier...

Prenons l'exemple de l'affichage ci-dessous qui simule un oscilloscope :

[silverlight:source=/SLSamples/MovingSpot/MovingSpot.xap;width=288;height=237]

(passez la souris sur "settings" pour ouvrir la fenêtre de paramètres qui permet de jouer sur la lumonisité des carrés de ciblage, le flouté du spot et sur le volume du bip)

Pour créer l'effet du spot et de ses échos visuels le développeur aura donc tendance à se ruer sur son clavier et, s'inpirant des sprites (voir mon billet sur la création d'un effet de neige), il voudra créer une classe pour le spot, puis recherchera la formule mathématique de la courbe du déplacement du spot pour animer ce dernier, les échos étant créés à la volée avec une animation d'opacité décroissante. Tout cela va fonctionner parfaitement. Au bout du compte on disposera même d'un véritable affichage de type oscilloscope réutilisable avec des courbes différentes.

Mais s'agissant juste d'un affichage, d'un effet visuel, n'est-ce pas un peu lourd ?

On peut en effet réaliser la chose bien plus rapidement et sans avoir de debug à faire (puisque pas de code) si on "pense" design, pur dessin. Et au lieu d'ouvrir Visual Studio, ouvrons Blend...

Bien plus que l'exemple lui-même (assez simple) c'est cette démarche sur laquelle je souhaite attirer votre attention.

Côté réalisation comment cela se passe alors ? Deux contraintes nous sont données : animer un spot selon une courbe "réaliste" et avoir un rendu de la rémanence de l'écran. Prenons le déplacement pour commencer: il suffit de positionner un cercle à gauche de l'écran de l'oscilloscope (une image réalisée sous Design), d'ouvrir un StoryBoard et de créer une keyframe à 2 secondes du début puis de déplacer le cercle jusqu'à le faire sortir de l'écran. Le mouvement horizontal est réglé. Concernant le mouvement vertical et la simulation d'une courbe amortie, l'astuce consiste à déplacer en début d'animation le spot sur l'axe Y pour le faire monter. C'est tout, l'effet de courbe sera rendu en utilisant astucieusement les nouveaux modes d'Ease in et out de Silverlight /Blend. Le point de déplacement Y sera doté d'un Quintic In pour amortir le mouvement, le dernier point de l'animation sera lui doté d'un Elastic Out avec 5 oscillations et un Spingness de -2. On peut bien entendu ajuster ces éléments à sa guise.

L'effet du déplacement est ainsi rendu sans développement. Bien entendu il s'agit d'un simple affichage qui n'effectue pas le rendu d'une "vraie" courbe passée en paramètre. Mais ce n'était pas ce qu'on voulait. Juste un effet visuel "réaliste" en y passant le moins de temps possible.

Pour l'écho visuel ? La ruse est un peu grosse mais elle passe plutôt pas mal : il suffit de copier le spot et son animation deux fois puis de décaler légèrement les timelines vers la droite. On change ensuite l'opacité du 1er et du 2d écho (de façon décroissante). L'effet est ainsi rendu sans aucun code.

Un oscilloscope plus vrai que nature ! Il ne lui manque que la parole pourrait-on dire. Justement, ajoutons un son pour "faire plus vrai" (disons plutôt "plus cinéma" car les vrais oscillos ne font pas de bip). Au départ on avait envisagé de lancer l'animation du spot en mode bouclage infini. Hélas les storyboards n'ont qu'un seul événement : Completed qui ne sera jamais déclenché en plus (puisque bouclage infini). Comment attraper le début de l'animation pour jouer le son et le synchroniser avec l'affichage du sport ?

Pas facile, et pas vraiment de solution pour l'instant. Mais il n'est pas interdit de faire marcher ses neurones ! Nous allons simplement supprimer le mode infini à la boucle d'animation du spot. Ensuite nous allons pouvoir gérer l'événement Completed de celle-ci. Dans le gestionnaire il suffira alors de relancer l'animation, on en profitera alors pour activer le son qui est placé dans un MediaElement (donc Stop() puis Play()).

Le son est maintenant synchronisé avec l'affichage.

Mais si le son devait être déclenché au milieu de l'animation ? Hmm.. Là nous serions un peu coincé je pense. Tout de suite le développeur dirait "ha je l'avais bien dit, si j'avais fait une gestion de sprite j'aurais presque fini!". Peut-être. Mais revenir à une animation pas à pas pour avoir la main n'est toujours pas ce qu'on veut.

Il est dommage que Silverlight ne puisse pas animer le play et le stop d'un MediaElement, c'est un point faible car un comportement largement utilisé sous Flash par exemple. Mais si tel était notre besoin, là encore, il serait possible de réfléchir un peu au lieu de repartir sur une animation pas à pas complexe.

Par exemple nous pourrions créer un petit UserControl possédant une propriété PlaySound (une propriété de dépendance bien entendu sinon elle ne sera pas animable) pouvant passer de 0 à 1 pour bénéficier de l'animation des Doubles sachant que seul le passage à la valeur exacte 1.0 déclenchera la séquence Stop/Play du MediaElement intégré. Dès lors ne resterait plus qu'à poser ce contrôle sur notre application et d'animer sa propriété PlaySound en la faisant passer de 0 à 1.0...

Voilà un bon exercice sur lequel je vous laisse vous amuser un peu. La solution dans un prochain billet !

Pour le reste de l'exemple il n'y a rien de bien compliqué. La petite fiche des settings est dessinée sous Expression Design, son titrage a été converti en Paths poru éviter d'avoir à intégrer la fonte à l'application Silverlight. Les sliders sont reliés aux propriétés qu'ils modifient par la nouvelle possibilité de binding élément à élément de Silverlight 3.

Pour le petit composant permettant de synchroniser le son dans une animation, je ne dirais qu'une chose : Stay Tuned !

J'allais oublier, le code du projet : MovingSpot.zip (91,58 kb)

La mer, les propriétés de dépendance et les user control's...

La mer... un souvenir qui va s'effacer jusqu'à l'année prochaine... Mais pour prolonger le plaisir nous avons Silverlight !

Et comment mieux rendre grâce à la Grande Bleue qu'en fabriquant un User Control la mettant en scène ? Et bien c'est ce que nous allons faire, ce qui permettra ludiquement d'aborder un problème épineux concernant les propriétés de dépendance sous Silverlight. Mais d'abord le visuel :

[silverlight:source=/SLSamples/LaMer/LaMer.xap;width=480;height=480]

La mer bleue, ou la mer rouge, au choix... et c'est bien ce choix qui va poser problème.

La base du UserControl 

Concernant le composant lui-même je suis parti d'un UserControl vide créé pour l'occasion. A l'intérieur un Path dessiné avec l'outil plume. Ce path est dupliqué deux fois (ce qui donne donc 3 exemplaires au final). Les deux copies sont modifiées : l'une tassée en largeur, l'autre agrandie sur le même axe.

Un StoryBoard complète le tout : les trois vagues sont déplacées de gauche à droite, l'animation est mise en mode Auto-Reverse et boucle infinie. Pour un mouvement plus doux en début et fin j'ai ajouté un ease in/out choisi parmi les nouveaux modes offerts par SL 3.

Le tout est englobé dans une grid pour bénéficier du clipping, le layout root étant une ViewBox, mais cette partie là de la cuisine interne du composant n'est pas forcément la plus subtile. Pour terminer j'ajoute un rectangle sans bordure qui est placé en mode Stretch au fond du Z-Order, il servira a définir un éventuel background.

Jusqu'à là rien de bien compliqué, juste un peu d'imagination est suffisant.

Là où ça se complique c'est lorsqu'il faut gérer la couleur des vagues et celle du rectangle de fond...

Héritage et propriété de dépendance sous Silverlight

Pour terminer correctement le UserControl il faut en effet penser à son utilisation. C'est à dire à ajouter des propriétés qui permettront à l'utilisateur du contrôle (le développeur ou l'intégrateur sous Blend) de modifier ses caractéristiques sans avoir besoin, bien entendu, de bricoler le source du contrôle lui-même.

Ici, nous souhaitons pouvoir modifier la couleur des vagues et celle du fond. Parfait me direz-vous, cela tombe bien, un UserControl descend de Control qui lui-même définit deux propriétés on ne peut plus à propos : Foreground et Background. Il "suffit" de les surcharger.

En effet, "il suffit de". Yaka.

Première chose, ces propriétés sont dites de dépendance (dependency property). Voir à ce sujet mon article Les propriétés de dépendance et les propriétés jointes sous WPF (article à télécharger). Ces propriétés ne sont pas définies comme ce qu'on nomme aujourd'hui pour les différencier les "propriétés CLR", les propriétés habituelles. Je vous renvoie à l'article cité ici pour creuser la question si vous ne connaissez pas les propriétés de dépendance.

Dès lors, et telles que fonctionnent ces propriétés spécifiques de Silverlight et WPF, pour les surcharger il faut passer par un système de métadonnées autorisant l'affectation d'une méthode callback. Dans cette dernière il est facile de répercuter sur le visuel les changements de valeur de la propriété. L'override d'une propriété de dépendance est donc assez simple. Sous WPF. Et c'est là qu'est le problème.

En effet, tout à l'air tellement merveilleux sous Silverlight depuis la version 2 qui accèpte du code C#, qu'on en oublie que si tout le Framework .NET pouvait tenir dans quelques méga octets on se demanderait bien pourquoi l'installation du dit Framework pour une application classique (desktop) réclame des dizaines et des dizaines de méga octets... Y'a un truc. Y'a même une grosse astuce je dirais : forcément yapatou. En clair, le Framework Silverlight est un découpage chirurgical de haute précision pour donner l'impression que tout fonctionne tout en évitant 90% du code du Framework. Et il y a des petits bouts qui manquent, et parfois des gros !

Concernant les propriétés de dépendance, l'équipe de Silverlight a implémenté le principal mais a laissé de côté les subtilités. Les métadonnées sont par exemple moins sophistiquées. Mais il n'y a pas que les données qui ont été simplifiées, les méthodes aussi. Et de fait, en tout cas pour l'instant, il manque aux propriétés de dépendance Silverlight la possibilité de les surcharger.

Aie ! Comment réutiliser Foreground et Background définies dans Control et accessibles dans le UserControl s'il n'est pas possible de modifier les métadonnées et d'enregistrer notre propre callback ? J'ai longuement cherché car le problème est loin d'être évident à résoudre. Certains préconisent même face à ce problème de redéfinir vos propres propriétés. C'est tellement horrible comme solution que je m'y suis refusé. Comment avoir le courrage de définir une couleur de fond et une couleur d'avant plan au sein d'un composant visuel qui affichera fièrement de toute façon Foreground et Background qui n'auront, hélas, aucun effet ? Quant à faire une réintroduction de ces propriétés (avec le mot clé "new"), n'y pensez pas, j'ai essayé et ça coince un peu (par code ça marche, mais le XAML se fiche de la redéfinition et utilise toujours la propriété originale, ce n'est pas un bug mais une feature ou plutôt un effet assumé du fameux découpage savant dans le Framework).

J'avoue que pour l'instant cet oubli volontaire dans Silverlight me chagrine. Pourquoi l'équipe Silverlight, qui fait la chasse au gaspi un peu partout, s'est amusée à définir ces deux propriétés dans la classe Control si on ne peut pas en hériter, sachant que Control ne sert à rien d'autre qu'à créer des classes héritées  ? C'est assez mystèrieux même si je suppose qu'il s'agit d'un problème de compatibilité avec WPF, Silverlight en faisant moins que son grand frère mais toujours en permettant que cela soit transparent pour le code. Bref, à satisfaire deux besoins opposés, d'un côté en coder le moins possible pour assurer la taille la plus petite au plugin et de l'autre assurer la compatibilité du code avec WPF, on finit par tomber sur des paradoxes de ce genre.

L'Element binding (ajouté dans SL 3) n'est pas utilisable non plus, à moins de donner un nom au UserControl (je veux dire à l'intérieur même de la définition de celui-ci). Ce qui n'est pas acceptable car si l'utilisateur du composant change ce dernier, le binding est cassé. Pire si l'utilisateur tente de placer deux instances sur une fiche, il y aura un conflit de nom. Solution inacceptable donc. J'ai testé, je pense, toutes les combines possibles. Mais j'ai enfin trouvé celle qui fonctionne !

La Solution

La piste de l'Element binding, nouvelle feature de SL 3, n'était pas mauvaise. Le problème c'est qu'en Xaml cela réclamait de pouvoir indiquer le nom de la source (le UserControl) alors même qu'à l'intérieur de la définition de notre contrôle il n'était pas question de lui donner un x:Name figé.

Mais en revanche, ce qui est possible en Xaml l'est tout autant par code (et souvent inversement d'ailleurs). Par chance, la classe permettant de définir un Binding n'utilise pas les noms pour la source ni le destinataire. Elle utilise les noms des propriétés mais là on les connait et ils ne changeront pas. Du coup, en définissant le Binding dans le constructeur (ou plutôt dans le gestionnaire de l'événement Loaded) on peut référencer "this", c'est à dire l'instance du UserControl, sans connaître son nom. On peut donc créer un lien élément à élément entre la propriété Fill des Path's et la propriété Foreground du UserControl (idem pour le Fill du rectangle et la propriété Background du UserControl).

Et ça marche ! Lorsqu'on compile tout ça et qu'on pose un composant "LaMer" sur une fiche on peut modifier la propriété Foreground et les vagues changent de couleur.

 

   1:          public LaMer()
   2:          {
   3:              // Required to initialize variables
   4:              InitializeComponent();
   5:              Loaded += new System.Windows.RoutedEventHandler(LaMer_Loaded);
   6:          }
   7:   
   8:   
   9:          private void LaMer_Loaded(object sender, System.Windows.RoutedEventArgs e)
  10:          {
  11:              // l'astuce est là !
  12:              var b = new Binding("Foreground") { Source = this, Mode = BindingMode.OneWay };
  13:              Vague1.SetBinding(Shape.FillProperty, b);
  14:              Vague2.SetBinding(Shape.FillProperty, b);
  15:              Vague3.SetBinding(Shape.FillProperty, b);
  16:   
  17:              var bb = new Binding("Background") { Source = this, Mode = BindingMode.OneWay };
  18:              rectBackground.SetBinding(Shape.FillProperty, bb);
  19:             
  20:              VaguesAnim.Begin();
  21:          }

Pour le Background on fait pareil avec le rectangle. Mais, allez-vous me dire (si si, vous y auriez pensé, un jour :-) ), pourquoi aller mettre un rectangle pour obtenir une couleur de fond alors même qu'il y a déjà une grille en dessous ? La grille possède aussi une propriété Background. Pourquoi, hein ?

La réponse est simple, j'ai forcément essayé, et ça fait un magnifique plantage avec une erreur dont le message fait peur en plus (du genre "anomalie irrémédiable dans cinq secondes tout va sauter, non ça a déjà sauté!"). Le message d'erreur étant assez peu clair quant aux raisons du plantage j'ai fini par abandonner. Ce qui marche une ligne avant pour la propriété Fill des rectangle avec la propriété Foreground du UserControl ne fonctionne pas du tout pour le Background de la grille liée au Background du UserControl. Là, ce n'est pas une feature, je penche sérieusement pour un gros bug.

Cela étant donné, j'ai donc ajouté un rectangle en fond pour qu'il puisse justement servir de ... Background. Et là ça passe. Ouf !

Ouf !

Silverlight c'est génial, c'est tout .NET et tout WPF dans un petit plugin. Mais dès qu'on sort du carré de verdure, on tombe dans les bois et là des loups il y en a quelques uns qui vous attendent au tournant ! Cela est logique, on s'y attend, c'est le prix à payer pour avoir .NET dans un browser Internet. Forcément le costume est un peu serré, le tissu a été économisé. Mais cela ne change rien à l'amour qu'on porte à Silverlight, au contraire, on se rend compte ainsi à quel point le travail de l'équipe Silverlight a été (et est encore) un véritable casse-tête et à quel point ils ont réussi un tour de force en faisant entrer un éléphant dans une boîte d'allumettes... Tout de même, une fois arrivé à la solution j'ai poussé un grand Ouf!

Le code source du projet : LaMer.zip (62,54 kb)

Pour de nouvelles aventures : Stay Tuned !

Silverlight 3 : Un média player complet bien caché !

Silverlight 3 est livré de base avec un contrôle très versatile, MediaElement, capable de jouer de nombreux formats comme les mp3 ou les vidéos.

Si ce contrôle est très puissant il est un peu "nu" de base et il faut soi-même ajouter les boutons de commande comme "play" et looker l'ensemble. Avec Expression Blend 3 c'est un jeu d'enfant. Enfin, de grand enfant qui a un peu de temps devant lui tout de même. D'où la question : n'existerait-il pas un MediaElement déjà tout habillé ?

Si vous possédez Expression Media Encoder 3 vous savez que cet extraordinaire outil (servant principalement à encoder des médias) donne le choix entre plusieurs formats de sortie dont des projets HTML tout fait intégrant un média player tout looké, et mieux encore, avec le choix parmi de nombreux modèles.

Quel rapport entre Media Encoder et un projet Blend/VS ? C'est tout simple : lorsque vous installez Media Encoder, sous Blend 3 dans l'onglet Assets vous disposez, en plus de MediaElement d'un nouveau contrôle "MediaPlayer" !

Par défaut ce composant ressemble à l'image ci-dessous. Pour le relooker, il suffit de faire un clic droit et d'éditer un copie du template !

Reste la question à 10 centimes d'euro : oui mais Media Encoder est livré avec de nombreux modèles dont certains ont déjà un look sympa, ne pourrait-on pas récupérer ces modèles au lieu de templater à la main le MediaPlayer ?

Si, c'est possible (© Les Nuls, "Hassan Cehef").

Comment ? Là c'est plus cher... Non, comme je suis un chic type, voici la solution gratuite :

Encoder 3 s'installe avec le code source des modèles qui se trouvent dans le répertoire "C:\Program Files\Microsoft Expression\Encoder 3\Templates\en", il suffit donc de piocher le modèle qu'on désire utiliser, et grâce aux sources d'extraire le contrôle avec son template et de l'intégrer à son propre projet !

Le célèbre Tim Heuer décrit (en englais) la méthode à suivre, je vous renvoie ainsi à son billet Using Encoder Templates in your Silverlight Application si jamais vous n'arrivez pas à vous dépatouiller seul avec le code source des projets Encoder 3.

Intégrer de la vidéo, même HD, dans une application Silverlight n'a jamais été aussi simple... et beau.

Amusez-vous bien, et .. Stay Tuned !

Silverlight 3 : Ecrire des Behaviors (composants comportementaux)

Blend 3 est une version majeure de ce puissant outil qui permet de travailler le visuel d'une application WPF ou Silverlight. Blend 3 tend vers une plus grande simplicité de manipulation notamment pour les infographistes qui ne veulent / peuvent s'investir dans du code trop complexe.

Les notions de Behaviors (comportements), Actions et Triggers (déclencheurs) sont soient nouvelles pour ce qui est de la première, soit sont renforcées pour les secondes. Le but étant bien de pouvoir ajouter du comportement visuellement sans programmation. Avec les outils d'il y a 15 ans comme VB Microsoft inventait la notion de composant visuel qui se place sur une fiche par Drag Drop. Un bon en avant  quand on connaît les méthodes qui ont précédé et quand on sait avec le recul quel succès a eu cette technologie et ses clones (EDI Delphi ou Java...) ! Avec Blend 3 Microsoft innove encore dans une voie qui trace forcément ce que sera l'avenir : le "composant comportemental". On applique le "RAD" (mot plus très en vogue mais qui garde tout son sens) non plus seulement au visuel mais aussi au code.

Aujourd'hui je vais vous parler des Behaviors (comportement).

Qu'est qu'un Behavior ?

Un Behavior est un comportement, c'est à dire un bout de code qui peut faire à peu près n'importe quoi. Avec ça, vous êtes bien avancé allez-vous dire ! Mais imaginez tout ce que peut faire un bout de code autonome (ne dépendant pas d'états externes) et faites un joli paquet cadeau autour afin qu'il apparaisse dans une palette, comme un composant visuel et vous obtenez un Behavior. Un Behavior est donc avant tout une sorte de code snippet qui porte un nom et qui est listé dans Blend comme le sont les composants. Un Behavior n'existe en renvanche pas par lui-même, comme un composant Rectangle ou Button par exemple. Un Behavior s'accroche à un composant existant et c'est en jouant le poisson pilote vis à vis de ce composant qu'il lui amène le fameux comportement.

Un petit exemple pour clarifier : Imaginons qu'on souhaite pouvoir déplacer un objet par Drag Drop, il faudra gérer le mouse Down, le mouse Up et le mouse Move selon un scénario classique que je ne développerai pas. Imaginons maintenant que nous souhaitions apporter le même comportement à un autre objet. Il faudra programmer aussi tous ces événements. Si maintenant nous encapsulons tout cela dans un Behavior, il suffira de déposer le dit Behavior sur tous les composants qui, ipso facto, disposeront du comportement. La réutilisation du code est totale. Côté utilisation de Blend cela permet même de développer une application sans être développeur pour peu qu'on dispose d'une bonne bibliothèque de Behaviors, d'Actions et de Triggers (je reparlerai des deux derniers dans de prochains billets).

Créer un Behavior

Créer un nouveau Behavior est aussi simple que de créer une nouvelle classe dérivant de Behavior<T>. Il faut néanmoins à trouver la classe mère... Elle se cache dans un assembly qui n'est pas référencé de base et qui joue un peu à cache-cache... Avec la finale de Blend 3 il s'agit de :

C:\Program Files\Microsoft SDKs\Expression\Blend 3\Interactivity\Libraries\Silverlight\System.Windows.Interactivity.dll

Une fois la classe créée, il ne reste plus qu'à surcharger deux méthodes : OnAttached() et OnDetaching(). C'est à partir de là qu'on peut programmer le comportement. Le Behavior peut connaître l'objet auquel il est attaché : AssociatedObject.

Un Behavior Exemple : RotateBehavior

Pour illustrer le propos j'ai cherché quel genre de petit Behavior je pourrais bien développer. Les classiques du genre ne me disait rien (faire en flou quand la souris entre sur l'objet, ou bien changer sa transparence, etc). Mais, restons réalistes, pour un petit exemple on ne pouvait faire beaucoup plus compliqué. Je me suis donc dit, tiens, une transformation du type rotation, je n'ai pas encore vu, et mieux, avec une animation pour tout de même pimenter la chose...

[silverlight:source=/SLSamples/Behavior/Behavior_base.xap;width=460;height=210]

Dans l'arbre des objets on peut voir le RotateBehavior accroché aux rectangles et à l'objet texte :

 

De même, pour le behavior sélectionné on peut voir les propriétés suivantes :

 

On note la propriété Angle, en degrés, et la propriété Duration qui définit la longueur de l'animation. Bien entendu, chaque RotateBehavior placé sur chacun des objets est paramétrable de façon séparée.

Le code exemple

Le blog est parfait pour publier des extraits de code mais pas des listing entiers. Du coup, plutôt que de vous imposer une lecture difficile, je vous propose de télécharger le projet exemple à ouvrir sous Blend 3 ou Visual Studio 2008 + SL3 SDK: Behavior_base.zip (61,84 kb)

Amusez-vous bien avec les comportements, et Stay Tuned !