Dot.Blog

C#, XAML, WinUI, WPF, Android, MAUI, IoT, IA, ChatGPT, Prompt Engineering

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 !

Code source Silverlight 'les codes postaux'

Il y a quelques temps j'avais mis en ligne l'application exemple Codes Postaux Français sous Silverlight. Application Silverlight 2.0 montrant l'utilisation d'un service Web avec base de données.

Beaucoup de lecteurs m'ont demandé si le code source était disponible, ce qui n'était pas le cas. J'avais prévu d'écrire un article se basant sur ce code mais le temps passant vite sans pouvoir mener à bien cette écriture j'ai opté pour une publication du code source complet, brut de fonderie.

Vous trouverez le lien de téléchargement sur la page suivante : https://www.e-naxos.com/Blog/page/Exemples-Silverlight

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 : un Behavior ajoutant un effet de reflexion (avec les nouvelles WriteableBitmap)

Dans le précédent billet je vous proposais d'écrire un Behavior effectuant une rotation animée d'un élément visuel, aujourd'hui je vous invite à la réflexion...

Je veux parler de l'effet de réflexion tant à la mode. WPF sait parfaitement le faire grâce notamment au brosses visuelles qui permettent facilement de "peindre" un contrôle avec la copie visuelle d'un autre objet (ou arbre d'objets). Hélas, Silverlight 3 ne possède pas encore de brosse de ce type et pour obtenir l'effet désiré il faut dupliquer le ou les objets sources et leur faire subir une rotation, un flip, et l'ajout d'un masque d'opacité. Tout cela est assez fastidieux il faut bien l'avouer.

Mais grâce aux Behaviors il est possible de rendre tout cela automatique et avec l'aide des WritableBitmap il n'est même pas nécessaire de dupliquer tous les objets !

J'ai déjà détaillé dans le billet précédent la technique de création d'un Behavior, je vous renvoie ainsi à cet exemple pour la base. Pour l'effet de réflexion, le mieux est de regarder ci-dessous l'exemple live :

[silverlight:source=/SLSamples/Reflection/ReflectionBehavior.xap;width=424;height=294]

Pour créer cet effet nous allons utiliser une nouvelle classe de Silverlight 3 : les WritableBitmap. Cette classe permet de créer des images de toute pièce ce qui n'était pas possible jusqu'à lors.

Non seulement les WritableBitmap permettent de créer une bitmap par code, mais en plus elle possède une méthode de Render qui est capable de faire le rendu de tout objet ou arbre visuel et de le placer dans la bitmap. Nous allons utiliser cette possibilité pour remplir une bitmap à laquelle nous appliquons ensuite quelques transformations (renversement, échange gauche/droite, translation). Un masque d'opacité est ajouté pour l'impression de dégradé d'opacité.

Le Behavior fonctionne sur des sources de type Canvas uniquement, cela permet d'illustrer comment limiter un Behavior à une classe ou une branche de classes donnée. Une fois l'image du reflet créé elle est ajoutée en tant qu'élément au canvas (après avoir pris soin de supprimer la précédente image s'il y en a déjà une).

Le Behavior n'a pas de rendu en mode design sous Blend 3, c'est un petit défaut. De même l'effet de réflexion est créé une fois pour toute et n'est modifié que si le canvas source voit sa taille changée. Si vous créez dynamiquement des objets sur celui-ci, le reflet ne changera pas. Bien entendu on pourrait penser connecter le Behavior à l'événement de rendu du canvas LayoutUpdated. Cela eut été une solution élégante. Hélas on créerait une boucle infinie puisque notre Behavior ajoute un objet au canvas ce qui déclenche le renouvellement de son rendu. On pourrait améliorer les choses en ne faisant pas le rendu dans le canvas source et en ajoutant une propriété au Behavior qui serait un objet de type Image (non enfant de la source) qu'on utiliserait comme sortie de rendu de l'effet de reflet. Mais cela poserait d'autres problèmes. 

Toutes les idées sont envisageables et le code proposé n'est qu'un exemple. A vous de l'améliorer sans oublier de venir ici nous dire ce que vous avez fait !

Côté paramétrage le Behavior permet de modifier les points de départ et d'arrivée du dégradé du masque d'opacité ainsi que l'offset sur l'axe Y. Sans ce dernier le reflet serait collé au canvas source ce qui n'est pas forcément très beau. Par défaut un décalage de 3 pixels vers le bas décolle légèrement le reflet de l'original.

Code la publication d'un code un peu long n'est pas forcément très lisible sur le blog, je vous renvoie au code source du projet complet que vous n'avez plus qu'à télécharger ici : ReflectionBehavior.zip (62,91 kb)

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 !

