Archive for the 'Composants' 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"

    Grid extension [Extension]

    Aucun commentaire

      Il est très souvent utile d'agrandir un panneau spécifique au sein d'une application. J'ai réalisé une extension à base de propriétés attachées qui permet très simplement de maximiser des éléments au sein d'un conteneur de type Grid. Voici un exemple d'éléments contenus au sein d'une grille.

      Pour cela, il suffit tout d'abord de spécifier que la grille peut maximiser ces enfants. Ensuite chaque enfant peut être maximiser via l'utilisation d'un bouton sur lequel vous définissez la cible de l'agrandissement. La transition est un fondu entre le visuel de départ et celui d'arrivée. La durée de la transition, exprimée sous forme de millisecondes, peut être configurée. Durant celle-ci et jusqu'à sa fin, le contenu de la grille n'est plus interactif. La capture ci-dessous illustre les différentes méthodologies et possibilités en terme de code XAML.

      Voici l'exemple ci-dessous en fonctionnement.

      Install Microsoft Silverlight

      je mets cette extension en téléchargement ici.

      ClipToBound Behavior [Extension]

      Aucun commentaire

        Comme beaucoup d'autres petits détails, vous aurez sans aucun doute remarqué qu'il n'est pas possible de définir la propriété ClipToBounds dans Silverlight contrairement à WPF. Autrement dit, vous ne pouvez pas faire en sorte qu'un objet contenu dans un autre ne dépasse pas des limites de son parent sauf si cela est prévu nativement. Comme d'habitude, la solution consiste à créer une extension ou un comportement. J'ai fais une propriété attachée ainsi qu'un comportement qui facilitent cette mise en oeuvre.

        Voici une copie d'écran réalisée sous Blend.

        Et voici le code très simpliste qui permet de créer cet effet via une AttachedProperty :

        public static class ClippingExtension
            {
         
                public static bool GetClipToBounds(DependencyObject obj)
                {
                    return (bool)obj.GetValue(ClipToBoundsProperty);
                }
         
                public static void SetClipToBounds(DependencyObject obj, bool value)
                {
                    obj.SetValue(ClipToBoundsProperty, value);
                }
         
                // Using a DependencyProperty as the backing store for ClipToBounds.  This enables animation, styling, binding, etc...
                public static readonly DependencyProperty ClipToBoundsProperty =
                    DependencyProperty.RegisterAttached("ClipToBounds", typeof(bool), typeof(FrameworkElement), new PropertyMetadata(false,OnClipToBoundsChanged));
         
         
                private static void OnClipToBoundsChanged (DependencyObject d, DependencyPropertyChangedEventArgs e)
                {
                    FrameworkElement f = d as FrameworkElement;
         
                    if (f == null)
                    {
                        return;
                    }
         
                    if (System.Convert.ToBoolean(e.NewValue))
                    {
                        f.SizeChanged += new SizeChangedEventHandler(f_SizeChanged);
         
                        RectangleGeometry rg = new RectangleGeometry();
         
                        rg.Rect = new Rect() { Width = f.ActualWidth, Height = f.ActualHeight };
         
                        f.Clip = rg;
                    }
                    else
                    {
                        f.SizeChanged -= new SizeChangedEventHandler(f_SizeChanged);
         
                        f.Clip = null;
                    }
         
                }
         
                static void f_SizeChanged(object sender, SizeChangedEventArgs e)
                {
                    FrameworkElement f = sender as FrameworkElement;
         
                    if (f == null)
                    {
                        return;
                    }
         
                    RectangleGeometry rg = new RectangleGeometry();
         
                    rg.Rect = new Rect() { Width=f.RenderSize.Width, Height=f.RenderSize.Height };
         
                    f.Clip = rg;
         
                }
            }

        RangedSlider [Custom Control]

        Aucun commentaire


          J'ai récemment eu besoin d'utiliser un Slider avec deux thumbs, certaines librairies le proposent, mais de manière générale, je ne les trouve pas simple à "templatiser". N'en ayant pas trouver d'adéquat à télécharger, je l'ai codé et je le fournirai en version finale avec la prochaine version de la librairie Tweened. Le look du contrôle n'est pas terminé. Un RangeSlider permet de filtrer ou de définir une plage de valeur. Cela peut être du double, des DateTime (ou tout autre type à l'aide d'un converter).
          Le contrôle fourni ne propose pour l'instant que le mode horizontal mais je prévois la version avec affichage verticale. Il est possible de définir le mode d'update de la propriété Value durant le drag ou lors du relâché. Cela est très utile lorsque vous souhaitez éviter un rafraichissement trop fréquent coté Vue-Modèle par exemple. Coté intégration graphique, c'est exactement la même procédure que pour le slider classique et donc assez facile à personnaliser.

          Get Microsoft Silverlight

          Voici une exemple.

          Install Microsoft Silverlight


          Vous pouvez télécharger le projet ici.

          Yet Another FlipControl [Custom Control]

          2 commentaires


            Lorsque vous développez une application Silverlight ou Wpf, certaines questions se posent avec plus de force. C'est notamment le cas concernant l'ergonomie. Définir un zoning simple et sans heurts est indispensable pour une utilisation intuitive de votre application. Vous remarquerez souvent que certains usages ou même certaines entités métier présentent plusieurs facettes.
            Prenons l'exemple de plusieurs classes héritant d'une classe ou implémentant une interface. Nous pourrions partir du principe que chaque sous-classe présente des membres communs a la classe mère ainsi qu'un jeu de propriétés ou de méthodes qui lui est spécifique. Lorsqu'une vue de l'application représente ce type d'instance, nous pouvons répartir l'affichage de cette dernière en deux zones distinctes que l'utilisateur n'aura pas forcément besoin de voir en même temps. La rotation 3D de la surface est un bon moyen d'y parvenir. Je mets à disposition un nouveau contrôle utilisateur : le FlipControl. Il est constitué de deux faces qui exposent chacune, un visuel distinct. On affiche successivement les deux faces par une rotation 3D sur l'axe horizontal ou vertical.
            Je mets donc à disposition un contrôle de ce type directement utilisable dans Blend. C'est un ContentControl qui possède les propriétés Header, FrontContent, BackContent et FrontTemplate et BackTemplate représentant le DataContext de chaque face.

            Il possède également une propriété IsFlipped sur laquelle vous pourrez créer une liaison de donnée afin de déclencher une rotation 3D du contrôle. L'avantage principal de ce FlipControl est de faciliter la configuration du contenu de chaque face directement dans Blend.

            Install Microsoft Silverlight

            Les sources de base ainsi que le projet de test sont téléchargeables à cette adresse.

            InertialSlider [Custom Control]

            Aucun commentaire


              Dans le cadre d'un projet Silverlight sur tablette tactile, j'ai récemment eu besoin d'un Slider sans limite de valeur. Autrement dit, la valeur Maximum est indéfinie ou alors très éloignée. L'idée est d'augmenter la vitesse d'incrémentation au fur et à mesure du drag du contrôle Thumb. Vous pouvez tester le contrôle ci dessous. Il est possible de définir le pas du changement de valeur ainsi que la finesse d'accélération.

              Install Microsoft Silverlight


              Les sources de base ainsi que le projet de test sont téléchargeables à cette adresse.

              FrameReported & ContactButton [Custom Control]

              Aucun commentaire

                Si vous avez déjà développé pour Windows Phone 7, vous avez déjà du remarquer certaines interactions problématiques. C'est notamment le cas lorsque vous utilisez les contrôles de base. Par exemple, lorsque vous cliquez sur un bouton par inadvertance (très souvent le cas pour les enfants), cela bloque toute autre tentative d'écoute sur un autre contrôle. Pour le vérifier posez deux boutons dans une grille windows phone 7 et compilez le projet sur le device. Lorsque vous cliquez sur le premier bouton, la diffusion de tout autre événement souris ou contact (tels que MouseLeftButtonDown par exemple) est impossible. Vous vous trouvez dans un cas classique d'interaction Multi-Touch sans l'avoir prévu. Afin de réaliser des applications réellement multitouch qui prennent ce genre de problématiques en compte, il existe différentes solutions plus ou moins efficaces .

                Utiliser des événements plus adaptés : ManipulationStarted, ManipulationDelta et ManipulationCompleted. Ceux-ci sont directement implémentés dans le framework au niveau de la classe UIElement. Ces événements fournissent plus de détail concernant l'interaction utilisateur en cours. C'est cette solution qui est privilégiée dans 90% des cas. Toutefois cela ne répond pas à notre problématique principale.

                Implémenter l'API de Gesture proposée par le Toolkit Windows Phone 7. Cette API facilite la vie lorsqu'il est nécessaire de connaitre l'écartement entre deux contacts, la rotation du segment entre des contacts et tout autre type d'opérations de ce genre. Le problème est qu'encore une fois, cela n'est pas une bonne solution car les événements proposés ne concernent qu'une seule surface (contrôle) à la fois. De plus l'implémentation de cette API n'est pas très pratique à mettre en oeuvre et ajoute une surcouche à l'API native et engendre de faibles performances (voir figure ci-dessous).

                La seule bonne solution, et la plus exhaustive en matière de possibilités, consiste à Utiliser la classe static Touch. Celle-ci représente le plus bas niveau de traitement MultiTouch. Elle vous permet d'écouter un unique évènement nommé FrameReported. L'objet événementiel récupéré par l'écouteur possède plusieurs informations qui permettent de développer votre propre classe Wrapper MultiTouch ou encore des contrôles particuliers. Ces informations sont contenus dans l'objet événementiel de type TouchFrameEventArgs. L'objet événementiel permet de récupérer le premier contact, de supprimer la diffusion d'événements souris via la méthode SuspendMousePromotionUntilTouchUp. TouchFrameEventArgs donne également accès à la liste des contacts en cours sur une surface via la méthode GetTouchPoints. Cette liste est relative au contexte conteneur passé lors de l'appel de la méthode. De la même manière que GetPosition() de la classe MouseButtonEventArgs, passer null renvoie la liste relative au VisualRoot, donc à l'application elle-même. Chaque contact sur la surface est de type TouchPoint. A ce stade, l'API commence à être réellement utile puisqu'il est possible, pour chaque contact, de récupérer des informations utiles comme sa dimension , son action(relaché, déplacé, appuyé) et sa position relative au conteneur. Voici un exemple :

                public MainPage()
                {
                    InitializeComponent();
                 
                    Touch.FrameReported += new TouchFrameEventHandler(Touch_FrameReported);
                 
                }
                 
                void Touch_FrameReported(object sender, TouchFrameEventArgs e)
                {
                     var tp = e.GetPrimaryTouchPoint(null);
                 
                     switch(tp.Action)
                     {
                           case TouchAction.Down:
                           break;
                 
                           case TouchAction.Move:
                           break;
                 
                           case TouchAction.Up:
                           break;
                 
                           default:
                           break;
                     }
                }

                Sur un projet en cours, j'ai du développé un contrôle ContactButton permettant de résoudre cette problématique. L'objectif d'un point de vue utilisateur était d'activer certains menus lorsque deux boutons à la fois étaient cliqués. Ce contrôle est basé sur l'API native multiTouch de Silverlight et sur les classes d'AutomationPeer. L'idée est de diffuser un événement lorsque plusieurs boutons d'un même groupe sont cliqués en même temps. Il est possible d'écouter un événement static de la classe ContactButton afin de récupérer le groupe de bouton cliqué. Au moins deux boutons d'un même groupe doivent être appuyés en même temps pour que la classe diffuse l'événement. Il est également possible d'écouter un groupe spécifique. Voici comme utiliser ces événements :

                public TestTwinBehavior()
                {
                    InitializeComponent();
                 
                    tcb1.ContactSelectionChanged += new EventHandler(tcb1_ContactSelectionChanged);
                 
                    ContactButton.ContactGroupChanged += new EventHandler(ContactButton_ContactGroupChanged);
                }
                 
                void ContactButton_ContactGroupChanged(object sender, ContactButtonGroupEventArgs e)
                {
                    Debug.WriteLine(e.ContactButtonGroupName);
                }
                 
                void tcb1_ContactSelectionChanged(object sender, Tweened.WP7.Controls.ContactButtonGroupEventArgs e)
                {
                    foreach (ButtonBase item in e.ContactedButtons)
                    {
                        Debug.WriteLine(item.Name);
                    }
                }

                Je mets à disposition un projet et la dll qui l'accompagne. En outre, le contrôle LayoutGridSelector est fourni avec des améliorations concernant le support des thèmes visuels Windows Phone 7.

                TooltipHelper [Custom Control]

                Aucun commentaire

                  L'une des problématiques que l'on rencontre lorsque l'on travaille avec Silverlight, c'est la difficulté de modifier le style de certains contrôles. L'un des exemples les plus flagrants est la customisation du contrôle ToolTip. De plus dans environnement Silverlight, les ToolTip possèdent moins de capacités que sous WPF. Il est par exemple impossible de définir un délai pour l'apparition et la disparition du ToolTip. Je mets donc en téléchargement un contrôle dont le but est de simplifier la mise en oeuvre d'un ToolTip simple à configurer. Ce contrôle a pour but d'accompagner des zones de saisie, de sélection ou tout autre contrôle afin de signifier à l'utilisateur leur mode de fonctionnement.

                  Il est possible de :

                  - Modifier le style et le template du ToolTip directement dans Blend ou d'en créer une copie.

                  - d'écouter les événements Opened et Closed

                  - de définir un en-tête, une corps et un pied

                  - de définir un délai d'apparition / disparition

                  - de définir le mode de placement

                  La dll est téléchargeable Ici.

                  LayoutGridSelector WP7 [Custom Control]

                  Aucun commentaire

                    Dans un projet Windows Phone 7 récent, nous avons eu besoin de simplifier l'une des interface utilisateur. Cette dernière permet à un utilisateur de déterminer le nombre de ligne et de colonnes qu'un diaporama peut afficher sur une surface de la dimension d'un Windows Phone (à savoir 800*480). Au départ deux TextBox étaient placés dans une grille et possédaient tous deux une liaison de donnée à deux voies vers des propriété d'un Vue-Modèle. Ce mécanisme n'étant pas des plus simple notamment sur téléphone mobile : click sur TextBox, effacement de l'ancienne saisie et saisie d'un nouveau chiffre et sauvegarde pour finir, j'ai simplifié au maximum cette interface graphique en créant un contrôle qui fait permet à l'utilisateur de définir directement un agencement de manière intuitive.

                    Le contrôle peut définir :

                    1. Un nombre maximum de colonnes et de lignes sélectionnables (bindable)
                    2. La cellule ou la ligne ou la colonne sélectionnée (bindable)
                    3. Diffuse un événement lors de la sélection
                    4. Utilise en interne des ToggleButton doint vous pourrez modifier le style à loisir via le menu Edit Additionnal Style / Template
                    5. De plus, celui-ci fonctionne également pour une application Silverlight classique
                    6. J'ai pris en compte toutes les interactions utilisateurs possible du point de vue tactile pour éviter les erreurs de sélection ou d'affichage

                    La dll contenant le controle est téléchargeable ici et vous pouvez voir le résultat ci-dessous :

                    Install Microsoft Silverlight

                    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()]

                      Next Page »