# Tuesday, April 28, 2009

France Télévisions – L’info en vidéo

Encore une nouvelle version pour l’application L’info en vidéo.

L’accès aux journaux des régions simplifié par l’affichage de celles-ci sur une carte de France :-)

image

Et un accès direct aux différents journaux de chaque région :

image

#    Comments [0] |
# Friday, April 24, 2009

Gestion de la mémoire sous Silverlight / .Net

On m’a interrogé récemment sur la libération de la mémoire des objets en silverlight. Je vais essayer de synthétiser ici ce que j’ai répondu.

Silverlight réagi exactement de la même manière que le framework .Net. La mémoire est gérée par un garbage collector (ramasse-miettes) qui libère les allocations mémoires des objets une fois qu’ils ne sont plus référencés. 

L’application de test

Voila le principe de mon application de test. Au clic sur un bouton, mon application ajoute un usercontrol dans l’arbre visuel et au clic sur un autre bouton, je l’enlève. La seule référence vers mon contrôle est faite par l’arbre graphique. Dans le constructeur de mon usercontrol, j’incrémente un compteur qui m’indique le nombre d’instances en cours et dans le destructeur je le décrémente. Ce compteur est affiché par binding dans l’interface graphique.

Petite parenthèse pour le fonctionnement de l’application, la destruction des objets ne se passe pas dans le thread graphique, pour que la mise a jour du binding fonctionne, je lève l’événement PropertyChanged de AppController par le SynchronizationContext récupéré du thread graphique.

Contrôle sans références

image

A l’ajout de mon usercontrol, on note bien l’incrémentation du compteur.

image

Je clic sur le bouton Remove et je vois que mon compteur est toujours à 1. Le garbage collector ne passe pas tout de suite. Il y a un algorithme qui détermine quand le garbage collector doit passer sur les objets. Pour le besoin de mon application, je vais forcer l’appel au garbage collector au clic sur le bouton Force GC. Il faut noter que c’est une mauvaise pratique que de vouloir appeler soit même de garbage collector. GC.Collect() ne doit être appelée que si vous en avez réellement l’utilité. Un appel trop fréquent peut dégrader les performances de votre application.

image

Le clic sur Force GC ramène bien le compte de référence à 0.

Contrôle s’abonnant à un événement

Comme je vous le précisait dans le fonctionnement de mon application, je ne maintient aucune référence vers mes usercontrol, je n’ai théoriquement rien qui va empêcher le garbage collector de passer. Cependant le contrôle lui même peut faire qu’il ne sera pas libéré. Ici ma classe AppController fourni un événement auquel mes contrôles peuvent s’abonner. MyControl2 va s’abonner à cet événement. Au clic sur le bouton Raise Test event, vous verez que la couleur du usercontrol change.

image

Si vous ajoutez le usercontrol MyControl2, que vous l’enlevez et que vous forcez le garbage collector, vous verrez que l’instance ne se libère pas. Que se passe-t-il ?
Lorsque le contrôle s’est abonné à l’événement de AppController, il a créé un EventHandler (une référence vers une fonction) et l’a ajouté à la liste des fonctions à appeler lorsque l’événement est levé. Un événement n’est rien d’autre qu’une liste de références qui pointe vers les fonctions à appeler. Pour que notre contrôle se libère, il faut donc supprimer cette référence. Un clic sur Clear Test event list demande à AppController de vider sa liste d’événement, un clic sur Force GC remettra les compteurs à 0.

IDisposable

Une bonne pratique pour gérer la libération de ressources internes et le désabonnement d’évenement est d’implémenter l’interface IDisposable. Cette interface vous demande d’implémenter la fonction Dispose dans laquelle vous pouvez remettre à null les références externes, vous désabonnez d’événements, etc…
Le usercontrol MyControl3 implémente cette interface. Lorsque le bouton Remove First Item est cliqué, mon application vérifie si le contrôle à enlever implémente IDisposable et l’appel le cas échéant.

image

Les clics successifs sur Add MyControl3, Remove First Item et Force GC incrémentent et décrémentent bien notre compteur de références.

WeakReference et proxy d’événement

