Dot.Blog

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

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à !

Créer un arbre des répertoires avec XML (et l'interroger avec LINQ to XML)

Pour les besoins d'un prochain billet je voulais obtenir une structure arborescente de test sous la forme d'un fichier XML. Une telle chose peut se faire à la main mais cela est fastidieux et à force de copier/coller les données seront trop semblables et donc peu utilisables pour des tests et du debug, ce qui est dommage puisque tel était le but recherché...

Pour générer des données aléatoires mais réalistes je possède déjà un outil fantastique (puisque c'est moi qui l'est fait :-) ), DataGen, un logiciel puissant mais qui n'est pas étudié pour générer des données dont la strucuture est récursive. Restait donc à concevoir un utilitaire pour satisfaire mon besoin de l'instant.

Je n'aime pas développer "pour rien" donc il fallait que cet utilitaire en soit bien un. Tout disque dur de développeur contient une telle quantité de données que l'ensemble des répertoires forment une superbe structure arborescente ni trop petite ni trop gigantesque. Exactement ce qu'il faut pour des tests ! Ainsi, l'outil devra fabriquer un fichier XML à partir de n'importe quel niveau de répertoire, la structure étant hiérarchique.

Je m'empresse donc de vous faire partager ce petit bout de code qui vous sera utile un jour ou l'autre je pense.

using System;
using System.IO;
using System.Linq;
using System.Xml.Linq;
 
namespace Enaxos.Tools.Xml
{
    /// <summary>
    /// This class can generate an XML file from a folder hierarchy.
    /// </summary>
    public static class DirToXml
    {
        /// <summary>
        /// Builds the tree document.
        /// </summary>
        /// <param name="dirname">The name of the starting folder.</param>
        /// <returns>a LINQ to XML <c>XDocument</c></returns>
        public static XDocument BuildTreeDocument(string dirname)
        {
            return new XDocument(new XDeclaration("1.0", "utf-8", "yes"), 
                new XComment("Structure au "+DateTime.Now),new XElement("directories", BuildTree(dirname)));
        }
 
        /// <summary>
        /// Builds the tree (recursive method).
        /// </summary>
        /// <param name="dirName">Name of the starting folder.</param>
        /// <returns>a LINQ to XML <c>XElement</c> being the root (or 1st item) of the tree.</returns>
        public static XElement BuildTree(string dirName)
        {
            var di = new DirectoryInfo(dirName);
            var files = di.GetFiles();
            var dirsize = 0l;
            foreach (var file in files)
            {
                dirsize += file.Length;
            }
            var subdirs = di.GetDirectories();
            // each item is a "directory" having 5 attributes
            // name is the name of the folder
            // fullpath is the full path including the name of the folder
            // size is the size of all files in the folder (in bytes)
            // files is the number of files in the folder
            // subdirs is the count of possible sub directories in the folder
            var elem = new XElement("directory", 
                new XAttribute("name", di.Name), 
                new XAttribute("fullpath",dirName),
                new XAttribute("size", dirsize),
                new XAttribute("files",files.Count()),
                new XAttribute("subdirs",subdirs.Count()));
            foreach (var dinf in subdirs)
            {
                var elemDir = BuildTree(dirName + "\\" + dinf.Name);
                elem.Add(elemDir);
            }
 
            return elem;
        }
    }
}

