Dot.Blog

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

Deux règles pour programmer Silverlight & WPF

Des règles et des bonnes pratiques pour développer des applications Silverlight il en existe bien plus que deux, faire croire le contraire ne serait pas honnête. Ne serait-ce que par la richesse des EDI utilisés (Visual Studio, Expression Blend, Expression Design…) il faut accumuler de l’expérience et mémoriser de nombreuses patterns pour architecturer et designer correctement une application.

Alors, en vrac, voici deux règles qui me passent par la tête et que je voulais vous communiquer:

Programmation par Templates

Sous Silverlight lâchez les vieilles habitudes. L’une de celles-ci que je rencontre souvent est celle qui consiste à se jeter sur son clavier pour créer un UserControl (ou un Control par héritage) dès qu’on a besoin d’un nouveau composant qui semble absent de la bibliothèque.

Erreur. Méthode du passé.

En effet, Silverlight et WPF impliquent des changements radicaux de mode de pensée et d’habitudes. Sous ces environnements, la majorité des besoins sont couverts par les composants existants, il “suffit” juste d’en modifier le template.

Nous sommes passés d’une programmation par héritage à une programmation par modèle (template).

Deux exemples pour bien cerner ce que j’entends par là.

Sticky Note

Prenons pour premier exemple un cas d’école, déjà ancien mais en plein dans notre propos : sticky notes pour WPF (sticky notes listbox). Ce “composant” se présente comme une liste verticale de petites notes de type “post-it” légèrement décalées les unes par rapport aux autres.

L’effet est sympathique et donne un peu de fantaisie et de vie à ce qui ne serait qu’une liste rectangulaire dans une grille en programmation classique.

Ce magnifique “composant” n’utilise aucun code “'classique”. Inutile dans les sources de chercher la classe StickyNotes qui hériterait de Control ou même de chercher dans les fichiers de l’application le UserControl créé pour l’occasion.

Rien de tout cela.

Sticky Notes est créé uniquement en jouant sur le templating d’une simple… Listbox ! Une poignée de Xaml et des templates, c’est tout.

Language button

Dernièrement dans une application Silverlight je devais implémenter un bouton permettant de choisir depuis la page d’accueil la langue à utiliser (français ou anglais). Forcément on pense à un ToggleButton ou quelque chose d’équivalent (ce qui n’existe pas de base). On pourrait aussi associer deux RadioButton dans une grille.

Mais le plus intéressant consiste à se demander “quel control existant se rapproche le plus du comportement que je souhaite implémenter”. En y réfléchissant quelques secondes on s’aperçoit assez vite qu’une CheckBox possède deux états stables (oublions ici l’état indéterminé). Ce composant comporte toute la logique et les états visuels permettant de gérer deux états.

Par défaut une CheckBox c’est une case à cocher avec un bout de texte devant ou derrière.

Le plus dur consiste à se l’imaginer comme deux drapeaux (français et US) côte à côte, celui qui est sélectionné étant à 110% de sa taille et l’autre à 50%. Par convention arbitraire et chauvinisme inconscient certainement j’ai considéré que IsChecked = True était le Français, à False l’anglais. En partant d’une CheckBox que j’ai totalement vidée de son contenu, et en ajoutant les deux drapeaux (et quelques autres ingrédients et animations via le VSM) j’ai obtenu un merveilleux “ToggleButton” sans jamais “sous-classer” le moindre contrôle. Juste en écrivant un template.

Le ViewModel de la page d’accueil offre une propriété IsFrench de type booléen qui est simplement bindée à la propriété IsChecked du CheckBox (en mode TwoWay) et l’affaire est jouée !

Règle 1 : De l’héritage au templating

La programmation Xaml (Silverlight / WPF) est une programmation visuelle qui s’effectue par templating. Créer des UserControl et encore plus sous-classer des contrôles existants devient quelque chose de rarissime.

Nous sommes passés de l’ère de la programmation objet par héritage à celle de la programmation visuelle par templating. Bien comprendre toute la signification de ce changement est un point primordial et un préliminaire indispensable pour comprendre cette technologie et donc la programmer intelligemment.