Si vous ne maitrisez pas la logique d’ajout suppression des contrôles, vous ne pouvez pas toujours appeler la IDisposable.Dispose. (A noter que ce n’est pas parce qu’un contrôle n’est plus dans l’arbre d’affichage qu’il faut nécessairement le disposer. Un tabcontrol ajoute et enlève chaque tab de l’abre graphique.)

La classe WeakReference permet de maintenir une référence “qui ne compte pas” du point de vue du garbage collector. La propriété Target de la WeakReference vaut null quand le garbage collector est passé et renvoi l’instance de l’objet sinon.

Ici pour gérer notre abonnement à l’événement, nous allons utiliser ces WeakReference pour pouvoir quand même être libéré. La fonction à appeler en retour de l’événement est : MyControl4.Instance_TestEvent. Cette fonction est privée. WeakListener est une classe interne à MyControl4 et va donc pouvoir appeler cette fonction privée. C’est WeakListener.Instance_TestEvent que nous allons abonner à l’événement de AppController. Lors de l’appel de l’événement, elle va vérifier que sa WeakReference vers MyControl4 est toujours valide, si tel est le cas, cette fonction va transmettre l’appel vers la fonction MyControl4.Instance_TestEvent, sinon elle se désabonne de l’appelant et pourra donc aussi être collecté par le garbage collector.

    public partial class MyControl4 : UserControl

    {

 

        private class WeakListener

        {

            public WeakListener(MyControl4 target)

            {

                weakReference = new WeakReference(target);

            }

 

            private readonly WeakReference weakReference;

 

            public void Instance_TestEvent(object sender, EventArgs e)

            {

                MyControl4 ctrl = (MyControl4)weakReference.Target;

 

                if (ctrl != null)

                {

                    ctrl.Instance_TestEvent(sender, e);

                }

                else

                {

                    ((AppController)sender).TestEvent -= this.Instance_TestEvent;

                }

            }

        }

 

        void Instance_TestEvent(object sender, EventArgs e)

        {

            this.LayoutRoot.Background = new SolidColorBrush(GetRandomColor());

        }

 

       

    }

image

Un clic sur Add MyControl4, Remove et Force GC vous donnera un compte de référence de MyControl4 à 0. Il faut noter qu’à ce stade, MyControl4 a été libéré mais le WeakListener est toujours présent en mémoire. Sa libération ne sera effectué que lorsque l’événement sera appelé, la classe s’autodésabonnera toute seule de l’événement et pourra est collectée.

WeakEventList

J’ai ajouté dans slextensions une WeakEventList permettant de gérer une liste d’événements basés sur des WeakReference. A la place de stocker les événements par le processus standard de .Net, nous créons une liste de WeakReference directement sur les delegates abonnés.

Voila la déclaration de l’événement de ma class AppController.

        private WeakEventList<EventArgs> weakTestEvent;

 

        private event EventHandler<EventArgs> testEvent;

        public event EventHandler<EventArgs> TestEvent

        {

            add

            {

                if(UseWeakEvent)

                {

                    weakTestEvent.Event += value;

                }

                else

                {

                    testEvent += value;                   

                }

            }

            remove

            {

                if (UseWeakEvent)

                {

                    weakTestEvent.Event -= value;

                }

                else

                {

                    testEvent -= value;

                }

            }

        }

image

En cochant la case UseWeakEvent et en reproduisant le cas qui posait problème au début de ce post ( Add MyControl2, Remove, Force GC), on voit que la référence est bien libérée.

Vous trouvez plusieurs autres manières de créer des “weakevent” sur internet. Celle-ci est la seule qui marche en silverlight pour des appels à des fonctions privées. De nombreuses utilisent la réflexion pour pouvoir accéder à la fonction finale à appeler par l’événement, ceci n’est pas permit en silverlight si la déclaration est privée. Le mode MediumTrust de silverlight nous en empêche.

#    Comments [7] |
# Thursday, April 23, 2009

Silverlight Extensions

Comme vous le savez les 2 projets Silverlight Extensions et Silverlight Contrib sont en cours de fusion :

image

- Silverlight Extensions et Silverlight Contrib

- The Future of Silverlight Contrib

- MVPs Shine Silverlight Together at MVP Global Summit!

 

Pour l’occasion nous avons mis en place le nouveau site de Silverlight Extensions sous N2 CMS et le futur Blog de Silverlight Extensions sous BlogEngine.NET.

* En attendant que la fusion avance, n’oubliez pas d’aller sur les sites des deux projets Silverlight Extensions et Silverlight Contrib.

#    Comments [0] |
# Tuesday, April 21, 2009

France Télévisions – L’info en vidéo

Nouvelle version de l’application Silverlight L’info en vidéo.

Vous pouvez maintenant retrouver les vidéos des différentes émissions !

image

image

#    Comments [0] |

UCAYA Tour Next 7

::Au Sénégal::

Ucaya au sénégal

Encore le Sénégal :-)

#    Comments [0] |
# Friday, April 17, 2009

Silverlight Extensions et Silverlight Contrib

Comme nous vous le disions dans un précédent post et comme vous avez pu le voir sur les sites respectifs des deux projets, les 2 projets sont en cours de fusion. Le nom du projet résultant de cette fusion a été publié par Page Brooks sur un son blog. Un vote était organisé et c’est le nom silverlight extensions qui a remporté la majorité des voix. Retrouvez le post de Page Brooks

image[1] 

En attendant que la fusion avance, n’oubliez pas d’aller sur le site web des deux projets. slextensions et silverlight contrib

#    Comments [0] |
# Tuesday, April 14, 2009

Utilisation des behaviors pour localiser une application silverlight ou WPF

Il y a quelques temps déjà, j’ai ajouté à slextensions une classe permettant de localiser simplement une application à partir d’attachedproperty. La mode étant aux behaviors, j’ai rajouté une classe permettant cette localisation à la branche SL3 de slextensions. Cette classe possède 4 propriétés :

  • Key : la clé de la ressource provenant de votre ResourceManager
  • PropertyName : Le nom de la propriété à localiser
  • ResourceManagerKey : La clé avec laquelle retrouver le ResourceManager dans les ressources de l’application
  • ConvertXaml : Si la resource contient du Xaml, permet d’instancier ce xaml par le XamlReader.

L’utilisation est très simple voici un exemple de localisation d’un texte

            <TextBlock  >

                <i:Interaction.Behaviors>

                    <SLExtensions_Interactivity:Localize Key="WelcomeMessage" PropertyName="Text" ResourceManagerKey="MyResource" />

                </i:Interaction.Behaviors>

            </TextBlock>

et d’un ContentControl

            <ContentControl Height="121" Width="244" Canvas.Left="25" Canvas.Top="206" >

                <i:Interaction.Behaviors>

                    <SLExtensions_Interactivity:Localize Key="Welcome" PropertyName="Content" ResourceManagerKey="MyResource" ConvertXaml="True"/>

                </i:Interaction.Behaviors>

            </ContentControl>

Le code de la classe :

using System;

using System.Linq;

using System.Net;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Documents;

using System.Windows.Ink;

using System.Windows.Input;

using System.Windows.Media;

using System.Windows.Media.Animation;

using System.Windows.Shapes;

using Microsoft.Expression.Interactivity;

using System.Resources;

using System.Globalization;

using System.Windows.Markup;

using SLExtensions.Globalization;

 

namespace SLExtensions.Interactivity

{

    public class Localize : Behavior<FrameworkElement>

    {

 

        private string key;

 

        public string Key

        {

            get

            {

                return this.key;

            }

 

            set

            {

                if (this.key != value)

                {

                    this.key = value;

                    Refresh();

                }

            }

        }

 

        private bool convertXaml;

 

        public bool ConvertXaml

        {

            get

            {

                return this.convertXaml;

            }

 

            set

            {

                if (this.convertXaml != value)

                {

                    this.convertXaml = value;

                    Refresh();

                }

            }

        }

 

 

        private DependencyProperty dependencyProperty;

 

        private string propertyName;

 

        public string PropertyName

