Archive for the 'Tutos Docs' Category


ForceColumnBoundary AttachedProperty

Aucun commentaire


    J'ai récemment eu besoin d'un Datagrid avec lignes dépliables, c'est une demande classique de nos clients et des éditeurs comme Telerik propose cette fonctionnalité en standard. Toutefois les clients ne souhaite pas toujours dépenser 1000$ de licence. Du coup, je suis parti d'une ListBox que j'ai configuré pour qu'elle prenne la forme d'un Datagrid, déplier une ligne se révèle assez simple dans ce cas. Pourtant je suis tombé sur un bug du Panel Grid auquel je ne m'attendais pas vraiment. Chaque colonne de ma grille au sein de mon ItemTemplate possède une valeur relative, chacune d'entre elles possède un FrameworkElement dans le cas présent un TextBlock.

    Toutefois lorsque le contenu d'un TextBlock (par exemple) au sein d'une colonne est trop important, cela décale chaque colonne, du coup on perd l'effet Datagrid. Ce comportement du Layout est illogique si l'on considère qu'en mode Stretch horizontal, chaque TextBlock ne devrait pas dépasser la largeur de la colonne dans laquelle il se trouve. Le résultat est affiché ci-dessous.

    Pour remédier à cela, j'ai codé une propriété attachée qui force chaque élément au sein d'une colonne à respecter les limites de cette dernière. Le code est relativement simple :

    class FrameworkElementExtension
    {
     
        private static Dictionary<frameworkelement , GridLength> FrameworkElementToGridLength = new Dictionary</frameworkelement><frameworkelement , GridLength>();
     
        public static bool GetForceColumnBoundary(DependencyObject obj)
        {
            return (bool)obj.GetValue(ForceColumnBoundaryProperty);
        }
     
        public static void SetForceColumnBoundary(DependencyObject obj, bool value)
        {
     
                obj.SetValue(ForceColumnBoundaryProperty, value);
        }
     
        // Using a DependencyProperty as the backing store for EnsureColumnBoundary.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ForceColumnBoundaryProperty =
            DependencyProperty.RegisterAttached("ForceColumnBoundary", typeof(bool), typeof(FrameworkElement), new PropertyMetadata(new PropertyChangedCallback(OnEnsureColumnBoundary)));
     
        private static void OnEnsureColumnBoundary(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
     
            bool b = System.Convert.ToBoolean(e.NewValue);
     
            FrameworkElement fe = d as FrameworkElement;
     
            if (fe == null)
            {
                return;
            }
     
            Grid g = fe.Parent as Grid;
     
            ColumnDefinition cd = g.ColumnDefinitions[Grid.GetColumn(fe)];
     
            if (g == null || cd == null || DesignerProperties.GetIsInDesignMode(fe))
            {
                return;
            }
     
            if (b)
            {
                if (!FrameworkElementToGridLength.ContainsKey(fe))
                {
                    FrameworkElementToGridLength.Add(fe, cd.Width);
     
                    g.SizeChanged -= new SizeChangedEventHandler(g_SizeChanged);
                    g.SizeChanged += new SizeChangedEventHandler(g_SizeChanged);
                }
            }
            else
            {
                FrameworkElementToGridLength.Remove(fe);
     
                g.SizeChanged -= new SizeChangedEventHandler(g_SizeChanged);
            }
        }
     
        static void g_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            Grid g = sender as Grid;
     
            foreach (FrameworkElement fe in g.Children)
            {
                bool b = GetForceColumnBoundary(fe);
     
                if (b)
                {
                    GridLength gl = FrameworkElementToGridLength[fe];
     
                    ColumnDefinition cd = g.ColumnDefinitions[Grid.GetColumn(fe)];
     
                    cd.Width = gl;
     
                    fe.Width = gl.Value*g.ActualWidth;
                }
            }
        }
    }
    </frameworkelement>

    Le résultat est immédiat et fonctionne qu'elle soit le type de colonne dans votre DataGrid.

    Il suffit d'appliquer la propriété attachée à l'objet que vous souhaitez contraindre dans votre grille de cette manière :

    Text="{Binding Client}" TextWrapping="Wrap" Grid.Column="1"  
    		local:FrameworkElementExtension.ForceColumnBoundary="True"

    Fournir ItemsSource à partir de champs/propriétés statiques

    1 commentaire

      Lorsque l'on crée un Objet valeur sans logique, il est souvent nécessaire de récupérer la liste de ses propriétés ou de ses champs statiques. De cette manière vous pouvez l'affecter à une propriété ItemsSource de ComboBox ou d'AutoCompleteBox et ainsi, filtrer une listBox de ValueObject. Sous Silverlight, rien n'est réellement prévu à cet effet. Il serait possible de créer une énumération en affectant des attributs à chacune des valeurs contenues. Toutefois cette méthode est un peu lourde, avec un peu de réflexion on arrive à un résultat efficace sans se compliquer la vie. Il faut tout d'abord créer une classe contenant les champs ou les propriétés statiques :

      public static class WindowsPhone7
      {
           public static string Undefined = " ";
           public static string Omnia7= "Samsung Omnia 7";
           public static string Focus= "Samsung Focus";
           public static string HD7= "HTC HD 7";
           public static string Optimus7= "LG Optimus 7";
           public static string VenuePro= "Dell venue Pro";
      }

      Ensuite côté VueModèle, vous pouvez exposer une liste de ces dernières grâce à un peu de réflexion et à Linq :

      public List<String> WP7Types
      {
          get
           {
               Type t = typeof(WindowsPhone7);
       
               //GetProperties en lieu et place de getFields si beseoin
               var call = (from opp in t.GetFields() select opp.GetValue(t) as string).ToList();
       
               return call;
          }
      }

      Rien d'autre à faire à part un binding sur le ItemsSource d'un ItemsControl et le tour est joué :)

      Dark & Light Themes avec Windows Phone 7

      Aucun commentaire

        Depuis le service pack, Blend s'est enrichi de nombreuses fonctionnalités dont l'une permet de créer des applications Windows Phone 7. L'un des grands avantages de Blend est son extensibilité, les développeurs de MS ont naturellement ajouté un onglet qui facilite la prévisualisation d'une application WP7 dans sa version finale. Vous pouvez changer l'orientation de l'application, modifier le thème de couleur, tester l'application avec l'émulateur (ou un device) ou encore choisir le thème lumineux, Dark ou Light.

        Ces options sont très utiles, elles vous permettent de tester vos styles, modèles et composants personnalisables dans diverses configurations. C'est notamment le cas avec les deux thèmes lumineux. Contrairement au changement d'orientation du téléphone, vous ne pouvez pas réellement être notifié des changements de thèmes à l'exécution. Toutefois, il suffit d'utiliser les noms des ressources existantes pour préserver cette fonctionnalité lors du développement de vos composants. C'est notamment le cas pour le LayoutGridSelector que je développe actuellement comme vous pouvez le constater ci-dessous.

        La liste des ressources graphiques utilisées par WP7 est ici. Il est intéressant de noter plusieurs petits détails. Il est possible d'écraser une partie ou la totalité du thème chargé. Il vous suffit pour cela de créer une ou plusieurs ressources de couleur ou brush possédant le même nom que celles nativement chargées. Vous pouvez faire le test en changeant la couleur de sélection, créez une ressource de couleur (vert par exemple) comme montré ci-dessous puis placez un Slider dans la grille principale pouvoir la couleur de sélection changée :)

        Il existe également quatre ressources particulières, nommées PhoneDarkThemeVisibility, PhoneLightThemeVisibility, PhoneDarkThemeOpacity, PhoneLightThemeOpacity qui peuvent s'avérer super utiles. Elles permettent aisément d'afficher ou de cacher un objet selon que l'on soit dans le mode d'affichage lumineux ou sombre.

        Design time Custom Attributes

        Aucun commentaire

          Il arrive souvent lorsque vous concevez des composants d'avoir besoin d'une représentation au design time, dans Blend, très spécifique. Sans aller jusqu'à développer des design time dlls, les attributs existants peuvent être d'une grande aide (vous pourrez trouver des explications concernant les designs dll par Justin Angel ici). Du coup, je liste ci-dessous une suite d'attributs que j'utilise couramment lors de mes projets, ceux-ci sont accessibles via les espaces de nom System.ComponentModel et System.Windows.Interactivity :

          1. Définir une description au survol d'une propriété ou d'un contrôle dans le panneau Properties
            [Description("ma description")]
          2. Définir une valeur par défaut au Design Time
            [DefaultValue()]
          3. Définir dans quel sous catégorie du panneau propriété la propriété se trouve
            [Category("Common Properties")]
          4. Définir la propriété dans les options avancées d'une sous catégorie ou ne pas l'afficher. C'est cette sous-partie que vous pouvez déplier.
            [EditorBrowsable(EditorBrowsableState.Advanced)]
          5. Définir si une propriété supporte la liaison de donnée et si oui dans quelles directions.
            [Bindable(BindableSupport.Yes,BindingDirection.TwoWay)]
          6. Définir un type de présentation spécifique. Très utile lorsque la propriété est affectée de certain type d'objets, par exemple cibler un état visuel.
            [CustomPropertyValueEditorAttribute(CustomPropertyValueEditor.StateName)]
          7. Lorsqu'une classe définit des objets customizables, faciliter l'accès aux templates via les menus d'accès aux styles additionnels.
            [StyleTypedProperty(Property = "ToolTipStyle", StyleTargetType = typeof(ToolTip))]
          8. Définir une partie de contrôle
            [TemplatePart(Name="maPartieLogique", Type=typeof(FrameworkElement))]
          9. Définir un état visuel
            [TemplateVisualState(GroupName = "CommonStates", Name = "Normal")]
          10. Définir une propriété comme propriété de type Content alternative, celui-ci est réellement très pratique, C'est ce qu'utilise en interne les controles de type TabItem ou les charts de Silverlight ToolKit.
            [AlternateContentProperty()]

          Fxg Import versus DrawingBrush in Expression Blend SP1

          Aucun commentaire

            Depuis le service pack1 de Blend, ce dernier propose le nouveau type d'importation FXG qui jusque là était purement réservé aux produits de la gamme Adobe. Clairement Microsoft n'a jamais réellement eu pour vocation de créer des logiciels de conception graphique pour le print ou le web tels qu'Expression Design. C'est un coup de génie que l'on voit venir depuis des mois, la notion de DrawingBrush n'a pas été portée sur Silverlight 4 et aucune annonce ne va dans ce sens pour la version 5. Ce n'est plus rédhibitoire aujourd'hui avec l'import direct de librairies FXG, de cette manière Microsoft clarifie son positionnement aussi bien d'un point de vue métier (dans le flux de production) que d'un point de vue outil. Cette fonctionnalité n'est pas forcément connue des développeurs car beaucoup de projets WPF se sont passés des DrawingBrush exportés sous forme de dictionnaires de ressources depuis Expression Design. Cela est certainement dommageable mais on peut contourner cette manière de gérer les différents acteurs d'une production avec plus ou moins de réussite.

            Concrètement grâce aux DrawingBrush sous WPF, nous avons la capacité d'avoir des remplissages de formes vectorielles pour n'importe quel contrôle. Les DrawingBrush sont en général stockés dans des dictionnaires de ressource en bon XAML. L'avantage est qu'un graphiste peut à tout moment ré-exporter ces dictionnaires depuis Expression Design et écraser le précédent. Le développeur n'a de son côté qu'à re-compiler l'application et celle-ci est mise à jour en prenant en compte le nouveau dictionnaire. Chacun est bien a sa place dans ce type de flux de production.

            L'import de fichiers FXG, sans être aussi élégante propose le même type de flux de production, voici l'écran d'importation d'un fichier FXG.

            Pour ma part, Flasher depuis pas mal de temps, j'ai conçu chaque élément et exporté le FXG via Flash. Une fois le fichier importé, le premier réflexe consiste à essayer de récupérer les éléments graphiques du FXG au sein de l'interface Blend. Ceux-ci ne sont pas traités comme ressources graphiques mais bien comme contrôles, du coup vous les trouverez dans le panneau assets comme montré ci-dessous.

            La mise à jour fonctionne exactement de la même manière que les DrawingBrush puisque qu'il suffit de modifier le fichier exporté pour que Blend le détecte et vous alerte d'une mise à jour. Attention toutefois si vous ajoutez ou supprimez des éléments, j'ai remarqué qu'il fallait souvent supprimer le FXG du projet puis le ré-importer dans ce cas. Cette importation est moins élégante à ce jour qu'un export sous forme de dictionnaires de ressource mais sait-on jamais peut-être pouvons nous imaginer une amélioration sous forme d'option d'importation dans l'avenir...

            TextBox Attached Property

            Aucun commentaire

              Lorsque l'on conçoit avec MVVM en Silverlight, on se retrouve rapidement bloqué en matière de binding car le framework Silverlight contrairement à WPF n'a pas pour but d'être exhaustif. De nombreux comportements notamment la mise à jour d'un Binding, peuvent être configurés de manière direct dans WPF au niveau du XAML. Dans Silverlight, la mise à jour d'une liaison de données est souvent réalisée sur l'événement LostFocus ce qui n'est pas l'idéal. En effet lorsque l'utilisateur arrive sur le dernier champ d'un formulaire, juste avant de cliquer sur le bouton, ce dernier ne sait pas forcément si les données qu'il a saisit sont correctes. Pour le savoir celui-ci devra faire en sorte de perdre le focus sur le champ de saisie en question. L'experience utilisateur n'est pas vraiment friendly dans ce cas. Dès lors, on pense à WPF et à la fameuse option UpdateSourceTrigger=PropertyChanged qui résoudrait notre problème en toute simplicité. Grâce aux propriétés attachées, il est possible de reproduire ce comportement au cas par cas. Pour les TextBox, l'idéal serait d'écrire AutoUpdateBinding="True". Voici donc ci-dessous une propriété attachée qui permet de reproduire ce comportement :

              public static class MesExtensions
              {
                  public static bool GetAutoUpdateBinding(DependencyObject obj)
                  {
                      return (bool)obj.GetValue(AutoUpdateBindingProperty);
                  }
               
                  public static void SetAutoUpdateBinding(DependencyObject obj, bool value)
                  {
                      obj.SetValue(AutoUpdateBindingProperty, value);
                  }
               
                  // Using a DependencyProperty as the backing store for AutoUpdateBinding.  This enables animation, styling, binding, etc...
                  public static readonly DependencyProperty AutoUpdateBindingProperty =
                      DependencyProperty.RegisterAttached("AutoUpdateBinding", typeof(bool), typeof(TextBox),
                      new PropertyMetadata(false, new PropertyChangedCallback(OnAutoUpdateChanged)));
               
                  private static void OnAutoUpdateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
                  {
                      TextBox tb = d as TextBox;
               
                      if (tb == null)
                      {
                          return;
                      }
                      if (System.Convert.ToBoolean(e.NewValue))
                      {
                          tb.TextChanged += new TextChangedEventHandler(tb_TextChanged);
                      }
                      else
                      {
                          tb.TextChanged -= tb_TextChanged;
                      }
                  }
               
                  static void tb_TextChanged(object sender, TextChangedEventArgs e)
                  {
                      TextBox tb = sender as TextBox;
                      if (tb == null)
                      {
                          return;
                      }
               
                      BindingExpression be = tb.GetBindingExpression(TextBox.TextProperty);
               
                      if (be != null)
                      {
                          be.UpdateSource();
                      }
                  }
              }

              Ce n'est pas la seule problématique que pose MVVM en matière de binding, le fait de lier des données des collections en lecture seule en est une autre que nous verrons dans un autre post...

              Design Time Resources dans Blend 4

              Aucun commentaire

              Blend 4 nous offre de nouvelles fonctionnalités et l'une d'entre elles est assez obscure de prime abord. Dorénavant, il est possible dans Expression Blend de créer des ressources uniquement utilisées au design time (au moment de la conception sous Blend ou Visual Studio). Le problème est assez banal, Il est souvent nécessaire d'affecter des ressources graphiques qui ne sont définies qu'à l'exécution. Du coup, dans l'espace de travail graphique, sous Visual Studio ou Blend, celles-ci ne sont pas résolues puisque pas encore définies. Cela occasionne des erreurs d'accès et quelques problèmes d'affichage. Blend 4 permet maintenant de résoudre cette problématique mais la manière de procéder n'est pas très évidente. L'objectif est de créer un dictionnaire de ressource contenant une ressource ou plusieurs ressources que vous utiliserez uniquement au design time. Le première chose à faire consiste à créer un projet ainsi qu'une ressource contenue dans un dictionnaire de ressource. Un dégradé ou un solidColorBrush fera bien l'affaire (voir ci-dessous).

              Ensuite supprimez le lien contenu par App.xaml, les ressources contenues sont désormais inaccessibles et vous obtenez des erreurs d'accès. Ce n'est pas grave, il vous faut passer par cette étape. Sauvegardez le projet puis ouvrez-le à nouveau sous Blend 4, vous obtenez une jolie boîte de dialogue vous indiquant si vous souhaitez créer un dictionnaire utilisable au design time (voir ci-dessous).

              Pour finir, créez la ressource via C#, c'est cette dernière qui sera utilisée par Silverlight à l'exécution.

              Au final vous obtenez deux rendus différents. Le premier concerne le design time, le second correspond au rendu final à l'exécution utilisant la ressource instanciée lors de l'initialisation :) Du coup, le designer est complètement autonomes. Au final, ce procédé n'est pas très orthodoxe mais il est très utile. Le principe ne repose sur rien d'autre qu'une compilation conditionnelle générée au niveau du projet(.csproj). Le code suivant :

              <Page Include="Properties\DesignTimeResources.xaml">
               <Generator>MSBuild:Compile</Generator>
               <SubType>Designer</SubType>
               </Page>

              est remplacé par celui-ci :

              <Page Include="Properties\DesignTimeResources.xaml"
              Condition="'$(DesignTime)'=='true'
              OR ('$(SolutionPath)'!='' AND Exists('$(SolutionPath)')
              AND '$(BuildingInsideVisualStudio)'!='true'
              AND '$(BuildingInsideExpressionBlend)'!='true')">
               <Generator>MSBuild:Compile</Generator>
              <SubType>Designer</SubType>
               <ContainsDesignTimeResources>true</ContainsDesignTimeResources>
               </Page>
              

              Conception MVVM, l’exemple du diaporama

              7 commentaires

              Silverlight 4 et Blend 4 RC sont à peine sortis que l'on voit déjà tous les bénéfices apportés par ces derniers. Deux nouveautés ont retenues mon attention, l'amélioration du système de liaisons et le modèle de conception MVVM complètement géré dans Silverlight 4 via l'implémentation des commandes. L'objectif de cet article est de vous familiariser avec l'implémentation du modèle de conception MVVM (Modèle, Vue, Vue-Modèle) dans Silverlight 4 et de mettre en valeur les avantages apportés par le système de liaisons.

              I - Un mot sur le nouveau système de liaison

              Comme vous le savez sans doute, il est désormais possible de créer des liaisons de données entre différentes instances de DependencyObject. Auparavant, il était simplement impossible de créer une liaison de données lorsque vos objets n'héritaient pas de FrameworkElement. Dorénavant, tout ou presque est "bindable" et c'est tant mieux. Nous pouvons donc créer une liaison entre la rotation d'un RenderTransform et la propriété Value d'un Slider. Cet exemple est peut-être utile en matière de design mais pas vraiment pertinent pour un développeur. Nous montrerons donc plus loin les avantages de cette amélioration dans le cadre MVVM.

              II - Avant-propos

              Sachez tout d'abord qu'il y a de nombreuses interprétations du modèle de conception MVVM, cet article ne reflète que ma propre vision de ce pattern. C'est en partie pour cette raison que le type de projet MVVM "Silverlight MVVM Application" que l'on pouvait générer dans Blend 4 bêta a été renommé Silverlight Databound Application dans la version RC. Cela permet de ne pas figer MVVM à une seule interprétation et en même temps de dépasser le cadre de ce type de développement.

              III - Introduction à MVVM

              MVVM est un principe de conception et d'organisation qui sépare le développement d'applications RIA en trois catégories : Modèle, Vue et Vue-Modèle. De cette manière, le code et le design d'une application sont totalement découplés et il est plus facile de les faire évoluer. Concrètement le développeur mettra à disposition du designer des appels de méthodes dans Vue-Modèle qui seront très simple à appliquer sur des composants présents dans la vue.

              L'avantage direct de cette méthodologie est de libérer le designer de toute contraintes en matière de design. Il ne se contentera pas de modifier l'aspect visuel de contrôles déjà choisis au préalable. Il pourra, au contraire, définir librement le type de contrôles, leur nom, les transitions sans avoir à se préoccuper des détails de l'implémentation.

              Nous verrons un exemple concret de ce concept.

              1 - Qu'est-ce qu'une vue ?

              Pour faire simple une vue correspond à une interface visuelle dédiée aux interactions utilisateurs. Autrement dit, c'est là que vont agir les utilisateurs, celles-ci sont constituées de trois types de choses :

              • Un arbre visuel et logique composé d'instances d'UIElement.
              • Des transitions et des animations.
              • Des comportements logiques permettant, par exemple, de piloter animations et transitions.

              2 - Une vue doit-elle contenir de la logique ?

              Ici 90% des développeurs disent non. Pour ma part, je pense qu'une vue doit contenir sa propre logique permettant de gérer ses animations et ses transitions. Dans la majorité des cas, ces tâches ne sont pas gérées par la création de code C# mais par l'utilisation de comportements (behaviors). Ainsi, il n'y a nullement besoin de code C# car les comportements répondent pleinement à cette problématique. Toute la difficulté de conception provient souvent du couplage possible existant entre la gestion des données et l'affichage. Les comportements ont également pour but de répondre à cette problématique. Ceux-ci sont donc déterminants lorsque vous concevez en mode MVVM.

              3 - Qu'est-ce que le Modèle ?

              Pour faire simple, le modèle contient les données, les mets à disposition. Le modèle gère également toute les appels distants ou opérations permettant les opérations CRUD (Create, Read, Update, Delete). Les développeurs utilisent souvent une classe statique afin d'y accéder simplement. Le modèle peut diffuser des événements si besoin pour indiquer l'état de chaque opération. Vous trouverez quelques développeurs qui verront modèle uniquement comme espace de stockage.

              4 - Qu'est-ce que Vue-Modèle ?

              C'est là le cœur du modèle de conception MVVM. Pour faire simple, Vue-Modèle est une abstraction de la vue d'un point de vue Modèle. Autrement dit, Vue-Modèle gère la liaison entre les données contenues dans le modèle et leur affichage dans la Vue. C'est donc l'intermédiaire entre Vue et Modèle.

              On m'a récemment posé la question :

              doit-on partir de Vue-Modèle ou doit concevoir d'abord la vue puis Vue-Modèle ?

              Il n'y a pas de réponse simple à cette question. Cela dépend avant tout de votre métier et vers quel type de qualité vous souhaitez tirer l'application. Souhaitez-vous obtenir une forte interactivité graphique ou une architecture propre et efficace avec un maximum de performance ? Certains pensent que les deux sont possibles mais là encore il ne faut pas se leurrer, si l'un n'impactait pas l'autre nous n'aurions qu'un seul type de projet nommé Databound Application. Plus le graphisme et les interactions sont contraignantes, plus il est difficile de fournir une architecture propre et performante en conservant les mêmes délais. Toutefois, lors de la conception d'une application, il est vivement conseillé de partir d'un prototype. La vue est ce qu'il y a de plus concret pour votre client et pour vous en tant que développeur. C'est dans la vue que sont susceptibles de s'opérer de grande modification car c'est ce que le client souhaite mettre à disposition des utilisateurs. Ce ne serait pas forcément le discours d'un spécialiste SQL mais le client n'est pas là pour comprendre des schémas de Base de donnée. Dans le cas d'applications très standards comme un lecteur Vidéo, par exemple, il est possible de concevoir Modèle-Vue en premier car les opérations que souhaite réaliser l'utilisateur seront toujours les mêmes (Lecture, Stoppe, Pause, ect...). L'abstraction des commandes est, dans ce cas, très prévisible. Nous reviendrons sur la notion de commandes plus tard.

              5 - Vue-Modèle doit-il gérer les animations et les transitions ?

              Si l'on reprend la définition de base (Vue-Modèle est une abstraction de la vue d'un point de vue Modèle), Vue-Modèle ne doit pas gérer les animations directement mais notifier la vue par n'importe quel moyen (propre et efficace) afin que celle-ci déclenche transitions et animations. Encore une fois, les comportements vont grandement nous aider dans cette tâche. Toutefois, dans le cas d'animations créées dynamiquement ou dans certaines problématiques, il arrive que l'on soit obliger de gérer les animations dans Vue-Modèle. Il faudra dans ce cas créer une classe de gestion dédiée ou utiliser des propriétés de dépendances attachées. Tant que possible et contrairement à la croyance répandues, les vues contiennent la logique permettant l'affichage des transitions et animations, c'est à ça que sert le code behind du UserControl Vue. Les comportements ne sont rien d'autres que du code logique placés dans l'arbre visuel et logique. Il n'héritent pas de UIElement mais de DependencyObject. Par défaut, le développeur n'a pas à assurer cette gestion, cela est du ressort du designer interactif dans le meilleur des cas. Cela peut toutefois être remis en cause lorsque vous rencontrez certaines problématiques de conception.

              6 - Le tout MVVM ?

              La grande question, est-ce que vous devez toujours tout concevoir en MVVM ? La réponse est non. Voici mes arguments:

              • Si c'était le cas on aurait pas différents types de projets.
              • Utiliser un seul modèle de conception est une pratique extrémiste qui ne répond pas toujours aux contraintes de temps imparties.
              • Selon la taille de vos projets et vos besoins en matière d'évolution et de changements, il n'est pas forcément utile d'utiliser MVVM car cela peut par certains côtés ajouter une touche de complexité. Par exemple, lorsque vous essaierez de créer des liaisons de données entre des collections d'objet présent dans la vue et des collections contenues dans Vue-Modèle.
              • Faire du Full MVVM relève avant tout du dogme, c'est comme si vous ne cuisiniez que des plats italiens. En bref vous passez à côté de la cuisine française, indienne, etc...
              • Plus le design et ses contraintes sont importants, plus il sera difficile de normaliser le développement. C'est l'éternel problématique du développement informatique. Le design et le développement représentent deux facettes de la production qui se lancent des défis en permanence. Pour vous en convaincre, essayez de penser le site suivant en MVVM pour toutes les pages :: www.experience159.com

              7 - Schéma récapitulatif

              Voici un schéma simple reprenant une partie de ce que nous avons exposé :

              Après cette brève introduction, nous allons nous plonger dans la conception d'une application complète en MVVM.

              IV - Un diaporama MVVM côté Vue

              Téléchargez le projet GalleryMVVM_base. Ce projet est créé sur le modèle Silverlight Databound Application.Vous remarquez tout d'abord que MainPage.xaml contient une vue formalisée par un UserControl nommé GalleryView. On peut donc envisager MainPage comme un conteneur d'agencement des vues. Un répertoire contient les UserControl symbolisant la partie Vue, un autre contient les classes correspondant à Vue-Modèle, le dernier contient toutes les classes côté Modèle gérant l'accès aux données (voir Figure ci-dessous).

              Dans GalleryView.xaml, tous nos objets sont déjà créés et designés, ce travail peut être réalisé indépendamment du développement côté C#. On considère que le développeur et le designer se sont mis d'accord sur un certain nombre de fonctionnalités listés ci-dessous :

              • Afficher / cacher l'objet interactif (bouton par exemple) Image Précédente / Image Suivante
              • Charger Image suivante / précédente
              • Récupérer l'ImageBrush de la nouvelle et de l'ancienne image chargées (cela permet de créer des transitions)
              • Afficher la pagination
              • Afficher la description, le titre et l'url de l'image en cours
              • Naviguer vers l'état "chargement en cours" ou l'état "chargement terminé"
              • Afficher la barre de progression

              Le développeur et le designer ont donc élaboré chacun de leur côté une partie du projet. Le design étant finalisé, il nous faut maintenant récupérer l'abstraction Vue-Modèle, nommée GalleryViewModel, comme contexte de données de la vue. Nous reviendrons plus tard au code de GalleryViewModel. Ajoutez l'espace de nom xmlns:local="clr-namespace:GalleryMVVM", au sein du UserControl racine de GalleryView. Nous allons déclarer une nouvelle ressource de type GalleryViewModel comme montré ci-dessous :

              Compilez l'application si besoin par un ctrl+maj+b, maintenant que nous avons instancié la Vue-Modèle comme ressource, nous pouvons facilement la définir comme contexte de données de la grille LayoutRoot comme montré ci-dessous.

              Cela n'est pas suffisant, le mieux est de créer une liaison de données car cela permet de mettre à jour la Vue quand Vue-Modèle change, et inversement ( avec liaison à deux voies). Nous remplaçons cette expression

              par celle-là :

              Rendez-vous maintenant dans le panneau Data, conservez LayoutRoot sélectionné et dépliez Data Context. Vous obtenez une liste de propriétés et de commandes correspondant au cahier des charges établi entre développeur et designer (voir ci-dessous).

              Du côté de la vue le tour est joué, il suffit de glisser déposer, les propriétés et les commandes sur les objets de l'arbre visuel et logique. Il est également possible de procéder via l'utilisation du menu DataBinding obtenu lorsque vous cliquez sur l'icône carré présente à droite de chaque propriété d'objet.

              Vous pouvez faire un simple test en glissant déposant les commandes DisplayNextPicture et DisplayPreviousPicture sur les boutons et établir une liaison entre le remplissage du Rectangle nommé newBitmap et la propriété NewPictureIB de la Vue-Modèle. Comme vous le constater, le diaporama est entièrement designable, autrement dit, le graphiste peut choisir librement le type, l'agencement, le nom des composants ainsi que les états visuels qu'il souhaite activer. Pour glisser déposer les propriétés de la Vue-Modèle en choisissant la propriété ciblée, il suffit d'appuyer sur la touche alt en même temps que le glisser-déposer.

              V - Gérer les transitions

              Le nouveau système de liaison apporté par Silverlight 4 nous donne pas mal de souplesse lorsque l'on développe un projet en suivant ce modèle de conception. Dans ce mode de conception, on essaie de limiter au maximum le code logique de chaque vue, il est donc assez pratique d'utiliser des comportements au sein de Blend pour gérer les transitions et les animations. Nous allons utiliser le nouveau système de liaison basé applicable à l'ensemble des instances de type DependencyObject pour gérer nos comportements interactifs.

              1 - Déclencher les transitions

              C'est une des problématiques majeures lorsque l'on évoque MVVM. En premier lieu, Microsoft propose un comportement interactif nommé DataStateBehavior qui facilite cette gestion. Vous pouvez le déposer sur LayoutRoot, celui-ci est capable de déclencher une transition selon la valeur d'une propriété de Vue-Modèle. Dans notre cas, il s'agit de la propriété IsLoaded qui a pour valeur false lorsque la nouvelle image est en cours de chargement, et true lorsque celle-ci est chargée. La figure ci-dessous affiche le paramétrage du comportement.

              Concrètement, il suffit de créer une liaison entre la propriété Binding du comportement et la propriété IsLoaded de la Vue-Modèle. Lorsque la valeur attendue est égale à True alors on affiche l'état visuel Completed sinon on affiche l'état visuel Loading. Cela peut sembler étrange de mettre un T majuscule, toutefois la propriété Value est typée Object. Ainsi, bien que IsLoaded soit de type boolean, c'est sa représentation sous forme de chaîne de caractère qui est comparée par appel implicite de la méthode ToString(). Cette méthodologie a pour avantage de laisser au designer le soin de déclencher les transitions simplement.

              2 - activer ou désactiver un comportement interactif

              Dans Silverlight 3, il n'était pas possible de lier une propriété de comportement, comme IsEnabled à une propriété de la Vue-Modèle. Même si cette dernière implémentait INotifyPropertyChanged, la propriété IsEnabled n'était pas "bindable" car les comportements n'héritaient pas de FrameworkElement mais de DependencyObject. Avec le nouveau systèmes c'est désormais chose possible. Placez deux instances du comportement GotoStateAction sur chacun des deux exemplaires de bouton. Lorsque la souris survolera l'un ou l'autre, nous naviguerons vers l'état visuel RightButtonMouseOver ou LeftButtonMouseOver. Lorsque la souris quittera le survol de l'objet, nous afficherons l'état DefaultMouseLeave. La figure ci-dessous Toutefois correspond au paramétrage du bouton de droite.

              Jusque-là nous n'utilisons pas le contexte de données de la Vue-Modèle mais nous allons y remédier. Dans le scénario de base développeur et designer sont partis du principe que l'affichage d'une pagination (par exemple : image 1/6) n'est pas toujours de mise. Ainsi, cette navigation ne sera possible que dans le cas ou l'affichage de la pagination est prévue par Vue-Modèle. C'est le cas lorsque la propriété DisplayPageNumbers (de GalleryViewModel) est à true. il suffit donc d'établir une liaison de données entre IsEnabled du comportement et DisplayPageNumbers. Voici le code XAML correspondant :

              Le code est assez simple à comprendre :

              • La visibilité du bouton est liée à RightArrowVisibility de GalleryViewModel.
              • Le texte, affiché via la propriété Content, est lié à la propriété Paging de GalleryViewModel.
              • Très important, toutes les instances héritant du type ButtonBase implémentent, depuis Silverlight 4, le commanding via la propriété Command. Celle-ci permet d'exécuter des commandes (des actions de ViewModel). L'événement Click provoque l'exécution de la commande. Pour tout autre type d'événements que Click, vous pouvez utiliser le comportement nommé InvokeCommandAction.
              • Pour finir, les comportements de navigation sont activés ou désactivés par la propriété DisplayPageNumbers de GalleryViewModel.

              VI - le Diaporama côté Vue-Model

              Concernant Vue-Modèle, le développeur est sur des rails. Le code est standard et les étape sont toujours les mêmes :

              • Il est assez pratique de créer une classe de base dont toutes les classes de type Vue-Modèle hériteront. Vous pouvez l'appeler ViewModel ou ViewModelBase.
              • Cette classe doit implémenter deux interfaces : INotifyPropertyChanged et IDataErrorInfo. INotifyPropertyChanged permet de notifier la vue de tout changement de propriété. Cela permet d'utiliser le mécanisme de créer des liaisons de données qui seront rafraichies lorsque les propriétés de Vue-Modèle seront mise à jour. IDataErrorInfo est une nouvelle interface fournie par Microsoft qui permet aux vues d'afficher les erreurs levées lors d'une mauvaise affectation. Concrètement, cela est particulièrement utile lors de liaisons de données à deux voies. Dans notre cas, l'intérêt est moindre car la vue n'a pas de raison de modifier les propriétés de Vue-Modèle. Dans le cas d'un formulaire IDataErrorInfo est obligatoire car bien plus pratique à utiliser qu'une levée d'exception (via l'instruction Throw new Exception). Je vous renvoie à l'article de John Papa concernant l'implémentation de IDataErrorInfo.
              • Il est nécessaire de créer des propriétés qui seront accessibles directement depuis la vue
              • Pour finir, nous devons créer des commandes qui seront exécutées depuis la Vue. L'implémentation des commandes est également couvert par un article de John Papa que vous trouverez ici. Concrètement, il propose de créer une classe abstraite nommée DelegateCommand qui facilitera l'instanciation de ces dernières dans Vue-Modèle. Normalement, une commande correspond à un type d'objet mais cela peut-être éviter grâce à la délégation.

              1 - Les propriétés accessible au sein de la vue "Bindable"

              Pour qu'une propriété soit accessible depuis la vue, elle doit utiliser l'accesseur public. De plus, dans l'optique où celle-ci utilise le système de liaison classique, elle doit notifier la vue lorsque qu'elle est modifiée. Pour cela, il suffit d'utiliser la méthode NotifyPropertyChanged héritée de la classe ViewModel, implémentant INotifyPropertyChanged. Voici un exemple pour la propriété exemple

              2 - les commandes

              Les commandes doivent toujours faire référence à une méthode ainsi qu'à une valeur booléenne qui indique si la commande doit-être exécutée. Voici le code C# correspondant aux commandes DisplayNextPicture et DisplayPreviousPicture :

              Rien de bien compliqué, la propriété Index est affectée d'une nouvelle valeur, et notifie la vue de ce changement via le mécanisme héritée de INotifyPropertyChanged. La méthode CanDisplayPictures renvoie systématiquement true ce qui n'est pas l'idéal, vous pourrez faire certains tests à ce niveau. C'est ce que je fais pour la commande LoadAllPicture qui prend en deuxième paramètre de son constructeur le booléen renvoyé par CanLoadAllPictures :

              VII - l'application finale

              Elle est visible à cette adresse : http://www.tweened.org/wp-content/uploads/applis/diaporamaMVVM/.

              Le projet ainsi que les sources complètes sont quant à eux téléchargeables ici : http://www.tweened.org/wp-content/uploads/applis/GalleryMVVM.zip.

              Concevoir vos propres comportements avec Silverlight

              Aucun commentaire

              Si vous avez déjà utilisé Expression Blend, il est fort possible que vous ayez utilisé les comportements au cours de vos projets. Ils sont bénéfiques à plusieurs niveaux : d'une part, ils permettent d'ajouter de l'interaction sans coder en C# ou VB ; d'autre part, ils apportent une aide précieuse lorsqu'il est important de découpler la vue, le modèle et la vue-modèle. Pour finir, ils jouent un rôle essentiels en production puisqu'ils sont développés dans un objectif de réutilisation et qu'ils sont très simplement paramétrables dans Expression Blend.

              Il existe deux grandes familles de comportements, les comportements simples et les comportements d'action déclenchée. Les comportements simples sont représentés par une icône d'engrenage, MouseDragElementBehavior, que vous trouverez dans le panneau assets, est de ce type. Dès lors que vous le déposer sur l'objet, celui-ci acquière une capacité supplémentaire directement accessible. Au contraire les comportements déclenchés n'agissent que lorsqu'un événement est diffusé. Ils sont identifiés par l'icône de l'engrenage surmontée d'une flèche de lecture.

              Pour créer un nouveau comportement, il suffit dans Blend d'ajouter un élément dans un projet de type Behavior ou Action (via le menu Add Item...). Nous allons créer un comportement permettant de créer la copie visuelle d'un objet. Cela nous permettra de générer un effet de réflexion comme montré dans le projet Silverlight ci-dessous.

              Install Microsoft Silverlight

              Créez un nouveau projet et ajoutez un comportement de type Action. Faites-le hériter de la classe TargetedTriggerAction. Cela sera pratique car nous pourrons cibler un objet dont on souhaite répliquer le visuel. Vous obtenez le code ci-dessous :

              public class SnapshotAction : TargetedTriggerAction&lt;UIElement&gt;
              {
              	public SnapshotAction()
              	{
               
              	}
               
              	protected override void Invoke(object o)
              	{
               
              	}
               
              }

              C'est la méthode surchargée Invoke qui nous intéresse. Celle-ci sera exécutée chaque fois qu'un événement au choix sera diffusé. Nous allons maintenant récupérer une image du composant ciblé puis l'appliquée sur le composant possédant le comportement.

              protected override void Invoke(object o)
              {
                    //on déclare une nouvelle image en écriture
                    WriteableBitmap Wb;
               
                    //on l'instancie en lui passant l'objet
                    //elle doit créer un instantanné
                    Wb = new WriteableBitmap(this.Target, null);
               
                    //on crée un nouveau pinceau d'image
                    ImageBrush Ib = new ImageBrush();
               
                    Ib.AlignmentX = AlignmentX.Left;
               
                    Ib.Stretch = Stretch.None;
                    //on affecte l'image binaire de l'instantanné à
                    //la propriété ImageSource du pinceau d'image
                    Ib.ImageSource = Wb;
               
                    //Ensuite on teste le type de l'objet ciblé et on affecte
                    //la bonne propriété en fonction de ce dernier
                    if (AssociatedObject is Shape) ((Shape)AssociatedObject).Fill = Ib;
                    else if (AssociatedObject is Panel) ((Panel)AssociatedObject).Background = Ib;
                    else if (AssociatedObject is Control) ((Control)AssociatedObject).Background = Ib;
                    //nous pourrions utiliser un peu de réflexion pour améliorer ce test
              }

              La propriété Target du comportement correspond à l'objet ciblé par ce dernier. La propriété AssociatedObject fait quant à elle référence à l'objet sur lequel est déposé le composant. Dans notre cas, nous testons simplement le type de cet objet pour lui appliquer la capture bitmap en tant que pinceau de remplissage.

              Voilà le tour est joué, il suffit de compiler le projet une première fois pour accéder au comportement dans le panneau assets, puis de déposer ce dernier sur une instance de Shape (par exemple) comme indiqué sur la figure ci-dessous.

              Le comportement finalisé est téléchargeable à l'adresse suivante. J'y ai ajouté la possibilité de déclencher la réplication bitmap en mode automatique via l'utilisation d'un DispatcherTimer. Il est également possible de modifier la cadence de rafraichissement pour éviter les pertes de performances (voir figure ci-dessous).

              Pour finir, sachez qu'avec Silverlight 4, il est désormais possible de créer des comportements intégrant directement le pattern commande. Celui-ci est directement lié au modèle de conception MVVM mais cela est une autre histoire.

              Les polices de caractères avec Caractères spéciaux

              Aucun commentaire

                je remonte ce post de Aude Mousset qui explique comment contourner le problème d'embarquement des polices dont le nom contient des caractères spéciaux...

                Next Page »