mardi 16 septembre 2008

Programmation événementielle Acte IV)

Comment : créer un contrôle qui répond à des événements

L'exemple suivant affiche une classe, ListWithChangedEvent, qui est semblable à la classe ArrayList standard, mais qui appelle aussi un événement Changed à chaque fois que le contenu de la liste change. Il existe de nombreuses façons d'utiliser une telle classe universelle dans un programme important.

Prenons, par exemple, un traitement de texte qui gère une liste des documents ouverts. Chaque fois que cette liste change, il peut être nécessaire d'avertir de nombreux objets différents dans le traitement de texte pour autoriser la mise à jour de l'interface utilisateur. En utilisant des événements, le code qui gère la liste des documents n'a pas besoin de savoir qui doit être averti : une fois que la liste a été modifiée, l'événement est automatiquement appelé et tous les objets concernés sont avertis correctement. L'utilisation d'événements permet d'augmenter la modularité du programme.

Lors de la création d'un composant général pouvant être utilisé comme une classe de base pour d'autres composants, vous devez tenir compte du fait que les événements, contrairement aux champs, peuvent être appelés uniquement à partir de la classe qui les a déclarés. Les classes dérivées ne peuvent pas appeler directement les événements déclarés dans la classe de base. Bien que cela soit parfois le but recherché, il est souvent souhaitable d'accorder à la classe dérivée la liberté d'appeler l'événement, le plus souvent, en créant une méthode d'appel protégée pour l'événement. Les classes dérivées, en appelant cette méthode d'appel, peuvent appeler l'événement. Pour obtenir une souplesse encore plus grande, la méthode d'appel est souvent déclarée comme virtuelle, ce qui permet à la classe dérivée de la substituer. La classe dérivée peut ainsi intercepter les événements appelés par la classe de base, éventuellement en les traitant à sa façon.

Dans l'exemple suivant, cela a été fait avec la méthode OnChanged. Une classe dérivée pourrait appeler ou se substituer à cette méthode si nécessaire.

Il existe une autre différence entre les événements et les champs : un événement, contrairement à un champ, peut être placé dans une interface. Lors de l'implémentation de l'interface, la classe d'implémentation doit fournir un événement correspondant dans la classe qui implémente l'interface.

Exemple

namespace TestCollections
{
   // Un type délégue pour déclencher une notification de changement.
   public delegate void ChangedEventHandler(object sender, System.EventArgs e);
   // Une classe fonctionnant comme une ArrayList mais qui envoie des notifications 
   d'événements chaque fois que la liste change.

   public class ListWithChangedEvent : System.Collections.ArrayList
   {
      // Un événement que les clients peuvent utiliser pour être notifies chaque
      fois que la liste change.

      public event ChangedEventHandler Changed;
      // Invoque l'événement de changement; appelé chaque fois que la liste change
      protected virtual void OnChanged(System.EventArgs e)
      {
         if (Changed != null)
         {
            Changed(this, e);
         }
      }
      // Réécrit certaines des methods qui peuvent changer la liste;
      public override int Add(object value)
      {
         int i = base.Add(value);
         OnChanged(System.EventArgs.Empty);
         return i;
      }
      public override void Clear()
      {
         base.Clear();
         OnChanged(System.EventArgs.Empty);
      }
      public override object this[int index]
      {
         set
         {
           base[index] = value; OnChanged(System.EventArgs.Empty);
         }
      }
    }
 }
namespace TestEvents
{
   using TestCollections;
   class EventListener
   {
      private ListWithChangedEvent m_list;
      public EventListener(ListWithChangedEvent list)
      {
         m_list = list;
         // Ajoute "ListChanged" à l'événement Changed sur m_list:
         m_list.Changed += new ChangedEventHandler(ListChanged);
      }
       // Ceci sera appelé chaque fois que la liste change.
      
private void ListChanged(object sender, System.EventArgs e)
       {
         System.Console.WriteLine("This is called when the event fires.");
       }
       public void Detach()
       {
         // Détache l'événement et supprime la liste
       
 m_list.Changed -= new ChangedEventHandler(ListChanged); m_list = null;
       }

     }
     class Test
     {
       // Teste la classe ListWithChangedEvent.
       static void Main()
       {
          // Crée une nouvelle liste.
         
ListWithChangedEvent list = new ListWithChangedEvent();
          // Crée une classe qui écoute l'événement change de list
         
EventListener listener = new EventListener(list);
          // Ajoute et supprime des événements dans une liste
          list.Add("item 1"); list.Clear(); listener.Detach();
       }
     }
   }

Sortie

This is called when the event fires. This is called when the event fires.

Programmation fiable

·                     Déclaration d'un événement

Pour déclarer un événement dans une classe, vous devez d'abord déclarer un type de délégué pour l'événement, si aucun n'est déclaré.