        {

            get

            {

                return this.propertyName;

            }

 

            set

            {

                if (this.propertyName != value)

                {

                    this.propertyName = value;

                    this.dependencyProperty = null;

                    Refresh();

                }

            }

        }

 

        private string resourceManagerKey;

 

        public string ResourceManagerKey

        {

            get

            {

                return this.resourceManagerKey;

            }

 

            set

            {

                if (this.resourceManagerKey != value)

                {

                    this.resourceManagerKey = value;

                }

            }

        }

 

        private void Refresh()

        {

            if (AssociatedObject == null

                || string.IsNullOrEmpty(propertyName)

                || string.IsNullOrEmpty(Key)

                || string.IsNullOrEmpty(ResourceManagerKey))

                return;

 

            if (dependencyProperty == null)

            {

                Type t = AssociatedObject.GetType();

                var field = t.GetField(propertyName + "Property", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);

                if (field != null)

                {

                    dependencyProperty = field.GetValue(null) as DependencyProperty;

                }

            }

 

            if (dependencyProperty != null

                && Application.Current != null)

            {

                ResourceManager resourceManager = null;

                var obj = Application.Current.Resources[ResourceManagerKey];

                if (obj is ResourceLoader)

                {

                    resourceManager = ((ResourceLoader)obj).GetResourceManager();

                }

                else

                {

                    resourceManager = obj as ResourceManager;

                }

 

                var resourceValue = resourceManager.GetObject(Key, CultureInfo.CurrentCulture);

                if (convertXaml && resourceValue is string)

                {                   

                    resourceValue = XamlReader.Load((string)resourceValue);

                }

                AssociatedObject.SetValue(dependencyProperty, resourceValue);

            }

        }

 

        protected override void OnAttached()

        {

            Refresh();

            base.OnAttached();

        }

 

        protected override void OnDetaching()

        {

            base.OnDetaching();

        }

    }

}

#    Comments [0] |

Concours du jeu des caméléons

Le concours du jeu des caméléons de bonne année 2009 s’est achevé le 31 mars.

ucayahny2009-RankingCapture

Notre grand vainqueur est originaire des Caraïbes. Pour l’occasion j’ai tenu à lui remettre son lot en main propre :-)

20090405-ucayahny2009-winner 20090405-ucayahny2009-winner (2)

20090405-ucayahny2009-winner (3) 20090405-ucayahny2009-winner (4)

20090405-ucayahny2009-winner (5)

Il a nettement plus la classe maintenant non ?! ;-)

Merci d’avoir participé !

#    Comments [2] |
# Tuesday, April 07, 2009

Les behavior en Silverlight 3 - MapState behavior

Exemple

Les behaviors introduits par blend 3 servent à étendre les fonctionnalités d’un contrôle à l’aide d’une nouvel élément que l’on peut poser graphiquement dans l’interface de blend. L’ajout de comportement n’est pas nouveau, c’est utilisé dans de nombreux projets (par exemple, la navigation deepzoom de slextensions est géré de cette manière), mais ici, l’intégration à blend est plutôt pratique et permet à un intégrateur de gérer du comportement sans écrire de code.

La classe MapState me permet de mapper l’état d’une propriété d’un contrôleur à un VisualState posé sur mon contrôle.

Ma classe contrôleur de test n’a rien de particulier. Voici sa définition.

    public class ApplicationController : INotifyPropertyChanged

    {

 

        #region INotifyPropertyChanged Members

 

        public event PropertyChangedEventHandler PropertyChanged;

 

        protected void OnPropertyChanged(string propertyName)

        {

            if (PropertyChanged != null)

            {

                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));

            }

        }

 

        #endregion

 

        private TestStates state;

        public TestStates State

        {

            get { return state; }

            set

            {

                if (state != value)

                {

                    state = value;

                    OnPropertyChanged(StatePropertyName);

                }

            }

        }

 

        public const string StatePropertyName = "State";

 

    }

J’instancie ce contrôleur en l’injectant dans les ressources de l’application

<Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

            x:Class="TestBehavior.App"

            xmlns:controllers="clr-namespace:TestBehavior.Controllers"

            >

    <Application.Resources>

        <controllers:ApplicationController x:Key="appController" />

    </Application.Resources>