Programmation par Properties

Il s’agit du même genre de glissement, une pente douce mais dont la longueur finit par conférer une vitesse tellement grande à celui qui s’y laisse glisser que le décor n’a plus rien à voir avec celui qu’on a tout le temps d’admirer en balade à dos d’âne…

Dans l’exemple précédent on touchait du doigt cette nouvelle approche mais sans la mettre en exergue.

En effet, dans la pattern M-V-VM plutôt que de créer un code incompatible avec le visuel d’un côté pour ensuite créer de l’autre des tripotés de convertisseurs (programmation classique non M-V-VM sous WPF et Silverlight dans une moindre mesure) il semble bien plus simple d’exposer dans les ViewModels des propriétés directement exploitables par l’UI. Si adaptation des données il doit y avoir c’est le ViewModel qui s’en charge (directement si cela est ponctuel, ou via une classe de service si la chose doit être réutilisées ailleurs).

Dans l’exemple précédent du Checkbox transformé en ToggleButton de langue, aucun événement n’est programmé, aucun gestionnaire n’est écrit pour réagir au clic. Tout se joue dans le ballet automatique de IsChecked de la CheckBox et de IsFrench du ViewModel sous l’égide discrète mais indispensable d’un binding two way…

Quant l’utilisateur clique sur la CheckBox (enfin sur le ToggleButton avec les deux drapeaux) le composant sous-jacent bascule sa propriété IsChecked à vrai ou faux selon le cas. Comme cette dernière est liée à IsFrench du ViewModel, une propriété de type booléen aussi pour assurer la compatibilité des comportements, le ViewModel reçoit pas un événement mais l’une de ces propriétés (IsFrench) se voit modifiée. Ce qui déclenche le Setter de cette dernière. Ce dernier s’occupant de modifier le fichier de ressource utilisé pour puiser les chaines de caractères. De là et par une série de notification de changement de propriétés, il avertit en retour la Vue que toutes les propriétés de type texte ont été modifiées. La vue (et ses binding) y réagit en rafraichissant les affichages…

Toute cette mécanique s’est déroulée sans aucun gestionnaire d’événement, sans aucune programmation “classique” (en dehors du ViewModel et de ces propriétés gérant INotifyPropertyChanged, ce qui peut être automatisé ou simplifié en utilisant Mvvm-light par exemple).

Règle 2 :  de l’événementiel à binding

La seconde règle d’or à bien comprendre pour tirer totalement partie de Xaml et de ses enfants (Silverlight et WPF) est ainsi d’opter pour un modèle de développement de type Model-View-ViewModel se basant presque exclusivement sur des couples de propriétés mis en relation via binding.

On est passé de l’ère du développement dit “événementiel” des premières versions de Windows à ce qu’on pourrait appeler la “programmation par Binding” ou par “properties”.

Conclusion

Pour résumer :

Règle 1 on cherche à templater des contrôles existants sans créer des UserControl ni dériver des contrôles existants.

Règle 2 : on base la dynamique de l’application sur le binding entre propriétés et non plus sur les gestionnaires des événements des contrôles.

Si vous avez déjà fait vôtre ses règles alors vous allez devenir, si ce n’est pas déjà le cas, de très bons développeurs Silverlight et Xaml très recherchés !

Si vous n’aviez pas encore vu les choses sous cet angle là, je serai très heureux si par chance j’ai réussi à tirer le voile qui vous empêchait de les voir ainsi. Vous ne ferez qu’entrer plus vite dans la catégorie précédente !

Bon Dev, et, œuf corse,

Stay Tuned !

Xaml Dynamique

Xaml n’est pas compilé, ce qui signifie que le runtime l’interprète à l’exécution. Cet interpréteur est donc présent aussi bien quand on exécute une application WPF que Silverlight. Cela peut permettre des choses intéressantes comme la création de code Xaml dynamiquement au runtime…