Ce code peut être utilisé sans passer par un fichier disque puisqu'on récupère le document en mémoire. L'exemple ci-dessous montre comment appeler le code et comment interroger la structure via Linq To Xml pour connaître les répertoires vides (n'ayant ni fichier ni sous répertoire) :

   1:  var d = DirToXml.BuildTreeDocument(@"D:\WpfToolkit");
   2:  d.Save(@"d:\test.xml");
   3:  Console.WriteLine(d.ToString());
   4:  Console.WriteLine(new string('-',60));
   5:   
   6:  var q = from e in d.Descendants("directory")
   7:          where (int) e.Attribute("files") == 0
   8:               && (int)e.Attribute("subdirs") == 0
   9:               orderby (string) e.Attribute("fullpath")
  10:               select e.Attribute("fullpath");
  11:   
  12:  Console.WriteLine("Répertoires vides");
  13:  foreach (var element in q)
  14:    { Console.WriteLine(element); }
  15:  Console.WriteLine(string.Format("{0} répertoires vides",q.Count()));

 

A bientôt pour un prochain billet, so.. Stay Tuned !

Silverlight 3: Accélération GPU et cache image

Dans un récent billet sur les Pixel Shaders (effets bitmap) j'avais, par un raccourci de pensée un peu rapide, associé trop facilement les capacités d'accélération graphique GPU de Silverlight 3 avec celles de WPF. Or il existe des nuances très sensibles entre les deux implémentations.

Par souci de rigueur j'ai modifié le billet original en lui ajoutant les précisions nécessaires mais les corrections de billets ne sont pas propagées par feedburner qui gère une parite des flux RSS des abonnés à mon blog. Et pire, ceux qui ont déjà téléchargé le billet sous Outlook ou un autre news reader n'ont aucune chance de voir la correction. Ne reste plus qu'à créer un nouveau billet pour m'assurer que l'info est passée.. Voici la notice ajoutée au billet cité plus haut :

--extrait--

Quelques mots sur l'accélération GPU : Même si ce n'est pas le sujet du présent billet, il s'agit d'une nouvelle feature très intéressante de SL3. On a tendance hélas (et j'ai été le premier à me faire "avoir") à trop vite faire le parallèle avec WPF et à transposer ce que fait l'accélération sous ce dernier. En fait, le support de l'accélération GPU de SL3 est pour l'instant assez limité et ne se met en route que de façon volontaire. Elle ne s'applique pas aux effets bitmaps ni aux nouveaux flux vidéos qui restent traités par le CPU.

Malgré tout le système d'accélération graphique peut rendre d'immense service, dans certains de mes tests sur l'animation d'images assez grandes j'ai pu voir mon PC passer de 88% d'utilisation CPU (double coeur Asus plutôt rapide) à moins de 4% ! Ce qui est énorme. Mais pour profiter de l'accélération il faut aboslument en connaître le fonctionnement et les limitations.

Je vous invite ainsi à lire l'excellent billet de Andras Verlvart sur la question. Il est assorti d'une application exemple en live qui permet de bien voir l'effet des différentes possibilités d'accélération et leur impact sur le CPU et le GPU. Cet article est en anglais, pour ceux qui préfère la lecture en français, David Rousset a écrit un billet qui fait le point sur cette même question.

--fin d'extrait--

Espérant ainsi avoir rétabli la précision technique nécessaire à propos de cette nouvelle possibilité de Silverlight 3, et incidemment, vous avoir donné envie de vous familiariser avec et de l'adopter dans vos développements,

Stay Tuned pour d'autres nouvelles !

« Il neige » ou les boucles d’animation, les fps et les sprites sous Silverlight

Il n’est jamais trop tard pour préparer Noël et la période estivale est le meilleur moment pour parler de neige, vision rafraîchissante s’il en est. Mais tel n’est pas réellement mon propos. Sous ce prétexte glacé (mais pas glaçant) se cache des notions techniques très utiles sous Silverlight (ou WPF).


Les boucles d’animation, le contrôle des fps (frame per second, images par seconde) et les sprites (petits objets autonomes animés) sont certes autant d’incursions dans le monde du jeu mais la créativité permet d’envisager mille utilisations de ces techniques dans d’autres contextes.
Je me suis librement inspiré d’un billet s’intitulant « Making it snow in Silverlight » écrit par Mike … Snow (facile à se souvenir !). J’utilise un code différent et (à mon sens) amélioré, mais il était important de rendre à Snow la neige qui lui appartient (et de faire un peu d’humour bas de gamme au passage).


Avant d’expliquer certains aspects, voici une démonstration live que vous pouvez passer en mode plein écran :

[silverlight:source=/SLSamples/IlNeige/IlNeige.xap;width=500;height=350]

Le but

Le but du jeu est donc de faire tomber de la neige sur un fond, c'est-à-dire de faire se mouvoir des centaines d’objets autonomes ayant chacun un aspect légèrement différent et une trajectoire un peu aléatoire. Le tout assez vite pour que cela paraisse naturel.

Le principe

Chaque flocon de neige est en réalité une instance d’un UserControl, il y a ainsi des centaines d’instances au même instant. Chaque flocon est responsable de son affichage et de la façon dont il avance à chaque « pas ». La coordination générale étant réalisée par une boucle d’animation principale, c’est elle qui scande le rythme et demande à chaque flocon d’avancer d’un « pas ».
Tout se trouve donc dans deux endroits stratégiques : la boucle principale et le UserControl « Flocon ».

Suivre l'article : le code n'étant pas très lisible dans le blog, sauf extraits très courts, je n'ai volontairement pas placé ce dernier dans le corps du billet. Je vous conseille ainsi de télécharger le projet (en fin de billet) et de suivre les explications en ayant le code chargé sous Blend 3 ou Visual Studio (2008 + SL3 minimum).

Le sprite Flocon

Il s’agit d’un UserControl possédant une interface très simple puisqu’il s’agit d’un bitmap en forme d’étoile (ou pourrait utiliser n’importe quelle forme vectorielle créée sous Design ou Illustrator). Côté XAML on retrouve ainsi une grille avec un PNG à l’intérieur, rien de palpitant. A noter qu’au niveau du UserControl lui-même on définit deux transformations : une d’échelle (FlakeScale) et une de rotation (FlakeRotation). Dans l’exemple de la neige cette dernière n’a que peu d’effet visuel sauf lorsqu’on choisit une taille assez grosse pour les flocons. L’œil ne discerne pas forcément la rotation de façon consciente mais comme elle est choisit aléatoirement pour chaque flocon cela renforce l’aspect global plus « organique ». De plus il s’agit d’illustrer des techniques utilisables dans d’autres contextes ou avec d’autres sprites pour lesquels l’effet serait plus évident. La technique est extrapolable à toutes les transformations supportées, même les nouvelles transformations 2,5D de Silverlight 3.
Le code de la classe Flocon se divise en deux points : le constructeur et la méthode « Tombe ».

Le constructeur

Il est responsable de la création de chaque instance. C’est à ce niveau qu’on introduit un peu d’aléatoire dans la rotation du flocon. On choisit aussi à ce moment la position de départ en X (la position en Y étant toujours à zéro au départ).
La gestion des flocons est séquentielle (par la boucle d’animation que nous verrons plus loin). De fait il n’y a pas de multithreading et on peut se permettre un développement très simple de la classe. Par exemple les limites du tableau de jeu sont stockées dans des propriétés statiques de la classe. Le programme initialise ces valeurs au lancement et les modifie à chaque fois que la taille du tableau est changée (redimensionnement du browser). 
Le constructeur fixe aussi de façon aléatoire l’échelle de l’instance afin que chaque flocon soit de taille légèrement différente par rapport à une échelle de base. Cela permet d’obtenir un effet de profondeur de champ.

La méthode « Tombe »

Il s’agit bien de tomber et non d’outre-tombe et de zombies façon Thriller du très médiatisé Jackson. Pour chaque flocon il s’agit donc ici de savoir choir, avec si possible le sens du rythme et du mouvement du sus-cité Michael.
Pour ce faire la méthode « Tombe » doit calculer la nouvelle position X,Y du flocon. Les flocons évoluent dans un espace de type Canvas particulièrement indiqué pour tout ce qui doit être positionné de cette façon à l’écran.
Sur l’axe des Y le déplacement s’effectue d’un pixel à la fois. Une incrémentation simple est utilisée. Sur l’axe horizontale le flocon change régulièrement de direction pour rendre l’ensemble visuel plus réaliste. On utilise ici un tirage aléatoire pour savoir s’il faut ou non changer de direction. Dans l’affirmative on modifie la valeur du pas de déplacement (hzDeplacement). La position sur X est alors calculée en ajoutant cette valeur à la position courante.
Reste à savoir quand un flocon n’a plus d’intérêt, c'est-à-dire lorsqu’il échappe au champ de vision et qu’il peut être détruit (n’oublions pas que des centaines d’instances sont gérées à la fois et que tout flocon inutile doit être détruit le plus vite possible pour d’évidentes raison d’efficacité). C’est le rôle du test en fin de méthode qui initialise la valeur « HorsZone ». Lorsqu’elle passe à true, le flocon peut être détruit. Cette information est utilisée par la boucle principale.


La boucle d’animation

Maintenant que nous disposons d’un modèle de flocon (la classe Flocon) reste à animer de nombreuses instances pour donner l’illusion de la neige qui tombe.
La technique pourrait utiliser un Timer ou bien une StoryBoard vide dont le déclenchement est réalisé à chaque pas (et comme elle s’arrête immédiatement on obtient une sorte de Timer rythmant la boucle principale). Cette dernière façon de faire était très utilisée sous Silverlight 1.x.  Selon la vitesse de la machine cliente la boucle est ainsi déclenchée plus ou moins vite ce qui évite le problème de fixer un pas déterminé comme avec un Timer. Toutefois il est en réalité inutile de calculer la position des flocons plus rapidement que les FPS, pure perte d’énergie, ni de calculer moins souvent (autant abaisser les FPS).


Sous Silverlight 2 un événement a été ajouté donnant accès au rythme du moteur de rendu : CompositionTarget.Rendering. En gérant la boucle principale dans ce dernier on s’assure d’être parfaitement dans le tempo du moteur de rendu, ni plus rapide, ni plus lent.

Cette technique a toutefois dans le principe un défaut connu dans tous les jeux programmés de la sorte : la célérité de l’ensemble dépend de la vitesse de la machine hôte. Un PC très lent fera donc tomber la neige trop lentement, alors qu’un PC fulgurant des années 2020 ne permettra même plus de distinguer le déplacement des flocons tellement cela sera rapide, rendant le jeu « injouable ». Pour le premier problème il n’y a pas vraiment de solution, on ne peut donner plus puissance à la machine hôte (sauf à permettre à l’utilisateur de diminuer la charge du jeu lui-même en limitant par exemple le nombre maximum de flocons, le paramètre de densité peut jouer ce rôle dans notre exemple). Quant au second problème il trouve sa réponde dans la limite à 60 FPS par défaut de Silverlight, limite modifiable via les paramètres du plugin (voir ce billet).

Débarrassés des écueils propres à cette technique, l’événement de rendering devient l’endroit privilégié pour placer le code d’une boucle principale d’animation.

Le contrôle des FPS

Lorsque vous exécutez une application de ce type il est important de contrôler les FPS pour voir comment certaines améliorations du code peuvent accélérer ou non le rendu. Pour ce faire Silverlight permet d’activer l’affichage de deux informations essentielles dans la barre d’état du browser : les FPS actuels et la limite fixée dans le plugin. L’activation se fait via Application.Current.Host.Settings en plaçant EnableFrameRateCounter à true. Il est bien entendu vivement conseillé de déconnecter la fonction en mode Release. Si votre navigateur et ses réglages le supporte vous devez en ce moment pouvoir lire ces informations en bas à gauche de la barre d'état (et si l'exemple live de l'application en début de billet s'est chargé correctement).

Attention : Selon les réglages de sécurité Internet Explorer autant que FirexFox peuvent ne pas autoriser la modification de la barre d’état. Sous IE (sous FF cela est quasi identique) il faut vérifier le paramètre Outils / Options Internet / Sécurité / Zone Internet / Personnaliser le niveau / Autoriser les mises à jour à la barre d’état via le script. Ce paramètre doit être activé pour voir les FPS.

Le Rendering

A chaque pas du rendering nous allons réaliser deux tâches essentielles : déplacer les flocons déjà présents et supprimer ceux devenus inutiles et créer de nouveaux flocons.
Pour déplacer les flocons nous ne faisons que balayer la liste de tous les flocons et appeler la méthode « Tombe() » de chacun. Au passage nous archivons dans une liste temporaire tous les flocons dont l’état HorsZone est passé à true. En fin d’animation les flocons inutiles sont supprimés de la liste principale les laissant ainsi disponible au Garbage Collector.

Pour créer de nouveaux flocons nous déterminons si la densité désirée est atteinte ou non. Le processus est randomisé pour plus de réalisme bien que le seuil de densité soit lui fixé par le programme (et modifiable dans notre exemple). Comme le placement de chaque flocon est réalisé dans le constructeur de la classe Flocon il n’y a rien d’autre à faire que de créer les flocons et de les ajouter à la collection Children du Canvas parent.

Le plein écran

Silverlight permet de passer une application en mode plein écran. Sachez toutefois que ce mode pose de sérieuses conditions à son utilisation. Notamment il ne peut être activé qu’en réponse directe à un clic de l’utilisateur. Cela permet d’éviter (en partie) qu’une application Silverlight soit utilisée de façon à tromper l’utilisateur final en prenant le contrôle de tout l’écran sans son autorisation (on pourrait penser à un fake du bureau Windows permettant par exemple d’extorquer un login). De même, lorsque l’application bascule en mode plein écran un message impossible à supprimer est affiché pour avertir l’utilisateur et lui indiquer que l’appui sur Escape permet de sortir de ce mode spécial.

Si cela n’était pas suffisant, les saisies clavier sont impossibles en plein écran. Seules certaines touches ont un effet (voir la documentation sur MSDN). Ne pensez pas utiliser le plein écran pour une application de saisie par exemple. En revanche pour visionner des vidéos ou pour créer un jeu les touches disponibles et la gestion de la souris sont suffisants. C'est le cas de notre démo.

Une fois connues ces limites raisonnables (Microsoft ne veut pas, à juste titre, que Silverlight soit utilisé par des fraudeurs et autres adaptes du phishing) le passage en plein écran s’effectue par Application.Current.Host.Content.IsFullScreen en mettant cette propriété à true, et ce en réponse au clic sur un bouton en général.

Le code

Télécharger le projet complet de l'exemple "IlNeige" : IlNeige.zip (229,54 kb)

Silverlight : Dessiner des horloges, un peu de trigonométrie !

Tout le monde sait faire un cercle, enfin une Ellipse particulière sous Silverlight ayant une hauteur et une largeur identiques. En revanche placer les marques des minutes ou placer correctement le texte des heures dans le cadran n'est pas toujours évident.

Si on tente de le faire "à la main", et même sous Blend, c'est une véritable galère (imaginez 60 petites ellipses pour les secondes à répartir soigneusement, sans parler d'un éventuel changement de dimension qui mettrait tout par terre !).