</Application>

Je rajoute les références qui vont bien à mon projet

image

Je peux maintenant rajoute mon behavior à l’aide de blend

image

Je remplie le mapping et la source de mon behavior avec blend

image

Le morceau de code qui change l’état du contrôleur.

        private void Button_Click(object sender, RoutedEventArgs e)

        {

            ApplicationController appController = Application.Current.Resources["appController"] as ApplicationController;

            appController.State = (TestStates) ((1 + (int)appController.State) % 3);

        }

Le code

Ma classe MapState hérite d’une classe ancêtre Behavior que vous trouverez dans l’assembly Microsoft.Expression.Interactivity.dll qui se trouve dans le dossier Libraries/Silverlight de Blend 3.

J’ai déclaré une propriété Source qui me sert à récupérer l’objet métier qui va me donner mon état et une propriété Property me donnant le nom de la propriété d’état. Ma source doit bien sur implémenter INotifyPropertyChanged pour que MapState soit notifiée du changement de propriété.
J’ai également une collection de MapStateMapping qui me fournisse un couple Valeur / VisualState.
Au changement de valeur sur ma source, je n’ai plus qu’à comparer par rapport aux valeurs de mapping est à déclancher le visualstate sur mon élément associé.

Le code complet de la classe :

namespace SLExtensions.Interactivity

{

    [ContentProperty("Mappings")]

    public class MapState : Behavior<FrameworkElement>

    {

        public MapState()

        {

            Mappings = new List<MapStateMapping>();

        }

 

        #region Source

 

        private PropertyInfo propertyInfo = null;

 

        public object Source

        {

            get

            {

                return (object)GetValue(SourceProperty);

            }

 

            set

            {

                SetValue(SourceProperty, value);

            }

        }

 

        /// <summary>

        /// Source depedency property.

        /// </summary>

        public static readonly DependencyProperty SourceProperty =

            DependencyProperty.Register(

                "Source",

                typeof(object),

                typeof(MapState),

                new PropertyMetadata((d, e) => ((MapState)d).OnSourceChanged((object)e.OldValue, (object)e.NewValue)));

 

        /// <summary>

        /// handles the SourceProperty changes.

        /// </summary>

        /// <param name="oldValue">The old value.</param>

        /// <param name="newValue">The new value.</param>

        private void OnSourceChanged(object oldValue, object newValue)

        {

            propertyInfo = null;

            INotifyPropertyChanged notifyingObject = oldValue as INotifyPropertyChanged;

            if (notifyingObject != null)

            {

                notifyingObject.PropertyChanged -= new PropertyChangedEventHandler(notifyingObject_PropertyChanged);

            }

            notifyingObject = newValue as INotifyPropertyChanged;

            if (notifyingObject != null)

            {

                notifyingObject.PropertyChanged += new PropertyChangedEventHandler(notifyingObject_PropertyChanged);

            }

            if (newValue != null && !string.IsNullOrEmpty(Property))

            {

                propertyInfo = newValue.GetType().GetProperty(Property);

            }

            RefreshState();

        }

 

        #endregion Source

 

        #region Property

 

        public string Property

        {

            get

            {

                return (string)GetValue(PropertyProperty);

            }

 

            set

            {

                SetValue(PropertyProperty, value);

            }

        }

 

        /// <summary>

        /// Property depedency property.

        /// </summary>

        public static readonly DependencyProperty PropertyProperty =

            DependencyProperty.Register(

                "Property",

                typeof(string),

                typeof(MapState),

                new PropertyMetadata((d, e) => ((MapState)d).OnPropertyChanged((string)e.OldValue, (string)e.NewValue)));

 

        /// <summary>

        /// handles the PropertyProperty changes.

        /// </summary>

        /// <param name="oldValue">The old value.</param>

        /// <param name="newValue">The new value.</param>

        private void OnPropertyChanged(string oldValue, string newValue)

        {

            if (Source == null)

                propertyInfo = null;

            else

                propertyInfo = Source.GetType().GetProperty(newValue);

 

            RefreshState();

        }

 

        #endregion Property

 

        public List<MapStateMapping> Mappings { get; private set; }

 

 

 

        void notifyingObject_PropertyChanged(object sender, PropertyChangedEventArgs e)

        {

            if(e.PropertyName == Property)

                RefreshState();

        }

 

        private void RefreshState()

        {

            // Do not refresh at desing time

            if (!HtmlPage.IsEnabled)

                return;

 

            if (Source != null && propertyInfo != null )

            {

                object propValue = propertyInfo.GetValue(Source, null);

 

                if (propValue == null)

                {

                    var nullMappings = from m in Mappings

                                       where m.Value == null

                                       || ((m.Value is string) && string.Empty.Equals(m.Value))

                                       select m.StateName;

 

                    try

                    {

                        SLExtensions.Controls.Animation.VisualState.GoToState(AssociatedObject, UseTransitions, nullMappings.ToArray());

                    }

                    catch

                    {

                    }

                }

                else

                {

                    MapStateMapping mapping = null;

                    foreach (var item in Mappings)

                    {

                        var value = item.Value;

                        if (value != null)

                        {

                            if (object.Equals(value, propValue))

                            {

                                mapping = item;

                                break;

                            }

 

                            if (propValue is IConvertible && value is IConvertible)

                            {

                                try

                                {

                                    var val2 = Convert.ChangeType(propValue, value.GetType(), CultureInfo.InvariantCulture);

                                    if (object.Equals(val2, value))

                                    {

                                        mapping = item;

                                        break;

                                    }

                                }

                                catch

                                {

                                }

 

                                try

                                {

                                    var val2 = Convert.ChangeType(value, propValue.GetType(), CultureInfo.InvariantCulture);

                                    if (object.Equals(val2, propValue))

                                    {

                                        mapping = item;

                                        break;

                                    }

                                }

                                catch

                                {

                                }

                            }

                        }

                    }

 

                    if (mapping != null)

                    {

                        try

                        {

                            SLExtensions.Controls.Animation.VisualState.GoToState(AssociatedObject, UseTransitions, mapping.StateName);

                        }

                        catch

                        {

                        }

                    }

                }

            }

        }

 

        /// <summary>

        ///

        /// </summary>

        public static readonly DependencyProperty UseTransitionsProperty = DependencyProperty.Register("UseTransitions", typeof(bool), typeof(MapState), new PropertyMetadata(true));

 

        /// <summary>

        /// True if transitions should be used for the state change.

        /// </summary>

        public bool UseTransitions

        {

            get { return (bool)this.GetValue(MapState.UseTransitionsProperty); }

            set { this.SetValue(MapState.UseTransitionsProperty, value); }

        }

 

        /// <summary>

        /// Hooks up necessary handlers for the state changes.

        /// </summary>

        protected override void OnAttached()

        {

            base.OnAttached();

 

            FrameworkElement element = this.AssociatedObject;

            if (element != null)

            {

                element.Loaded += new RoutedEventHandler(element_Loaded);

 

                RefreshState();

            }

            else

            {

                Dispatcher.BeginInvoke(delegate

                {

                    this.OnAttached();

                });

            }

        }

 

        void element_Loaded(object sender, RoutedEventArgs e)

        {

            RefreshState();

        }

    }

}

Les sources

En attendant de publier tout ça sur slextensions, vous pouvez télécharger le projet ici: 
Vous pouvez retrouver d’autres behavior sur le site de la gallery expression : http://gallery.expression.microsoft.com/

#    Comments [0] |
# Friday, April 03, 2009

silverlight 3 beta et silverlight 2 sur la même machine de dev

logo Travailler avec des versions beta de logiciels est parfois compliqué. Que faire avec les outils ? Est-ce que je les installe sur ma machine de développement, est-ce que je les installe sur une machine virtuelle ?

Vous trouverez ici : http://blogs.msdn.com/amyd/archive/2009/03/18/switching-from-silverlight-3-tools-to-silverlight-2-tools.aspx un petit script permettant de changer simplement la version des Silverlight tools utilisés par visual studio.

#    Comments [0] |