WPF : Qui l'utilise ?

Après plusieurs années de présence et de maturation, WPF a plus que largement prouvé sa capacité à produire des applications riches et esthétiques. Et si la supériorité technique de WPF sur les Windows Forms ou apparentés est une évidence, une question revient très souvent : Mais qui utilise WPF ?

En effet, les applications développées en WPF sont, il faut l'avouer, plus rares que celles en Windows Forms. J'ai de longue date milité pour que cet état de fait change, et je continuerai à la faire (voir par exemple mon article sur les 10 bonnes raisons de préférer WPF et 9 raisons de plus d'utiliser WPF!). Mais ne s'est il vraiment rien passé ? Heureusement non !

Il est vrai que les interfaces séduisantes de WPF vont un peu de pair avec le nouveau look de Windows Vista. Hélas cette mouture de l'OS n'a pas rencontré le succès et la partie a été remise. En fin d'année Windows 7 viendra malgré tout enfoncer le clou. Et là, comme je vous le clame depuis longtemps, il sera juste un peu tard pour vous y mettre, WPF c'est beau, c'est puissant, mais on ne l'apprend pas un matin pour commencer un nouveau projet l'après midi... Ce n'est pas juste une nouvelle librairie d'affichage. Les principes de fonctionnement, les outils (comme Blend), tout est radicalement différent et demande une phase d'apprentissage qui ne peut être niée ni balayée d'un haussement d'épaule. Et je peux vous assurer que les projets que vous commencez aujourd'hui en Windows Forms et que vous testez joyeusement sur votre Windows XP, lorsqu'ils arriveront entre les mains de vos utilisateurs sous Windows 7 l'année prochaine (ou dans 2 ans, vous ne développez pas des softs-kleenex non ?) tous vos softs Winfows Forms auront le même air dinosauresque et désuet qu'une application console...

Bref mettez vous à WPF. Si vous préférez, formez vous en partant de Silverlight. Les bases sont les mêmes et il est vrai que Silverlight a un côté plus "exitant" pour certaines personnes (moi par exemple!). Mais l'un ou l'autre, il est temps de mettre les bouchées doubles !

Microsoft a sorti une petite brochure qui montre les principales applications WPF qui sont ou qui font sortir. De Intel à AMD, de BMW à AutoCad, regardez le look des applis que vos utilisateurs auront sur leur bureau juste à côté des vôtres en Windows Forms... Prenez du recul et, sincèrement, tiendrez-vous la comparaison ? ... Je ne le crois pas.

Voici quelques liens pour bien commencer :

Ce ne sont que quelques points de départ, il y en a bien d'autres dans mes billets mais vous les avez déjà trouvés si vous êtes un lecteur fidèle...

Bonne lecture, et Stay Tuned !

WPF et Silverlight : composants orientés données dans les Toolkits

Trouver des ressources sur les technologies ou outils récents est toujours difficile. Par force, et c'est une Lapalissade, ce qui est plus connu est plus facile à connaître, et vice versa...

Le WPF Toolkit ainsi que Silverlight Toolkit sont des mines de composants ayant beaucoup de choses en commun : même base de code, évolutions rapides et donc releases fréquentes avec à chaque fois des ajouts d'importance. Suivre le ryhtme pourrait réclamer un job fulltime...

Voici un post de Delay's Blog qui résume les dernières ressources autour de la visualisation des données dans ces deux Toolkit : http://blogs.msdn.com/delay/archive/2009/07/19/my-new-home-page-enhanced-updated-collection-of-great-silverlight-wpf-data-visualization-resources.aspx .

Visualiser les données est essentiel à toute application de type gestion, et l'avalanche de nouveautés dans les Toolkits ciblant ce besoin permet désormais d'envisager sérieusement de créer de "vraies" application avec WPF et Silverlight. Par "vraies" j'entends des applications telles que nous en concevons tous régulièrement, et non des petits jeux ou des caroussels tournoyants qui font de magnifiques démos ou des sites Web publicitaires mais qui ne sont pas forcément des applications représentatives du travail habituel d'un développeur. Il est donc d'autant plus important de connaître ces extensions de WPF et de Silverlight et pour ce faire il faut trouver les bonnes explications...

A lire absolument donc, pour y piocher des articles, des astuces, ou de simples explications sur toute la partie affichage des données sous Silverlight ou WPF.

Avec ça, vous pouvez vous occuper jusqu'à la rentrée de septembre !

Mais Stay Tuned, car j'ai plein d'autres choses à vous dire d'ici là !