La seule solution raisonnable passe ici forcément par code. Mais malgré la croyance populaire tous les informaticiens ne sont pas forcément des "bêtes" en math ! On peut être un expert en SQL sans se rappeler ses cours de trigonométrie et on peut développer toute une architecture multithreadée sans même savoir ce qu'est un Groupe de Lie. Mais parfois ce genre de connaissance peut manquer notamment dès qu'on aborde le traitement d'image ou de dessin.

[silverlight:source=/SLSamples/ClockHours/ClockHours.xap;width=360;height=222]

Le but du jeu est donc ici de répartir harmonieusement des objets sur un cercle. Dans un cas il s'agit de texte (les heures) dans l'autre des cercles (les dots). Mais avant de regarder le code, rappelons quelques points mathématiques :

L'unité Math fonctionne en radians c'est à dire qu'un cercle vaut 2 Pi (donc deux fois 3.1415926...). C'est bête parce qu'en général un humain normalement consituté raisonne plutôt en degrés, ce qui est plus facile à se représenter mentalement.

 

 

Comme on peut le remarquer en radian le 0 se trouve au milieu à droite du cercle et non pas en haut à midi comme on pourrait s'y attendre. Du coup, comme les fonctions mathématiques de .NET marchent en radians si on raisonne en degrés il faudra penser à cette petite spécifité lors de la conversion. Par exemple, la place de "1 heure" sur une horloge se situe à 300 degrés et non pas à 30° (1 pas de 360 divisé par 12).