Cette possibilité n’est que peu souvent évoquée alors que dans certains cas elle autorise de dynamiser tout ou partie d’une interface. Rien n’interdit en effet de créer une mise en page complète par ce procédé, VSM et animations comprises. La génération de Vues (partielles ou totales) en fonction d’une description paramétrée par l’utilisateur fait partie des cas concrets où de la génération dynamique de code Xaml peut grandement simplifier la mise en œuvre du code.

Le potentiel est grand, il suffit juste de savoir que cela existe car techniquement cela est finalement très simple. L’essentiel tient dans XamlReader.Load() qui sait rendre un objet à partir d’un code Xaml.

Pour sortir des sentiers battus, voyons comment on peut utiliser cette stratégie pour créer dynamiquement un composant à partir de son nom de classe non pas en utilisant la réflexion sous C#, juste en générant du code Xaml…

L’exemple live ci-dessous permet de créer un contrôle (pré-saisi par défaut pour un textBlock) qui sera ajouté dans un StackPanel dans la seconde moitié verticale de l’affichage. Créez plusieurs TextBlock de cette façon en modifiant le texte ou d’autres paramètres. Puis créez-en un auquel vous ajouterez dans les paramètres: Name=’Test’. Enfin cliquez sur le bouton “changer couleur des TextBlock”.

Tous les TextBlocks créés passeront en Foreground noir, et celui qui a pour non ‘Test’ vera sa taille de fonte augmentée de 4.

[silverlight:source=/SLSamples/DynXaml/DynamicXaml.xap;width=480;height=400]

Le code de création du contrôle est le suivant :

namespace DynamicXaml{    
public static class DynamicXamlHelper
{
public static UIElement CreateControlFromName(string controlName, 
string controlNamespace, string xmlnsPrefix, string properties)
{
var sb = new StringBuilder();
sb.Append("<" + controlName + " xmlns");
if (xmlnsPrefix.Length > 0) sb.Append(":" + xmlnsPrefix);
sb.Append("=\"" + controlNamespace + "\" " + properties + "/>");
try
            { return (UIElement)XamlReader.Load(sb.ToString()); }
catch
            {
return null;
}
}
}
}

Les paramètres sont ceux que vous pouvez taper dans les textBox de l’exemple live ci-dessus:

Pour le reste, le plus simple est jouer avec le code source de l’exemple : DynamicXaml.zip (7,08 kb)

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 !

Happy New year et MVP 2010 !

Je vous souhaite à tous une excellente année 2010, une année qui commence fort puisque je viens d'apprendre ma nomination MVP 2010 "Client App Dev" pour mes contributions principalement axées sur le développement des applications clientes sous WPF et Silverlight.

Cette récompense fait suite à ma nomination l'année dernière au titre de MVP C#. Cette complémentarité de compétence Framework-Langage / IHM traduit la réalité de mon engagement sur .NET depuis plusieurs années que vous pouvez constater ici même sur Dot.Blog par le nombre des articles et billets que je publie (108 pour être exact pour l'année passée !) sans compter tous les exemples de code et les applications exemples Silverlight vivantes intégrées aux billets.

Je suis heureux, et honoré, de partager cette nouvelle distinction MVP avec tous les lecteurs de Dot.Blog,

Que cette année vous apporte à vous et vos proches tout ce que vous en attendez !

Happy New Year 2010 !

 

Ma lettre de nomination MVP : ERL MVP 2010 OD.pdf (172,84 kb)

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 !

Lequel est le plus foncé sous WPF/Silverlight : Gray ou DarkGray ?

La réponse n'est pas forcément celle que vous pensez !

La taille du cul des vaches 

Rappelez-vous, il y a de de cela plusieurs années circulait sur Internet une histoire (vraie ou fausse peu importe) qui démontrait que le diamètre des fusées de la Nasa dépendait, par une suite invraisemblable de relations de cause à effet, de la taille du cul des vaches au temps des romains. On passait par la taille des charriots dont la largeur dépendait justement de celle du fessier des ces honorables bovins, ce qui imposait une distance entre les roues, elles-même créant des traces au sol qui firent que les premières voitures à cheval, pour emprunter les routes aux ornières profondes tracées par les charrues se devaient de respecter la même distance inter-roue, etc. On en arrivait ainsi au diamètre des fusées qui, pour passer sur les trains, qui devaient passer dans les tunels, dont la taille dépendait etc, etc... Au final un objet ultra technologique et on ne plus moderne se retrouvait à respecter une taille qui dérivait de celle du cul des vaches des romains...