public delegate void ChangedEventHandler(object sender, System.EventArgs e);


Le type délégué définit l'ensemble des arguments passés à la méthode qui gère l'événement. Plusieurs événements peuvent partager le même type délégué ; cette étape n'est donc nécessaire que si aucun type délégué approprié n'a encore été déclaré.

L'événement proprement dit est ensuite déclaré.

 

public event ChangedEventHandler Changed;


Un événement est déclaré comme un champ de type délégué, la seule différence étant que le mot clé "event" précède la déclaration de l'événement, à la suite des modificateurs. Les événements sont généralement déclarés comme publics, mais n'importe quel modificateur d'accessibilité est autorisé.

 

 

·                     Appel d'un événement

Une fois qu'une classe a déclaré un événement, elle peut traiter cet événement exactement comme un champ du type délégué indiqué. Le champ sera null si le client a raccordé un délégué à l'événement ; sinon, il fera référence au délégué qui devra être appelé en même temps que l'événement. Ainsi, appeler un événement consiste généralement à vérifier d'abord si le champ est null, puis à appeler l'événement.


if
(Changed != null) { Changed(this, e); }

L'appel d'un événement ne peut être effectué qu'à partir de la classe qui a déclaré cet événement.

·                     Raccordement à un événement

Vu de l'extérieur de la classe qui l'a déclaré, un événement ressemble à un champ, mais l'accès à ce champ est très limité. Les seules choses qui peuvent être faites consistent à composer un nouveau délégué sur ce champ, et supprimer un délégué d'un champ (pouvant être composite).

Vous utiliserez à cet effet les opérateurs += et -=. Pour commencer à recevoir des appels d'événement, le code client crée d'abord un délégué de type événement qui fait référence à la méthode devant être appelée à partir de l'événement. Il compose ensuite ce délégué sur tous les autres délégués auxquels l'événement peut être connecté à l'aide de +=.


m_list.Changed += new ChangedEventHandler(ListChanged);

Lorsque le code client a reçu les appels d'événement, il supprime son délégué de l'événement en utilisant l'opérateur -=.


m_list.Changed -= new ChangedEventHandler(ListChanged);

 

Comment : créer des événements conformes aux indications du .NET Framework

Le langage C# permet à un événement d'utiliser tout type délégué, mais le .NET Framework a des indications plus strictes pour les délégués et les événements. Si vous avez l'intention d'utiliser votre composant avec le .NET Framework, vous voudrez probablement suivre ces indications.

Les indications .NET Framework spécifient que le type délégué utilisé pour un événement doit disposer de deux paramètres : un paramètre source de l'objet qui désigne la source de l'événement et un paramètre spécifique à l'événement qui encapsule toutes les informations supplémentaires relatives à l'événement. Le paramètre spécifique à l'événement doit dériver de la classe EventArgs. Pour les événements qui n'utilisent pas d'informations supplémentaires, .NET Framework fournit la classe EventHandler.

L'exemple suivant est comme le code dans Comment : créer un contrôle qui répond à des événements , à ceci près que cette version suit les indications du .NET Framework.

Exemple

namespace TestCollections
{
   public class ListWithChangedEvent : System.Collections.ArrayList
   {

     public event System.EventHandler Changed;
     protected virtual void OnChanged(System.EventArgs e)
     {
        if (Changed != null)
        {
           Changed(this, e);
        }
     }
      public override int Add(object value)
      {
         int i = base.Add(value); OnChanged(System.EventArgs.Empty); return i;
      }
      public override void Clear()  
      {
         base.Clear(); OnChanged(System.EventArgs.Empty);
      }  
      public override object this[int index]
      {   
        set
        {
          base[index] = value; OnChanged(System.EventArgs.Empty);
        }
      }
    }
}

namespace TestEvents
{
    using TestCollections;
    class EventListener
    {
      private  ListWithChangedEvent m_list; public
      EventListener(ListWithChangedEvent list)
      {
         m_list = list;
         m_list.Changed += new System.EventHandler(ListChanged); }
         private void ListChanged(object sender, System.EventArgs e)
         {
            System.Console.WriteLine("This is called when the event fires.");
         }
         public void Detach()
         {
            m_list.Changed -= new System.EventHandler(ListChanged);
            m_list = null;
         }
      }
      class Test
      {
         static void Main()
         {
           ListWithChangedEvent list = new ListWithChangedEvent();
           EventListener listener = new EventListener(list);
           list.Add("item 1"); list.Clear(); listener.Detach();
         }
       }
    }

Sortie

This is called when the event fires. This is called when the event fires.

 

Comment : déclarer un événement dans une interface et l'implémenter dans une classe

Cet exemple montre qu'il est possible de déclarer un événement dans une interface et de l'implémenter dans une classe.

