begin process at 2008 05 11 23:36:31
1 170 075 membres
528 nouveaux aujourd'hui
13 956 membres club

Vous ne trouvez pas de réponse à votre problème ? Alors posez la question dans le forum.
Souvenez-vous qu'il n'y a jamais de question bête, mais rester dans l'ignorance parce que l'on n'ose pas poser une question, ça c'est une erreur !

Propriétés, accesseurs, et évènements d’une classe (2/3) - Evenements avant un changement


Information sur le tutorial

Catégorie :Divers Tutorial .NET ( DotNet ) Date de création : 06/02/2008 15:19:51 Vu : 1 382 fois

Note :
Aucune note

Commentaire sur cette source (1)
Ajouter un commentaire et/ou une note


Description

Ce tutoriel expose une solution pour envoyer des évènements BeforeChange lors de la modification d’une propriété dans une classe. Par exemple, on va envoyer un évènement alors que l’on s’apprête à changer le montant d’une opération bancaire. Un éventuel objet qui s’est connecté à cet évènement pourrait vouloir stopper cette modification avant qu’elle n’arrive, si jamais le nouveau montant ne répond pas à certaines règles métier. Essayons de voir une méthode pour gérer tout cela.

Tutorial

Ce tutoriel fait suite à un précédent opus, que vous pouvez visualiser ici :

Pour rappel, notre cas d'étude est un logiciel de gestion de comptes personnels. Nous nous intéressons principalement à deux classes :

  • La classe Compte, qui contient entre autres une liste d'opération bancaires, et une solde (solde = somme des opérations bancaires),
  • La classe Operation, qui a pour propriétés un montant, une date, un libellé, ...


Voici le code de la propriété "Montant" de notre objet "Operation" :


private Single m_Montant

public Single Montant

{

    get { return m_Montant; }

    set {

        if(m_Montant == value) return;

        m_Montant = value;

    }

}


Dans nos règles métier, admettons que nous ayons à implémenter la possibilité de "bloquer les opérations d'un compte". Ceci signifie que la modification du montant d'une des opérations d'un compte donné n'est plus permise. Pour cela, plusieurs possibilités :

- Intégrer une propriété "Locked" dans notre objet Operation. Dans le set de notre montant sur l'opération, on vérifiera si celui-ci n'est pas "locked" avant de mettre à jour notre variable privée "m_montant". C'est très bien, mais l'inconvénient, c'est que tous les autres objets peuveut modifier ce Locked pour débloquer l'objet Operation à n'importe quel moment. Ceci peut mettre en péril notre règle métier.

- Dans l'accesseur sur le montant dans notre objet Operation, on peut regarder si le compte au dessus n'est pas bloqué. Si c'est le cas, on n'autorise pas la modification... C'est bien, mais ce qui me dérange, c'est que dans ce cas, l'objet Operation doit connaitre l'objet Compte...

- Intégrer un évènement BeforeMontantChange qui sera lancé avant la modification de la valeur interne m_montant. Cet évènement permettra aux objets abonnés de spécifier s'ils veulent ou non annuler la modification du montant avant qu'elle ait lieu. C'est la solution que nous allons décrire dans ce tutoriel.


Tout d'abord, il est judicieux de déclarer un delegate et un évènement personnalisé qui vont nous permettre de spécifier la valeur actuelle du montant, ainsi que la future valeur de notre montant. En effet, si nous ne faisons pas ça, il sera impossible pour les classes s'abonnant à l'évènement de connaitre la nouvelle valeur du solde, puisque celle-ci na pas encore été mise à jour dans l'objet.


Voici la déclaration du delegate dans la classe Operation, et la classe MontantChangingEventArgs (qui hérite de EventArgs) :


public delegate void MontantChangingDelegate(object sender, MontantChangingEventArgs args);


public class MontantChangingEventArgs : EventArgs

{

    public Single newval;

    public Single oldval;

    public bool cancel = false;

    public MontantChangingEventArgs(Single newvalue, Single oldvalue)

    {

        newval = newvalue;

        oldval = oldvalue;

    }

}


Vous remarquerez la propriété booléenne "cancel". Celle-ci permettra aux objets qui s’abonnent à l'évènement MontantChanging d'arrêter la mise à jour de la valeur. Nous verrons ça un peu plus loin.


Dans notre classe Operation, nous déclarons maintenant un nouvel évènement :

private event MontantChangingDelegate MontantChanging;


Le code de notre accesseur sera maintenant le suivant :

public Single Montant

{

    get { return m_Montant; }

    set {

        if(m_Montant == value) return;

        if(LaunchValueChangingEvent(value, m_value))

        {

            m_Montant = value;

        }

    }

}


Et voici maintenant le code de la méthode LaunchValueChangingEvent, c'est ici que se trouve l'astuce :


private bool LaunchMontantChangingEvent(Single newvalue, Single oldvalue)

{

    MontantChangingEventArgs args = new MontantChangingEventArgs(newvalue, oldvalue);

    if (MontantChanging != null)

    {

        for (int j = 0; j < MontantChanging.GetInvocationList().Length; j++)

        {

            MontantChanging.GetInvocationList()[j].DynamicInvoke(new object[] { this, args });

            if (args.cancel) return false;

        }

    }


    return true;

}