Je n'ai pas pu retrouver cette histoire le Web pour vous mettre la référence, si un lecteur la connaît qu'il soit sympa et qu'il laisse le lien en commentaire...

Une histoire plus vraie qu'on le pense 

Bien entendu je n'ai jamais pu savoir à l'époque s'il s'agissait d'un hoax, d'une véritée romancée ou bien d'une vérité historique. Mais l'histoire que je vais vous raconter aujourd'hui me fait penser, des années plus tard, que cette histoire était probablement vraie...

Je tire librement inspiration d'un billet publié en 2006 par Tim Sneath, ceux qui désirent lire ce billet original (en anglais) n'ont qu'à cliquer ici.

Les couleurs sous WPF / Silverlight

24 bit RGB 

En Xaml il existe plusieurs façons d'exprimer une couleur. Une des options est d'utiliser la notation 24 bits hexadécimale de type RGB (Red/Green/Blue, rouge/vert/bleu) :

<Rectangle Fill="#B2AAFF" Stroke="#10F2DA" ... />  

32 bit ARGB 

Comme WPF et Silverlight savent gérer la transparence via le canal dit Alpha, on peut spécifier une couleur par un code hexadécimal 32 bits dit A-RGB (Alpha / RGB) :

<Line Stroke="#7F006666" ... />

Ce qui donnera une ligne en Cyan à 50% d'opacité.

scRGB

Plus subtile et bien moins connu (et encore moins utilisée) est la notation scRGB qui permet d'exprimer une couleur sous la forme de 4 nombres décimaux de type Single ce qui autorise la représentation d'un gamut ultra large. A quoi cela peut-il servir d'utiliser un système de notation des couleurs qui dépasse de loin les limites du RGB qui va de #000000 à #FFFFFF ? Créer un noir plus noir que le noir ou un blanc plus blanc que blanc, à part si on est on un publiciste en mal d'argument pour une nouvelle lessive, cela semble idiot.