Le code ci-dessous permet d'écrire les heures dans l'exemple live Silverlight plus haut dans ce billet :

private void writeHours(int radius, int offsetx, int offsety, Canvas parent, double fontsize, Color color)
        {
            var h = 1;
            for (var angle=300;angle<=630;angle+=30)
            {
                var t = new TextBlock {
                        Foreground=new SolidColorBrush(color), 
                        Text=h.ToString(),
                        FontSize = fontsize};
                h++;
                t.SetValue(Canvas.LeftProperty,(radius * Math.Cos(DegreetoRadian(angle))) + offsetx);
                t.SetValue(Canvas.TopProperty,(radius * Math.Sin(DegreetoRadian(angle))) + offsety);
                parent.Children.Add(t);
            }
        }

Les paramètres sont les suivants :

  • radius : rayon du cercle virtuel sur lequel les heures sont posées
  • offsetx et offsety : positionnement du centre du cercle dans le canvas parent, sachant que le 0,0 se trouve en haut à gauche.
  • parent : le Canvas parent
  • fontSize : taille de la fonte
  • color : la couleur de la fonte

Pour dessiner les dots on utilise le même principe (sans s'encombrer des détails du point de départ décalé puisque toutes les dots sont identiques).

Le code n'est pas optimal et on peut certainement faire mieux, mais cela vous donne un point de départ ... Pour compléter le tout, vous pouvez télécharger le projet : ClockHours.zip (61,42 kb)

Stay Tuned !