... Dans cette méthode, on parcourt tous les objets abonnés à l'évènement, et on les invoque dynamiquement. Au premier qui dit "STOP ! On annule la modification de la valeur !", on retourne false dans notre méthode. Ainsi, si 100 objets s'abonnent à l'évènement et que le premier fait un cancel, les 99 autres ne seront pas appelés, ce qui est bien, puisqu’on n’en a pas l'utilité.

Comme en cas de stop la méthode retourne false, et bien dans l'accesseur ne met pas à jour la valeur m_montant...


Mission accomplie : nous avons la possibilité depuis notre compte, de stopper les modifications sur les opérations qu’il contient. Si on veut, on peut même s'amuser à ne pas autoriser qu'un montant d'opération soit mis à jour avec une valeur farfelue (une somme au dessus de 99 999 999€ par exemple...).



Dernier point, comme il s'agit du deuxième tutoriel, nous allons mettre à jour notre code pour tenir compte des préconisations détaillées dans le tutoriel n°1 :


Le code dans la classe Operation :


private Single m_montant;


private event MontantChanged MontantChangedInternal;

private event MontantChanging MontantChangingInternal;


[field: NonSerialized]

private event MontantChanged MontantChangedExternal;

[field: NonSerialized]

private event MontantChanging MontantChangingExternal;


public delegate void MontantChanged(object sender, MontantChangedEventArgs args);

public delegate void MontantChanging(object sender, MontantChangedEventArgs args);


public event EventHandler OnMontantChanged

{

add {

    if(value.Target.GetType().Assembly == this.GetType().Assembly &&

    value.Target.GetType().IsSerializable)

    MontantChangedInternal += value;

    else

        MontantChangedExternal += value;

    }

    remove {

        MontantChangedInternal -= value;

        MontantChangedExternal -= value;

    }

}


public event EventHandler OnMontantChanging

{

    add {

    if(value.Target.GetType().Assembly == this.GetType().Assembly &&

        value.Target.GetType().IsSerializable)

        MontantChangingInternal += value;

    else

        MontantChangingExternal += value;

    }

    remove {

        MontantChangingInternal -= value;

        MontantChangingExternal -= value;

    }

}


public Single Montant

{

    get { return m_montant; }

    protected set {

        if(m_montant == value) return;

        if(LaunchValueChangingEvent(value, m_value))

        {

            Single oldMontant = m_montant;

            m_montant = value;

                LaunchMontantChangedEvent(m_montant, oldMontant);

        }

    }

}


private void LaunchMontantChangedEvent(Single newvalue, Single oldvalue)

{

    MontantChangedEventArgs args = new MontantChangedEventArgs(newvalue, oldvalue);

    if (MontantChangeInternal != null)

        MontantChangeInternal(this, EventArgs.Empty);


    if (MontantChangeExternal != null)

    MontantChangeExternal(this, EventArgs.Empty);

}


private bool LaunchMontantChangingEvent(Single newvalue, Single oldvalue)

{

    MontantChangingEventArgs args = new MontantChangingEventArgs(newvalue, oldvalue);

    if (MontantChanging != null)

    {

        for (int j = 0; j < MontantChanging.GetInvocationList().Length; j++)

        {

            MontantChanging.GetInvocationList()[j].DynamicInvoke(new object[] { this, args });

            if (args.cancel) return false;

        }

    }


    return true;

}


... Les classes évènements :

public class MontantChangingEventArgs : Value_ChangeEventArgs<Single>

{

    public bool cancel = false;

    public Value_ChangingEventArgs(Single newvalue, Single oldvalue) : base(newvalue, oldvalue) { }

}


public class MontantChangedEventArgs : Value_ChangeEventArgs<Single>

{

    public Value_ChangedEventArgs(Single newvalue, Single oldvalue) : base(newvalue, oldvalue) { }

}


public class Value_ChangeEventArgs<T> : EventArgs

{

    public T newval;

    public T oldval;

    public Value_ChangeEventArgs(T newvalue, T oldvalue)

    {

        newval = newvalue;

        oldval = oldvalue;

    }

}


Pour la suite : Gérer l'historique des modifications pour gérer les commandes annuler/refaire d'un logiciel

Ca se passe ici :

  • signaler à un administrateur
    Commentaire de yoannd le 06/02/2008 15:28:19

    Lien vers le tutoriel précédent :
    http://www.csharpfr.com/tutoriaux/PROPRIETES-ACCESSEURS-EVENEMENTS-CLASSE-EVENEMENTS-SERIALISATION_816.aspx

    Lien vers le tutoriel suivant :
    http://www.csharpfr.com/tutoriaux/PROPRIETES-ACCESSEURS-EVENEMENTS-CLASSE-GERER-UNDO-REDO-DANS_818.aspx

Ajouter un commentaire

Appels d'offres

Pub



CalendriCode

Mai 2008
LMMJVSD
   1234
567891011
12131415161718
19202122232425
262728293031 

VS Express FR Gratuit !

VS Express en français et 100% gratuit !

Boutique

Boutique de goodies CodeS-SourceS