Et pourtant cela ne l'est pas. Il n'y a qu'en matière de lessive que "plus blanc que blanc" est une ânerie (dont Coluche se délecta dans un sketch célèbre). Lorsqu'on parle infographie cela peut avoir un sens très concret : conserver les couleurs originales si celles-ci doivent subir de nombreux traitements, comme par exemple une correction du contraste ou de la Hue (teinte dans le système HSL). En effet, à force de calcul, d'arrondis et d'approximations, votre filtre de correction peut finir par créer des à-plat horribles. En utilisant le système scRGB, le code (qu'il soit C# ou XAML) pourra conserver le maximum d'information sur chaque composante couleur.

Un exemple de notation scRGB :

 <Rectangle Stroke="sc#1, 0.6046357, 0.223508522, 0.182969958" Fill="sc#1, 0.7785599, 1, 0"
      RadiusX="25" RadiusY="25" Width="250" Height="80" StrokeThickness="5" Margin="60" /> 
 

(cela donne un rectangle jaune à bords arrondis bistres).

Les couleurs nommées

Enfin, il est possible d'utiliser des noms pour définir des couleurs.

WPF et Silverlight divergent sur ce point car, économie de code oblige pour le Framework réduit de Silverlight, ce dernier ne contient pas toutes les définitions de couleur de son aîné WPF. Mais le principe reste rigoureusement le même (je vous joint d'ailleurs en fin d'article un code qui définit toutes les couleurs WPF utilisables sous Silverlight).

On peut ainsi utiliser des noms tels que : Green, SteelBlue ou MediumVioletRed. Ce qui donne en XAML :

<Rectangle Stroke="yellow" Fill="red" Width ="50" Height="50" />

Et le cul des vaches ?

C'est là que l'affaire devient croustillante... Attendez la suite pour juger !

Par souci de compatibilité avec HTML et SVG, Microsoft a repris la liste des couleurs définies dans ces standards. Une bonne idée, on a souvent accusé Microsoft de ne pas respecter les standards, ce que j'ai toujours trouvé idiot puisque justement l'innovation vient de ce qui est différent et non de l'uniformisation. Et bien justement, quand les développeurs de chez Microsoft se plient à cette servile obligation, cela donne des choses bien curieuses (à l'insu de leur plein gré comme disait l'idiot pédaleur).

En effet, la liste des couleurs HTML est pleine de bizarreries et d'idiosyncrasies qu'il eut été préférable de corriger. Expurgée de ces annomalies la liste des couleurs HTML aurait pu faire une belle liste pour XAML, mais voilà, compatibilité oblige (en fait rien ne l'obligeait, sauf les habitudes), on se retrouve dans l'un des environnements de développement le plus moderne à trainer des bêtises d'un autre âge ! La fameuse taille du cul des vaches au temps des romains influençant le diamètre des fusées de la Nasa...

Par exemple, le spectre couvert par les couleurs HTML ne respecte pas même une répartition à peu près homogène, ce qui fait qu'on dispose de très nombreuses teintes dans les rouges ou les oranges alors que les verts sont très mal couverts.

Autre exemple, les couleurs ont parfois des noms ésotériques qui montrent à quel point les auteurs originaux n'avaient aucune volonté de rendre l'ensemble compréhensible. En dehors d'un américain pure souche, qui peut bien en Italie, en France ou en Slovénie s'imaginer de quel bleu il s'agit lorsque HTML nous indique un "DodgerBlue" ? La charte couleur des Tshirts d'une équipe d'un sport totalement inconnu (le baseball) chez nous n'évoque rien (les dodgers sont en effet une équipe de baseball de Los Angeles, très connus certes, mais uniquement des américains et des rares amateurs étrangers).

Des origines encore plus lointaines

Mais si cela s'arrêtait là le rapprochement avec la petite histoire sur l'arrière train des vaches pourrait sembler un peu capilotractée. En fait nous sommes exactement dans le même cas. Car les choses ne s'arrêtent pas à HTML. Ces couleurs remontent en réalité aux premières implémentations sous UNIX du système X-Window ! HTML définit 16 couleurs qui sont directement mappées sur les 16 couleurs de la palette EGA. Mais plus loin encore, les premiers browsers comme Mosaic (1992) supportaient aussi les couleurs nommées de X11 ! Malheureusement certaines couleurs HTML avaient des homonymes X11 qui, bien entendu, ne donnait pas exactement la même teinte... par exemple ce vert HTML donnait ce vert sous X11.

Un gris plus foncé que le gris foncé

Et c'est ainsi que de tout ce mélange le Gray HTML fut défini par #808080 alors que le DarkGray est défini par  #A9A9A9, un gris plus clair que le gris...

On en revient à la question posée dans le titre de ce billet. Et vous voyez que la réponse est loin d'être celle qui semble s'imposer en toute logique !

WPF et Silverlight réutilisent cette liste de couleurs qui ne remonte pas aux temps des romains, mais pour l'informatique, l'époque de X11 c'est même pire : de la préhistoire !

Du coup, il semble bien plus intelligent d'utiliser les codes couleurs RGB, ARGB ou scRGB que les couleurs nommées si on ne veut pas obtenir des résultats étranges...

Incroyable non ?

On pourrait tirer milles conclusions de cette petite histoire. Je vous laisse y réfléchir, je préfère ouvrir des portes que d'asséner des jugements définitifs. Poser des questions ou créer le questionnement est souvent bien plus utile que d'apporter des réponses toutes faites.

On peut aussi juste en rire, c'est bon pour la santé :-)

Stay Tuned !

(pour ceux qui ont lu jusque là, le cadeau annoncé : ColorHelper.cs (16,31 kb) )

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)