Exemple

public delegate void TestDelegate();
//Déclaration du délégué
public interface ITestInterface
{
   event TestDelegate TestEvent;
   void FireAway();
}

public
class TestClass : ITestInterface
{
   public event TestDelegate TestEvent;
   public void FireAway()
   {
      if (TestEvent != null)
      {
         TestEvent();
      }
   }
}

public
class MainClass
{
   static private void F()
   {
      System.Console.WriteLine("This is called when the event fires.");
   }
   static void Main()
   {
      ITestInterface i = new TestClass(); i.TestEvent += new TestDelegate(F);
      i.FireAway();
   }
}

 

Comment : utiliser un dictionnaire pour stocker des instances d'événements

Une utilisation de accessor-declarations est d'exposer un grand nombre d'événements sans allouer de champ pour chaque événement, mais en utilisant à la place un dictionnaire pour stocker les instances d'événements. Cela est utile uniquement si vous disposez d'un très grand nombre d'événements, mais que vous prévoyez que la plupart des événements ne seront pas implémentés.

Exemple

public delegate void Delegate1(int i);
public delegate void Delegate2(string s);
public class PropertyEventsSample
{
   private System.Collections.Generic.Dictionary<string, System.Delegate>
   eventTable; public PropertyEventsSample()
   {
      eventTable = new System.Collections.Generic.Dictionary<string,
      System.Delegate>();
      eventTable.Add("Event1", null);
      eventTable.Add("Event2", null); } public event Delegate1 Event1
      {
         add
         {
            eventTable["Event1"] = (Delegate1)eventTable["Event1"] + value;
         }
         remove
         {
            eventTable["Event1"] = (Delegate1)eventTable["Event1"] - value;
         }
       }
       public event Delegate2 Event2
       {
          add
          {
            eventTable["Event2"] = (Delegate2)eventTable["Event2"] + value;
          }
          remove
          {
            eventTable["Event2"] = (Delegate2)eventTable["Event2"] - value;
          }
       }
       internal void FireEvent1(int i)
       {
          Delegate1 D; if (null != (D = (Delegate1)eventTable["Event1"]))
          {
             D(i);
          }
       }
       internal void FireEvent2(string s)
       {
          Delegate2 D; if (null != (D = (Delegate2)eventTable["Event2"]))
          {
             D(s);
          }
       }
    }
    public class TestClass
    {
       public static void Delegate1Method(int i)
       {
          System.Console.WriteLine(i);
       }
       public static void Delegate2Method(string s)
       {
           System.Console.WriteLine(s);
       }
       static void Main()
       {
           PropertyEventsSample p = new PropertyEventsSample(); p.Event1 += new
           Delegate1(TestClass.Delegate1Method); p.Event1 += new
           Delegate1(TestClass.Delegate1Method); p.Event1 -= new
           Delegate1(TestClass.Delegate1Method); p.FireEvent1(2); p.Event2 += new
           Delegate2(TestClass.Delegate2Method); p.Event2 += new
           Delegate2(TestClass.Delegate2Method); p.Event2 -= new
           Delegate2(TestClass.Delegate2Method); p.FireEvent2("TestString");
       }
     }

Sortie

2 TestString

 

Comment : implémenter deux interfaces avec un événement portant le même nom

Les propriétés d'événement peuvent également être utilisées lorsque vous implémentez deux interfaces, possédant toutes les deux un événement du même nom. Dans ce cas, vous devez utiliser une propriété d'événement d'implémentation explicite.

Toutefois, lorsque vous implémentez de manière explicite des événements dans une interface, vous devez fournir des méthodes d'ajout et de suppression.

 

 

Exemple

public delegate void Delegate1();
public delegate int Delegate2(string s);
public interface I1
{
   event Delegate1 TestEvent;
}
public interface I2
{
   event Delegate2 TestEvent;
}
public class ExplicitEventsSample : I1, I2
{
   public event Delegate1 TestEvent; // Implémentation normale de I1.TestEvent.
   private Delegate2 TestEvent2Storage;
   // Stockage sous – jacent pour I2.TestEvent.
   event Delegate2 I2.TestEvent
   //explicit implementation of I2.TestEvent.
   {

       add
       {
          TestEvent2Storage += value;
       }
       remove
       {
          TestEvent2Storage -= value;
       }
     }
     private void FireEvents()
     {
        if (TestEvent != null)
        {
           TestEvent();
        }
        if (TestEvent2Storage != null)
        {
           TestEvent2Storage("hello");
        }
     }
   }

 



--
Alain Lompo
Excelta - Conseils et services informatiques
MCT
MCSD For Microsoft .Net
MVP Windows Systems Server / Biztalk Server
Certifié ITIL et Microsoft Biztalk Server

Aucun commentaire: