Bienvenue à Blogs CodeS-SourceS Identification | Inscription | Aide

Frédéric Hamel

Assert.Success();

[WPF] SplashScreen pour WPF en .NET 3.5 sp1

Le démarrage des applications .NET et notamment WPF surtout à froid peut parfois être un problème car il peut prendre un certain temps. De nombreuses applications utilisent aussi le démarrage de l'application pour faire une partie des initialisations du programme, ce qui n'améliore bien évidement pas le temps de démarrage. L'idée classique est donc d'utiliser un SplashScreen qui va s'afficher très vite et qui va afficher une image qui aidera l'utilisateur à patienter.

Faire cela n'est généralement pas si simple, mais heureusement le SP1 de .NET 3.5 nous apporte une nouvelle classe SplashScreen.

Mise en place via VS 2008 SP1

Pour en profiter rapidement, il suffit de créer un .PNG avec l'image que l'on veut voir afficher au démarrage et de l'inclure dans le projet de votre application WPF. Ensuite, dans la fenêtre de propriété, il suffit de mettre la Build Action à Splash Screen.

Et c'est tout! Lancer l'application, et on voit l'image s'afficher.

La 1ère impression est que ça marche bien et que le splashscreen s'ouvre et se ferme automatiquement sans rien faire. Mais on voit aussi que la fenêtre de l'application passe devant quand elle s'ouvre et que le splashscreen repasse devant pour se fermer. Pas terrible donc...

 

Mise en place à la main

La méthode via VS 2008 SP1 est très simple et fonctionne imédiatement, mais le résultat n'est pas complètement optimal. Heureusement, on peut aussi le faire à la main.

Pour cela il faut toujours ajouter le .PNG au projet, mais cette fois il faut le mettre en tant que Resource. Et il faut un peu modifier le App.xaml et le App.xaml.cs.

Dans le App.xaml il faut enlever l'attribut StartUpUri car on va afficher nous-mêmes la fenêtre principale.

Dans le App.xaml.cs on rajoute le code suivant:

public App()
{
    var splashScreen = new SplashScreen("SplashScreen.png");
    splashScreen.Show(false);

    var mainWindow = new Window1();
    mainWindow.Show();

    splashScreen.Close(TimeSpan.Zero);
    
}

On crée l'objet SplashScreen avec le nom du .PNG et on fait Show. Le paramètre false signifie que l'on va fermer le splashscreen nous-mêmes. Si on le met à true, il va se fermer de lui-même à la fin du constructeur de App.

Ensuite, on crée la fenêtre principale et on la montre.

Et finalement on ferme le splashscreen avec Close et un timeSpan à zéro, ce qui permet d'avoir une fermeture immédiate et d'éviter ainsi qu'il repasse devant la fenêtre principale. Si on veut par contre un effet et voir le splashscreen disparaître petit à petit, on peut mettre TimeSpan.FromSeconds(1) qui va faire durer l'effet pendant une seconde.

 

Conclusion

Voilà une classe qui était attendue et qui est arrivée avec le SP1 de .NET 3.5 et VS 2008. On regrettera que la classe ne permette d'afficher que des PNG, pas d'image animée ni rien d'autre, et qu'il y ait un petit effet disgracieux quand on l'utilise directement via la BuildAction. J'ai essayé cette classe avec compositeWPF (Prism) et cela permet d'avoir le splashscreen pendant le chargement et l'initialisation des modules, ce qui n'est pas si mal. De plus, elle est quand même vraiment facile à mettre en oeuvre. On trouvera d'autres détails concernant cette classe sur le post what's new in WPF 3.5 sp1: Splash Screen to improve perceived startup perf.

Happy programming!

[Back to basics] Encapsulation, bien designer ses classes dans le monde réel

Écrire du code de qualité dans un langage ne sous-entend pas seulement maîtriser sa syntaxe, mais aussi comprendre la philosophie et les méthodologies qui sont derrière. C# est un langage orienté objet, et en tant que tel, les principes et concepts de la programmation orientée objet s'appliquent à lui.

Le design d'objets est parfois un challenge redoutable. L'exercice peut se révéler périlleux, surtout si les bases ne sont pas parfaitement maîtrisées. Je me propose ici de discuter d'encapsulation sur des cas concrets, pour que ce concept soient utilisé au mieux dans nos programmes.

Introduction et définition

L'encapsulation est un concept qui vise à protéger l'intégrité des données d'un objet et à en masquer les détails d'implémentation. En effet, un objet va avoir une ou plusieurs responsabilités : c'est à lui de s'assurer que la charge qui lui a été confiée se passe bien et de faire en sorte que ses clients ne soient pas impactés par la manière dont il le fait.

Prenons par exemple un objet responsable d'un compte bancaire. L'objet devra exposer, par exemple, les méthodes de débit, de crédit et d'obtention du solde du compte. L'objet doit s'assurer qu'aucune autre opération que celles prévues ne soit faite sur le compte, et que si pour des raisons x ou y l'objet doit changer la manière dont est géré le compte, cela soit transparent pour les utilisateurs du compte.

Exemple de la classe Point

Démarrons par un cas simple. Je m'inspire ici d'un excellent exemple de Simon Robinson.

public class Point
{
    public double X;
    public double Y;
}

Ici la classe Point expose directement ses données : aucune encapsulation n'est réalisée.

Pour améliorer cela, on va classiquement rendre les membres privés et les exposer via des propriétés.

public class Point
{
    private double x;
    public double X{
        get { return x; }
        set { x = value; }
    }

    private double y;
    public double Y
    {
        get { return y; }
        set { y = value; }
    }
}

Grâce à ces propriétés, les données de la classe Point sont protégées, et on a mis un niveau d'indirection entre elles et le client. Ce qui permettra par exemple si on le souhaite d'ajouter du code dans le set ou le get en fonction des besoins de l'application (par exemple du log, des validations, la notification du changement des propriétés, etc.).

Regardons maintenant quelques cas d'utilisation de cette classe.

var point = new Point();

//Translate the point
point.X = point.X + 10;
point.Y = point.Y + 10;

//Move the point
point.X = 5;
point.Y = 5;

Pour faire une translation du point, je prends la valeur de chaque axe et je leur ajoute les "offset" désirés. Pour déplacer un point, je lui affecte simplement les nouvelles valeurs.

Le problème ici vient du fait que l'objet ne masque pas vraiment son implémentation interne. En effet, le client récupère les données à l'exérieur de l'objet et les remet dedans une fois le traitement fini. L'objet n'a aucun moyen de savoir si la translation a été faite selon les règles attendues ou pas. Par rapport à la définition posée au début, on voit que les détails de l'implémentation ne sont pas vraiment cachés au client malgré les propriétés, et que l'intégrité des données n'est pas du tout gérée. D'ailleurs, si l'on regarde la syntaxe .NET 3.5 de la classe Point, il est troublant de voir qu'on a l'impression d'être sur l'implémentation originale.

public class Point
{
    public double X { get; set; }
    public double Y { get; set; }
}

Voila par exemple comment la classe Point pourrait être mieux encapsulée :

public class Point
{
    public double X { get; private set; }
    public double Y { get; private set; }

    public Point(double x, double y)
    {
        X = x;
        Y = y;
    }

    public void Translate(Point point) 
    {
        X += point.X;
        Y += point.Y;
    }

    public void Move(Point point)
    {
        X = point.X;
        Y = point.Y;
    }
}

Grâce au mot clé "private", on rend inaccessible la modification des data de l'extérieur. L'initialisation de l'objet est faite via le constructeur et la modification se fait via les méthodes. L'objet Point offre ainsi de meilleures garanties d'intégrité de ses données et ses détails d'implémentation sont mieux masqués.

Oui mais...

Une fois arrivé là, on se demande pourquoi toutes les classes Point ne sont pas implémentées comme cela ! En réalité, un point peut être considéré quasiment comme un type de base, une donnée en elle-même. De plus, il est difficile voire impossible d'imaginer à l'avance tous les traitements que l'on souhaite faire sur un point. Pour ajouter à la difficulté, les points sont des petits objets qui risquent d'être instanciés de très nombreuses fois et manipulés de manière intensive : pour des raison d'optimisation, l'objet Point est souvent implémenté sous forme de structure et ses membres sont directement accessibles pour manipulation.

Généralement, ce n'est pas le cas pour un objet business : l'objet Compte par exemple doit être correctement encapsulé si la banque ne veut pas avoir de sérieux problèmes.

Exemple des collections

Prenons l'exemple suivant : une banque possède un ensemble de comptes. Elle permet de consulter les comptes et de d'ouvrir un compte si tout est valide pour ce compte.

public class Bank
{
    public List<Account> Accounts { get; private set; }

    public Bank(){
        Accounts = new List<Account>();
    }

    public void OpenAccount(Account account) 
    {
        if (Validate(account))
        {
            Accounts.Add(account);
        }
    }

    private bool Validate(Account account) { 
        //Do something
        return true;
    }
}

public class Account
{
}

Ici, il est donc possible de voir les comptes via la propriété Accounts et d'ouvrir un compte grâce à OpenAccount (qui va en plus s'assurer que tout est ok avant de l'ajouter à la liste des comptes de la banque).

Le design de cette classe est catastrophique, car il existe une backdoor qui permet d'ajouter un compte sans que le code de validation ne soit appelé.

//Breaking business rules
bank.Accounts.Add(new Account());
//Instead of
bank.OpenAccount(new Account());

Il faut toujours se méfier quand on expose directement une collection, et se poser la question de si c'est vraiment ce que l'on veut ou pas.

Une solution classique à ce problème est d'exposer un énumérateur qui va offrir un accès en lecture seule à la collection.

public class Bank
{
    private List<Account> accounts = new List<Account>();
    public IEnumerable<Account> Accounts
    {
        get
        {
            return accounts.AsEnumerable();
        }
    }

    public void OpenAccount(Account account) 
    {
        if (Validate(account))
        {
            accounts.Add(account);
        }
    }

    public bool Validate(Account account) { 
        //Do something
        return true;
    }
}

Ici, le fait d'exposer un énumerateur va permettre de donner de la visibilité sur la collection, sans pour autant compromettre son encapsulation. De plus, grâce aux méthodes d'extension de .NET 3.5, exposer un énumerateur est vraiment simple. Si l'on n'a pas d'accès aux méthodes d'extension, on peut toujours utiliser le mot clé "yield".

Exemple ObservableCollection

En WPF par exemple, il n'est pas rare d'avoir à utiliser une ObservableCollection pour que les contrôles synchronisés dessus puissent se mettre à jour automatiquement si des objets sont enlevés ou rajoutés à la collection. En général, on est confrontés au même problème que pour les collections dans le cas précédent. Sauf qu'en plus, l'ObservableCollection ne peut pas vraiment être exposée au framework WPF via un enumerator, car on perd alors tout le côté observable de la collection. Heureusement, le framework .NET propose une solution :

public class Bank
{
    private ObservableCollection<Account> accounts 
        = new ObservableCollection<Account>();

    public ReadOnlyObservableCollection<Account> Accounts 
    {
        get {
            return new ReadOnlyObservableCollection<Account>(accounts);
        }
    }
}

La collection est ainsi protégée et les fonctionalités "Observable" sont préservées.

Remarque : si l'on a besoin de meilleures performances, il n'est pas nécessaire de recréer une nouvelle ReadOnlyObservableCollection à chaque fois.

Conclusion

L'encapsulation est un principe fondamental, et il faut parfois se creuser la tête pour le mettre en oeuvre dans certaines situations. Cependant, cela vaut généralement le coup de se poser ce genre de questions, surtout pour les objets qui vont être réutilisés ou utilisés par d'autres. J'espère que ce post vous sera utile dans vos développements.

Happy programming!

[WPF] Réglons son compte à INotifyPropertyChanged (dérivation, réflexion, AOP, EntLib, Spring.NET, extension)

Introduction

INotifyPropertyChanged est une interface classique pour notifier à un client qu'une propriété a changé. C'est un scénario typique en WPF quand on utilise du Binding. Je vais vous présenter plusieurs techniques pour l'implémenter et discuter des problèmes que l'on peut rencontrer.

Voilà le sommaire de ce post :

  • Introduction
  • Version élémentaire
  • Version avec une classe de base
  • Version avec une classe de base par Josh Smith
  • Bilan de mi-parcours
  • Version sans création de PropertyEventArgs à chaque fois
  • AOP interception et injection
  • Entreprise Library, Policy Injection Application block
  • Spring.NET and AOP
  • La solution .NET 3.5 via extension methods
  • Optimisation possible
  • Conclusion

Et voilà la classe qui va nous suivre tout au long de ce post :

public class Person : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private string _name;
    public string Name { 
        get { return _name; }
        set { _name = value; }
    }
}

La classe Person dérive de INotifyPropertyChanged et l'implémente grâce à l'événement PropertyChanged.

Version élémentaire

On rajoute la fonction suivante qui permet de déclencher l'événement :

private void RaisePropertyChanged(string propertyName) 
{
    if (PropertyChanged == null) return;
    PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}

Et on modifie le set de la propriété, comme cela, pour déclencher l'évènement :

set 
{ 
    _name = value;
    RaisePropertyChanged("Name");
}

C'est la méthode la plus simple et c'est celle que je me vois implémenter le plus souvent.

Discutons un peu cette méthode :

Les Pour :

  • Simple
  • Efficace

Les Contre :

  • Copier/coller de la fonction RaisePropertyChanged pour chaque classe
  • Utiliser la string "Name" pose des problèmes de maintenance car une faute de frappe est vite arrivée et il est souvent difficile de débugger ce genre d'erreur.
  • Création d'un objet PropertyChangedEventArgs à chaque set de la propriété : Josh Smith mentionne dans un de ses posts que cette création de petits objets peut risquer de fragmenter la mémoire managée.
  • C'est relativement fastidieux si l'on a beaucoup de propriétés et/ou beaucoup d'objets.

Version avec une classe de base

Le problème du copier/coller de la fonction RaisePropertyChanged peut être résolu en utilisant une classe de base :

public class NotifyPropertyChangedBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private void RaisePropertyChanged(string propertyName)
    {
        if (PropertyChanged == null) return;
        PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

Les Pour :

  • Les mêmes que pour la version précédente
  • Il n'y a plus de copier/coller de la fonction ni de l'événement

Les Contre :

  • A part le copier/coller les problèmes sont les mêmes
  • Dériver d'une classe de base n'est pas toujours possible à cause de l'héritage simple en C# ou VB.NET

Version avec une classe de base par Josh Smith

Josh Smith a écrit une classe de base aussi pour ce problème ici

Les principaux avantages de sa classe sont que le nom de la propriété est vérifié via réflexion et que les événements sont mis en cache.

Les Pour :

  • Vérification du nom de la propriété par réflexion
  • Mise en cache des PropertyChangedEventArgs
  • La vérification n'est faite qu'en mode débug, donc pas de perte de performance en mode release

Les Contre :

  • L'implémentation est lente, beaucoup plus que de recréer l'objet à chaque fois
  • Encore une fois cela oblige à dériver d'un objet
  • Il est toujours fastidieux d'écrire le RaisePropertyChanged pour chaque setter

Bilan de mi-parcours

On voit que malgré trois propositions d'implémentation, il reste toujours un certain nombre de problèmes plus ou moins gênants. Pour le moment, nous n'avons abordé que des solutions classiques, c'est-à-dire implémentation directe ou via dérivation. Nous avons aussi commencé à voir que via réflexion, on peut valider le nom de la propriété. Par la suite, je vais vous présenter une version optimisée pour la vitesse et d'autres solutions moins classiques.

Version sans création du PropertyChangedEventArgs à chaque fois

Dans la version de Josh, la création de l'évènement est remplacé par un lookup dans un dictionnaire pour récupérer la même instance de l'argument à chaque fois. C'est de là que vient la lenteur de la méthode. On peut, simplement, arriver à une méthode rapide et qui évite la fragmentation :

public class Person : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private void RaisePropertyChanged(PropertyChangedEventArgs eventArgs)
    {
        if (PropertyChanged == null) return;
        PropertyChanged(this, eventArgs);
    }

    private static readonly PropertyChangedEventArgs _nameChangedEventArgs
        = new PropertyChangedEventArgs("Name");

    private string _name;
    public string Name { 
        get { return _name; }
        set 
        {
            _name = value;
            RaisePropertyChanged(_nameChangedEventArgs);
        }
    }
}
 

Ici, la ruse est simple : on crée un singleton de l'argument et on le passe directement à la méthode RaisePropertyChanged. Grâce aux mots-clés static et readonly, le runtime .NET assure la création unique de l'objet.

Les Pour :

  • Simple
  • Très efficace
  • Pas de fragmentation de la mémoire

Les Contre :

  • Toujours les mêmes problèmes que pour la version élémentaire (à part la fragmentation)

AOP interception et injection

L'AOP (Aspect Oriented Programming) est un paradigme de programmation qui prône la séparation des considérations techniques et des descriptions métier dans une application. Ici, on est tout à fait dans ce cas. Je dois dire qu'en ce moment je suis très intéressé par ce paradigme de programmation.

Il existe beaucoup de frameworks permettant de faire de l'AOP en .NET et encore beaucoup plus dans le monde Java. Je vais en citer deux:

Je vais présenter une méthode sur Entreprise Library et son module de Policy Injection, et une pour Spring.NET.

Entreprise Library, Policy Injection Application Block

Le Policy Injection Application Block de Entreprise Library permet de construire ou d'envelopper un objet avec un proxy, qui va pouvoir intercepter les appels de méthodes et de propriétés et réaliser des actions avant ou après. Les injections de bases fournies par Entlib et Spring sont par exemple le log et la gestion d'exception, ou encore la mise en cache. Pour les deux frameworks, il faudra écrire le petit bout de code qui fait la notification.

De plus, l'objet aura une contrainte suplémentaire : il devra implémenter une interface ou dériver de la classe MarshalByRefObject. Tout ceci est très bien expliqué dans la documentation d'Entlib. Concentrons-nous donc sur l'implémentation de INotifyPropertyChanged.

Pour commencer, on va créer un handler qui vas permettre de gérer l'interception de la propriété :

public class NotifyPropertyChangedHandler : ICallHandler
{
    public IMethodReturn Invoke(IMethodInvocation input,
                                 GetNextHandlerDelegate getNext)
    {
        //Before the call

        IMethodReturn result = getNext()(input, getNext);

        //After the call
    }

    public int Order{
        get{ return 0; }
        set{ throw new NotImplementedException(); }
    }
}

Pour créer le handler, il suffit de dériver de ICallHandler qui se trouve dans Microsoft.Practices.EnterpriseLibrary.PolicyInjection. L'interface comprend la méthode Invoke et la propriété Order. La méthode Invoke, comme son nom l'indique, va représenter l'appel de la méthode ; la propriété Order va être utile dans le cas où il y a plusieurs handlers pour la même méthode.

IMethodReturn result = getNext()(input, getNext);

Cette ligne représente l'appel de la propriété ou de la fonction. Tout ceci est détaillé dans la documentation d'Entlib. Ce qu'il faut comprendre, c'est que le code que l'on met avant s'éxécutera avant, et celui qu'on met après... ben après ! Pour nous, c'est la partie après qui nous intéresse.

Dans la variable input on trouve les deux propriétés qui vont nous servir :

  • input.MethodBase.Name
  • input.Target

Le premier point nous permet d'avoir le nom de la propriété et le deuxième l'objet que l'on est en train d'intercepter.

Maintenant, reste à savoir quoi mettre après l'appel pour déclencher l'événement. On pense tout de suite à la réflexion, mais après avoir cherché un peu, ce n'est pas si facile qu'il y parait. Si on récupère l'événement via GetEvent, on obtient un EventInfo qui n'a pas l'air de permettre de lancer l'événement. En continuant mes recherches, j'ai trouvé une solution via le field qui permet de récupérer le PropertyChangedEventHandler. Voilà le code commenté :

public class NotifyPropertyChangedHandler : ICallHandler
{
    public IMethodReturn Invoke(IMethodInvocation input,
                                 GetNextHandlerDelegate getNext)
    {
        //Before the call

        IMethodReturn result = getNext()(input, getNext);

        //Check if it's a get or a set
        if (!input.MethodBase.Name.StartsWith("set_")) return result;

        //Get the field that contains the event handler
        var propertyChangedField =
            input.Target.GetType().GetField("PropertyChanged",
            BindingFlags.Instance | BindingFlags.NonPublic);

        var eventHandler = propertyChangedField.GetValue(input.Target)
            as PropertyChangedEventHandler;

        if (eventHandler == null) return result;

        //Get the name of the property by removing set_
        var propertyName = input.MethodBase.Name.Remove(0, 4);
        //Job done invoke the event
        eventHandler.Invoke(input.Target, 
            new PropertyChangedEventArgs(propertyName));

        return result;
    }

    public int Order{
        get{ return 0; }
        set{ throw new NotImplementedException(); }
    }
}

Pour se simplifier la vie, l'idéal est de rajouter un attribut qui permet de marquer les propriétés que l'on veut intercepter :

[AttributeUsage(AttributeTargets.Property)]
public class NotifyPropertyChangedAttribute : HandlerAttribute
{
    public override ICallHandler CreateHandler()
    {
        return new NotifyPropertyChangedHandler();
    }
}

Voilà, il ne reste plus qu'à marquer la classe Person et à utiliser l'injecteur pour créer le proxy :

public class Person : IPerson, INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private string _name;
    [NotifyPropertyChanged]
    public string Name { 
        get { return _name; }
        set 
        {
            _name = value;
        }
    }
}

On notera aussi que j'ai fait une interface IPerson car pour créer le proxy, il faut soit une interface, soit un MarshalByRefObject.

Voilà finalement le code qui créera le wraper et qui déclenchera le handler pour les propriétés :

var person = new Person();
person.PropertyChanged += 
    (sender, e) => Console.WriteLine(e.PropertyName);

var wrapedPerson = PolicyInjection.Wrap<IPerson>(person);
wrapedPerson.Name = "Frédéric Hamel";

Je pense que c'est une méthode très intéressante à cause des concepts qu'elle met en jeu, car cela peut permettre de résoudre de nombreux problèmes plus complexes.

Donc pour résumer :

Les Pour :

  • Pas de copier/coller
  • Réutilisation du code flexible
  • Récupération du nom de la propriété automatique

Les Contre :

  • Complexe la 1ère fois qu'on le fait
  • Introduit une dépendance dans le projet au framework d'injection
  • L'objet est wrapé dans un proxy
  • Oblige à avoir une interface ou à dériver de MarshalByRefObject

Spring.NET and AOP

Avec Spring.NET, il est possible d'aller plus loin qu'avec Entreprise Library. En effet, avec Spring.NET, on peut carrément mixer l'objet cible avec l'objet qui contient les mécanismes à insérer. Donc, avec Spring, il suffit de créer une classe qui va implémenter INotifyPropertyChanged, et de la mixer avec l'objet business.

Regardons le code qui permet de créer le proxy et voyons ensuite les classes à créer pour que cela fonctionne.

var factory = new ProxyFactory(new Person());

factory.AddIntroduction(advisor);
factory.AddAdvice(advisor.AfterAdvice);

factory.ProxyTargetType = true;

var person = (Person)factory.GetProxy();

On commence par créer une factory et on lui passe l'objet dont on souhaite avoir le proxy. Ensuite, on ajoute à la factory une introduction. Une introduction est la classe que l'on va mixer avec l'objet business. Une fois les deux objets mixés, l'objet business implémentera INotifyPropertyChanged : il ne reste donc plus qu'à écrire l'interception des setters pour pouvoir invoquer l'événement.

Pour cela, on ajoute une advice. Celle que j'ai choisie est l'AfterReturningAdvice. En effet, cette advice est particulièrement bien adaptée à notre situation car elle s'exécute après que la méthode ait retourné sa valeur. Pour finir, on met la propriété ProxyTargetType à true pour que l'on puisse caster le proxy dans le type d'origine et on appelle GetProxy qui va nous fabriquer tout cela.

Voyons maintenant l'objet à introduire :

public class NotifyPropertyChangedMixin : 
    INotifyPropertyChanged, 
    ITargetAware, 
    IAfterReturningAdvice
{
    public event PropertyChangedEventHandler PropertyChanged;

    private void RaisePropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            //Here you can use TargetProxy or the target
            //argument of the AfterReturning method
            PropertyChanged(TargetProxy, 
                new PropertyChangedEventArgs(propertyName));
        }
    }

    //The proxy
    public IAopProxy TargetProxy
    {
        private get;
        set;
    }

    //This method will be called after the method returned
    public void AfterReturning(
        object returnValue, 
        MethodInfo method, 
        object[] args, 
        object target)
    {
        //We check that we have a setter
        if (method.Name.StartsWith("set_") 
            && (method.GetParameters().Length == 1)) 
        {
            RaisePropertyChanged(method.Name.Remove(0, 4));
        }
    }

}

La classe dérive de INotifyPropertyChanged et l'implémente avec la technique élémentaire que l'on a vu au début du post. La classe implémente aussi ITargetAware qui est une interface fournie par le framework Spring et qui permet de savoir dans quel proxy l'on se trouve. C'est une interface optionnelle, mais ici je l'ai implémentée pour pouvoir récupérer le proxy.

Le private get a été rajouté par mes soins car il n'est pas nécessaire non plus pour l'interface ITargetAware.

La dernière interface IAfterReturningAdvice permet d'avoir la méthode AfterReturning qui, comme son nom l'indique, va nous permettre d'éxécuter du code après le retour de la méthode cible.

La méthode AfterReturning est ultra simple : elle vérifie juste que l'on a une propriété et déclenche l'événement.

J'aime bien la manière dont les interfaces sont nommées dans Spring. En effet, c'est très imagé : l'advisor (le professeur) va donner des advices (des conseils) à la factory, et ces advices vont s'appliquer au moment de l'exécution. Voila le advisor que j'ai réalisé :

public class NotifyPropertyChangedAdvisor 
    : DefaultIntroductionAdvisor
{
    public NotifyPropertyChangedAdvisor()
        :base(new NotifyPropertyChangedMixin()) {}

    public IAfterReturningAdvice AfterAdvice
    {
        get {
            return (IAfterReturningAdvice)base.Advice;
        }
    }
}

On voit qu'une implémentation par défaut est donnée par Spring et on lui passe juste la classe à mélanger. J'ai aussi rajouté une propriété pour récupérer le conseil.

Normalement, les classes à mélanger et les advices sont des objets différents. Ici, j'ai choisi de tout mettre dans NotifyPropertyChangedMixin, car je trouvais que c'était mieux du point de vue de l'encapsulation. En effet, comme l'advice est dans la même classe, elle peut déclencher l'événement sans avoir recours à la réflexion qui viendrait quand même court-circuiter l'encapsulation de l'objet.

Il est par ailleurs à noter que pour utiliser var person = (Person)factory.GetProxy();

il faut que les propriétés de la classe personne soient marqués virtual, car sinon le proxy ne peut pas les intercepter. On peut aussi utiliser une interface et remplacer la ligne par :

var person = (IPerson)factory.GetProxy();

Dans ce cas, les propriétés n'ont pas besoin d'être marquées virtual. Sur le forum de Spring, Mark Pollack (le co-lead de Spring) m'a expliqué que dans la prochaine version de Spring, il n'y aura plus besoin de marquer les propriétés virtual.

Les Pour :

  • Sont les mêmes que pour Entreprise Library et son Policy Injection Block
  • Le framework spring étant plus puissant, il permet aussi d'injecter l'événement, l'interface et les fonctions

Les Contre :

  • Ici encore on retrouve les mêmes problèmes. L'AOP est un paradigme de programmation auquel il faut s'habituer, et il faut faire l'effort d'apprendre les frameworks. De plus, on crée une dépendance sur notre projet.

La solution .NET 3.5 via Extension methods

Pour finir en beauté, voilà une solution élégante pour l'implémentation de INotifyPropertyChanged. En effet, on peut attacher une méthode d'extension à une interface, ce qui est conceptuellement génial car cela veut dire que l'on peut ajouter un comportement à l'interface quand l'extension est ajoutée ! C'est presque de l'héritage multiple du pauvre et limité, mais ça donne des idées.

public static class NotifyPropertyChangedExtensions
{
    public static void RaisePropertyChanged(
        this INotifyPropertyChanged reference) 
    {
        FieldInfo propertyChangedField = 
            reference.GetType().GetField("PropertyChanged", 
            BindingFlags.Instance | BindingFlags.NonPublic);

        var eventHandler = propertyChangedField.GetValue(reference)
            as PropertyChangedEventHandler;

        if (eventHandler == null) return;

        var previewStackFrame = new StackFrame(1);
        var method = previewStackFrame.GetMethod();
        var propertyName = method.Name.Remove(0, 4);

        eventHandler.Invoke(reference, 
            new PropertyChangedEventArgs(propertyName));
    }
}

Donc comme indiqué, je définis l'extension sur INotifyPropertyChanged : this INotifyPropertyChanged reference

Ensuite, via réflexion, je récupère l'événement comme on l'a vu dans d'autres méthodes. Et pour récupérer le nom de la méthode, j'ai recours à une petite ruse. En effet, grâce à la class StackFrame, je peux remonter la pile des appels et récupérer la méthode appelante. Les setters sont toujours transformés en "set_propertyName", donc il suffit d'enlever le set_ et on a le nom de la propriété.

public string Name { 
    get { return _name; }
    set 
    {
        _name = value;
        this.RaisePropertyChanged();
    }
}

Comme on a affaire à une méthode d'extension, on est obligé de mettre this pour que le compilateur et l'intellisense puissent trouver la méthode.

Optimisation possible

Une optimisation que l'on voit souvent est de ne déclencher l'événement que si la valeur de la propriété a effectivement changé.

if (_name != value)
{
    _name = value;
    this.RaisePropertyChanged();
}

Concrètement, ce genre d'optimisation ne va apporter un plus que si les traitements réalisés en réaction à l'événement sont importants.

Conclusion

Nous avons vu dans ce long post de nombreuses manières d'implémenter INotifyPropertyChanged.

Ce qui est important ici est surtout de voir les différentes approches qui ont été utilisées et qui peuvent être transposées dans d'autres cas. Certaines méthodes visent la maintenabilité, d'autres l'efficacité, d'autres abordent le problème de manière plus générale. Je dirais qu'il n'y a donc pas vraiment de bonne ou de mauvaise méthode : il n'y a que des méthodes différentes qui s'appliquent en fonction des besoins et des contraintes.

La technique .NET 3.5 avec l'extension est sûrement suffisante dans bien des cas. Si on a besoin de plus de performance, alors celle qui met les arguments en singleton static (Version sans création du PropertyChangedEventArgs à chaque fois) est peut être la plus efficace. Si le projet a déjà des dépendances sur Spring ou sur Entreprise Library, il peut être bon de connaître les possibilités et de savoir les utiliser si besoin.

Happy programming !

[WPF] Data Binding Quick Reference

Vous trouverez ici les principales syntaxes permettant de réaliser un Data Binding en WPF. Le but ici n'est pas d'expliquer en détail son mécanisme mais plutôt de proposer une référence où vous pourrez trouver la syntaxe qui correspondra à votre besoin.

Sommaire :

  1. Affecter la source d'un binding
  2. Affiner la sélection avec la propriété Path
  3. Mode du Binding
  4. Utiliser des Converters
  5. Quelques astuces pour le Binding
  6. Utiliser le MultiBinding
  7. Rendre ses objets .NET Binding-Friendly

Affecter la source d'un binding

  • Binding sur le DataContext
    <ListBox ItemsSource="{Binding}" />
  • Binding sur un autre élément

    <Slider Name="_mySlider"/>
    <TextBlock Text="{Binding ElementName=_mySlider,Path=Value}"/>
  • Binding sur soi-même
    <TextBlock Name="_selfName" 
     Text="{Binding Name, RelativeSource={RelativeSource Self}}" />
  • Binding vers un parent
    <Canvas Name="_canvas">
        <TextBlock Text="{Binding Path=Name, 
                                  RelativeSource=
     {RelativeSource FindAncestor, AncestorType={x:Type Canvas}}}" />
    </Canvas>
  • Binding vers le template parent
    <ControlTemplate x:Key="_template">
        <TextBlock Background="{TemplateBinding Background}"/>
    </ControlTemplate>
  • Binding vers le data item précédent
     {Binding RelativeSource={RelativeSource PreviousData}}
  • Binding d'une propriété statique
    <TextBlock Text="{x:Static local:Window1.AStaticProperty}"/>
  • Binding d'une ressource statique
    <TextBlock Background="{StaticResource _blackBrush}"/>
  • Binding d'une ressource dynamique
    <TextBlock Background="{DynamicResource _blackBrush}"/>

Affiner la sélection avec la propriété Path

  • Deuxième constructeur de la classe Binding
    <!--Use the constructor : public Binding(string path)-->
    <TextBlock Text="{Binding Name}"/>
  • Propriété Path

    <TextBlock Text="{Binding Path=Name}"/>
  • Syntaxe longue
    <TextBlock>
        <TextBlock.Text>
            <Binding>
                <Binding.Path>Name</Binding.Path>
            </Binding>
        </TextBlock.Text>
    </TextBlock>
  • Binding vers une DependencyProperty attachée
    <TextBlock Canvas.Left="50" 
               Text="{Binding Path=(Canvas.Left), 
    RelativeSource={RelativeSource Self}}" />
  • Binding vers l'item courant synchronisé
<TextBlock Text="{Binding Path=/}" />
<TextBlock Text="{Binding Path=Photos/}" />
<TextBlock Text="{Binding Path=/DateTime}" />
<TextBlock Text="{Binding Path=Photos/DateTime}" />

Mode du Binding

  • Bidirectionnel
    <TextBlock Text="{Binding Path=Name, Mode=TwoWay}"/>
  • De la source vers la target
    <TextBlock Text="{Binding Path=Name, Mode=OneWay}"/>
  • Une fois de la source vers la target
    <TextBlock Text="{Binding Path=Name, Mode=OneTime}"/>
  • De la target vers la source
    <TextBlock Text="{Binding Path=Name, Mode=OneWayToSource}"/>

Utiliser des Converters

  • Affecter un Converter
    <TextBlock Text="{Binding Name, 
        Converter={StaticResource myDoNothingConverter}}"/>
  • Passer des paramètres à un Converter
    <TextBlock Text="{Binding Name, 
        Converter={StaticResource myDoNothingConverter},
        ConverterParameter='Hello'}"/>
  • Écrire un Converter
    En Xaml:
    xmlns
    :local="clr-namespace:Wpf.BindingLibrary.QuickReference"
    <local:DoNothingConverter x:Key="myDoNothingConverter"/>
    En Code:
    namespace
    Wpf.BindingLibrary.QuickReference { public class DoNothingConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { return Binding.DoNothing; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } } }
  • Écrire un Converter multi-values
    public class MultiValueConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, 
            object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    
        public object[] ConvertBack(object value, Type[] targetTypes,
            object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
  • Utiliser le string formater converter, disponible bientôt dans le SP1 de .NET 3.5
     Lire le post de Lester à ce sujet

Quelques astuces pour le binding

  • Récupérer un Binding
    BindingOperations.
    GetBindingExpression(dependencyObject, TextBlock.TextProperty); BindingOperations.
    GetBinding(dependencyObject, TextBlock.TextProperty);
  • Mettre à jour un Binding
    var bindingExpression =
    BindingOperations.
    GetBindingExpression(dependencyObject, TextBlock.TextProperty); binding.UpdateSource(); binding.UpdateTarget();
  • Effacer un Binding
    BindingOperations.
    ClearBinding(dependencyObject,TextBlock.TextProperty); BindingOperations.ClearAllBindings(dependencyObject);
  • Ne rien faire pendant un Binding
    return Binding.DoNothing;

Utiliser le MultiBinding

  • Syntaxe
    <TextBlock>
        <TextBlock.Text>
            <MultiBinding 
    Converter="{StaticResource myMultiValueConverter}"> <Binding Path="Name"/> <Binding Path="Adress"/> </MultiBinding> </TextBlock.Text> </TextBlock>

Rendre ses objets .NET Binding-friendly

  • INotifyPropertyChanged
    public class Person : INotifyPropertyChanged
    {
        [field:NonSerialized]
        public event PropertyChangedEventHandler PropertyChanged;
    
        private void RaisePropertyChanged(string propertyName) 
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this,
                new PropertyChangedEventArgs(propertyName));
            }
        }
    
        private string _name;
        public string Name { get { return _name; }
            set {
                _name = value;
                RaisePropertyChanged("Name");
            }
        }
    }
  • ObservableCollection<T>
    Vous pouvez utiliser le deuxième constructeur avec votre liste existante
    public ObservableCollection(List<T> list);
[Hello World] 1er post sur mon 1er blog

Bonjour,

Voilà c'est fait : le blog est ouvert et le premier post posté :)
Je remercie Cyril et Thomas pour m'avoir aidé à ouvrir le blog.

J'aimerais grâce à ce blog faire partager mes réflexions sur .NET et sur WPF en particulier. J'espère que ça aidera la communauté et que la communauté aimera lire ce blog!

En ce qui me concerne, pour me présenter un peu, je m' appelle Frédéric Hamel, je suis ingénieur, et je travaille en Suisse à Genève pour une société financière et d'édition de logiciel. Les projets sont en .NET 3.5 et font intervenir WPF, WCF et WF donc je pense que ça va aider à alimenter ce blog avec des contribution à la pointe !

Bienvenue et bonne lecture :)



Les 10 derniers blogs postés

- Dell Inspiron Mini 9 - Enfin en vente !!! par The diary of EBArtSoft le il y a 14 heures et 2 minutes

- Solution Template et Project Template dans Visual Studio par Atteint de JavaScriptite Aiguë [Cyril Durand] le il y a 16 heures et 44 minutes

- PocketIE et Assignation du SRC d'un Element IMG par Jerome Laban le il y a 17 heures et 36 minutes

- Conversion de fichiers RAW en fichier JPEG avec WPF par Perspective le il y a 18 heures et 12 minutes

- Mise à Jour du Moteur de Recherche des Arrêts de Bus de Montréal par Jerome Laban le il y a 18 heures et 56 minutes

- [WPF] XPSReader v0.2 par Blog Technique d'Audrey PETIT le il y a 19 heures et 57 minutes

- Entity Framework : providers Oracle, MySQL et PostgreSQL par Matthieu MEZIL le 09-07-2008, 10:10

- [WPF] Nouvel article sur c2i.fr par Richard Clark le 09-06-2008, 17:33

- F# nouvelle CTP 1.9.6.2 (update) par Pierrick's Blog le 09-06-2008, 13:27

- La suite ...Proposition de collaboration rédactionnelle entre les communautés de développeurs et Microsoft France par LucasR le 09-05-2008, 17:45