Mise en place d’événements/delegates en VB.NET et C#.NET


Une question qui revient assez souvent dans les newsgroups est :



  • Comment mettre en place une communication entre mes classes métiers et mes IHM ?
  • Comment faire ça avec la philosophie .NET ?

(les exemples de code utilisés dans cet article sont en libre téléchargement ici)


Un exemple assez classique pour illustrer le propos reste la copie de fichier. L’idéal est de créer une classe métier qui va s’occuper de la copie du fichier puis de créer une IHM qui va consommer cette classe.


Cependant, si le fichier fait 10Mo, il peut être utile de présenter à l’utilisateur une gauge de progression pour le faire patienter. De plus, si il décide d’annuler la copie, il doit pouvoir le faire, ce qui veut dire que l’interface graphique ne doit pas être bloquée durant cette copie. A titre d’exemple, voici une classe “classique” (Librairie_CS.GestionFichiers ou Librairie_VB.GestionFichiers) permettant de réaliser cette opération de copie :


using System;
using System.IO;


namespace Librairie_CS {


 public class GestionFichiers {


  private string cheminFichier = string.Empty;
  public string CheminFichier {


   get {


    return(this.cheminFichier);
   }
  }


  public GestionFichiers(string cheminFichier) {


   this.cheminFichier = cheminFichier;
  }


  public void CopieFichier(string cheminFichierDestination, bool ecrasement) {


   // On vérifie si le fichier de destination n’existe pas déjà
   if (File.Exists(cheminFichierDestination)) {
    
    // Si demandé, on écrase le fichier de destination
    if (ecrasement) {


     File.Delete(cheminFichierDestination);
    }
    else {


     throw new IOException(string.Format(“Le fichier de destination ‘{0}’ existe déjà.”, cheminFichierDestination));
    }
   }


   // On ouvre le fichier de destination en écriture
   using (FileStream fileStreamDestination = new FileStream(cheminFichierDestination, FileMode.Create, FileAccess.Write)) {


    // On ouvre le fichier d’origine en lecture
    using (FileStream fileStreamOriginal = new FileStream(this.cheminFichier, FileMode.Open, FileAccess.Read)) {


     // On lit le prochain octet
     int octet = fileStreamOriginal.ReadByte();
     while (octet != -1) {
     
      // On écrit l’octet
      fileStreamDestination.WriteByte((byte)octet);


      octet = fileStreamOriginal.ReadByte();
     }


     fileStreamOriginal.Close();
    }
    fileStreamDestination.Close();
   }
  }
 }
}


Voici le même code écrit en VB.NET:


Imports System
Imports System.IO


Namespace Librairie_VB


    Public Class GestionFichiers


        Private privateCheminFichier As String = String.Empty
        Public ReadOnly Property CheminFichier() As String
            Get
                Return (Me.privateCheminFichier)
            End Get
        End Property


        Public Sub New(ByVal fichier As String)
            Me.privateCheminFichier = fichier
        End Sub


        Public Sub CopieFichier(ByVal cheminFichierDestination As String, ByVal ecrasement As Boolean)


            ‘ On vérifie si le fichier de destination n’existe pas déjà
            If File.Exists(cheminFichierDestination) Then


                ‘ Si demandé, on écrase le fichier de destination
                If ecrasement Then
                    File.Delete(cheminFichierDestination)
                Else
                    Throw New IOException(String.Format(“Le fichier de destination ‘{0}’ existe déjà.”, cheminFichierDestination))
                End If
            End If


            ‘ On ouvre le fichier de destination en écriture
            Dim fileStreamDestination As New FileStream(cheminFichierDestination, FileMode.Create, FileAccess.Write)


            ‘ On ouvre le fichier d’origine en lecture
            Dim fileStreamOriginal As New FileStream(Me.privateCheminFichier, FileMode.Open, FileAccess.Read)


            ‘ On lit le prochain octet
            Dim octet As Integer = fileStreamOriginal.ReadByte()


            While octet <> -1
                ‘ On écrit l’octet
                fileStreamDestination.WriteByte(CType(octet, Byte))


                octet = fileStreamOriginal.ReadByte()
            End While


            fileStreamOriginal.Close()
            fileStreamDestination.Close()


        End Sub


    End Class


End Namespace


Ceci étant fait, il faudrait maintenant implémenter trois événements dans nos classes:



  • CopieDemarrage : cet événement devra être émis juste avant le démarrage proprement dit de l’opération de copie. Il devra permettre notament d’informer le client de la taille du fichier à copier
  • CopieEnCours : cet événement devra être émis à intervalle régulier de manière à indiquer la progression de la copie. Il devra également permettre au client d’annuler l’opération en cours
  • CopieFin : cet événement devra être émis en fin de processus en indiquant le statut final de l’opération (Succès, Echec, Annulation)

Pou mettre en place ce genre de choses, il faut passer par des Delegates:



  • CopieDemarrageEventHandler
  • CopieEnCoursEventHandler
  • CopieFinEventHandler

Notez que le plan de nommage utilisé en standard par le .NET Framework consiste à terminer le nom de ses Delegates par EventHandler. Une fois les Delegates déclarés, il faut ensuite déclarer les événements eux-mêmes. Les événements que nous allons déclarés auront pour type les Delegates précédents. Voici les déclarations que nous allons utiliser :


namespace Librairie_CS {


 public delegate void CopieDemarrageEventHandler(object sender, CopieDemarrageEventArgs e);
 public delegate void CopieEnCoursEventHandler(object sender, CopieEnCoursEventArgs e);
 public delegate void CopieFinEventHandler(object sender, CopieFinEventArgs e);


 public class GestionFichiers {


  public event CopieDemarrageEventHandler CopieDemarrage = null;
  public event CopieEnCoursEventHandler CopieEnCours = null;
  public event CopieFinEventHandler CopieFin = null;


Namespace Librairie_VB


    Public Delegate Sub CopieDemarrageEventHandler(ByVal sender As Object, ByVal e As CopieDemarrageEventArgs)
    Public Delegate Sub CopieEnCoursEventHandler(ByVal sender As Object, ByVal e As CopieEnCoursEventArgs)
    Public Delegate Sub CopieFinEventHandler(ByVal sender As Object, ByVal e As CopieFinEventArgs)


    Public Class GestionFichiers


        Public Event CopieDemarrage As CopieDemarrageEventHandler
        Public Event CopieEnCours As CopieEnCoursEventHandler
        Public Event CopieFin As CopieFinEventHandler


Notons au passage que la création de Delegate n’est pas obligatoire mais que le compilateur VB.NET en créera un de toute façon pour vous. Autant bien comprendre cette notion et les écrire nous même… Exemple :


<Aparté>
Imaginons que nous déclarions un Event en VB.NET comme suit :


        Public Event Test(ByVal monParam As Integer)


L’introspection de la DLL générée grâce à ILDASM, nous permet de découvrir que le compilateur a effectivement bossé pour nous en créant le Delegate directement au niveau du code IL :


   |   |   |___[CLS] TestEventHandler
   |   |   |   |     .class nested public auto ansi sealed
   |   |   |   |      extends [mscorlib]System.MulticastDelegate


puis


    |   |   |___[FLD] TestEvent : private class Librairie_VB.GestionFichiers/TestEventHandler


</Aparté>


Revenons maintenant à nos moutons. Vous avez certainement remarqué que la déclaration des signatures de mes Delegates (et donc de mes événements in fine) avait l’air de suivre une règle commune:



  • (object sender, xxxEventArgs e)
  • (ByVal sender As Object, ByVal e As xxxEventArgs)

Le premier argument appelé sender permet à l’abonné de l’événement de connaître précisément l’instance de l’objet qui a émis l’événement. A quoi cela peut-il servir me direz-vous ?


Et bien imaginons un instant que je possède deux instances d’un même objet, Instance1 et Instance2, et que je souhaite m’abonner à l’événement NouveauButPourLOM. Comme dans mon cas le traitement que j’ai à faire dans cet événement côté abonné est identique pour les deux instances, je me dis que cela serait sympa de n’utiliser qu’une seule et même méthode pour traiter les deux événements. Et bien l’argument sender m’a justement me permettre de savoir si l’événement a été émis par Instance1 ou Instance2. A noter que si nous décidions que notre méthode CopieFichier était statique (static ou Shared), ce premier argument n’aurait pas de sens et ne figurerait pas dans la signature.


Généralement, l’événement émis par une classe à destination d’une autre classe abonnée transporte avec lui un certain nombre d’informations qui pourraient être utiles à l’abonné. On pourrait être tenté de créer autant de paramètres à la signature de notre Delegate/Event que nécessaire. Ce n’est pas l’approche qui a été choisie par les développeurs du framework .NET.


Lorsqu’on souhaite déclarer un évenement qui n’a aucun paramètre particulier à transmettre, on utilise comme deuxième paramètre du Delegate/Event la classe System.EventArgs. Voici ce que la documentation indique sur cette classe :


EventArgs est la classe de base des classes contenant des données d’événement.Cette classe ne contient pas de données d’événement; elle est utilisée par des événements qui ne passent pas d’informations d’état à un gestionnaire d’événements lorsqu’un événement est déclenché. Si le gestionnaire d’événements nécessite des informations d’état, l’application doit dériver une classe à partir de cette classe pour contenir les données.


Et voilà tout est dit, dès qu’on veut passer un certain nombre d’informations à un Delegate/Event, on écrit une classe qui dérive de la classe EventArgs à laquelle on ajoute les informations supplémentaires. Vous avez noté que le nom de ces classes se terminent généralement par EventArgs.


Pour l’événement CopieDemarrage, ce dont on a besoin est simplement la taille du fichier à copier. Ainsi cela permettra d’être notifié de l’imminence du démarrage de la copie et de pouvoir ajuster les valeurs de notre gauge de progression en fonction de la taille du fichier à copier. De plus, il pourrait être utile de pouvoir récupérer les chemins complets des fichiers source et destination. Cela nous donne :


namespace Librairie_CS {


 public class CopieDemarrageEventArgs : System.EventArgs {


  private string fichierSource = string.Empty;
  public string FichierSource {


   get {


    return(this.fichierSource);
   }
  }


  private string fichierDestination = string.Empty;
  public string FichierDestination {


   get {


    return(this.fichierDestination);
   }
  }


  private long tailleFichier = 0;
  public long TailleFichier {


   get {


    return(this.tailleFichier);
   }
  }


  public CopieDemarrageEventArgs(string fichierSource, string fichierDestination, long tailleFichier) {


   this.fichierSource = fichierSource;
   this.fichierDestination = fichierDestination;
   this.tailleFichier = tailleFichier;
  }
 }
}


Namespace Librairie_VB


    Public Class CopieDemarrageEventArgs


        Inherits System.EventArgs


        Private privateFichierSource As String = String.Empty
        Public ReadOnly Property FichierSource() As String
            Get
                Return (Me.privateFichierSource)
            End Get
        End Property


        Private privateFichierDestination = String.Empty
        Public ReadOnly Property FichierDestination() As String
            Get
                Return (Me.privateFichierDestination)
            End Get
        End Property


        Private privateTailleFichier As Long = 0
        Public ReadOnly Property TailleFichier() As Long
            Get
                Return (Me.privateTailleFichier)
            End Get
        End Property


        Public Sub New(ByVal source As String, ByVal destination As String, ByVal taille As Long
            Me.privateFichierSource = source
            Me.privateFichierDestination = destination
            Me.privateTailleFichier = taille
        End Sub


    End Class


End Namespace


Pour l’événement CopieFin, ce dont on a besoin est simplement le statut final de la copie. On sait que la copie peut s’achever de trois manières différentes : Succes, Echec ou encore Annulation (par l’utilisateur). C’est un cas idéal de création d’une énumération :


namespace Librairie_CS {


 public enum StatutCopie {


  Aucun,
  Succes,
  Echec,
  Annulation
 }
}


Namespace Librairie_VB


    Public Enum StatutCopie
        Aucun
        Succes
        Echec
        Annulation
    End Enum


End Namespace


De plus, il peut être utile d’indiquer dans cet événement en combien de temps s’est déroulé l’opération de copie. Cela nous donne finalement ceci:


namespace Librairie_CS {


 public class CopieFinEventArgs : System.EventArgs {


  private StatutCopie statutCopie = StatutCopie.Aucun;
  public StatutCopie StatutCopie {


   get {


    return(this.statutCopie);
   }
  }


  private int duree = 0;
  public int Duree {


   get {


    return(this.duree);
   }
  }


  public CopieFinEventArgs(StatutCopie statutCopie, int duree) {


   this.statutCopie = statutCopie;
   this.duree = duree;
  }
 }
}


Namespace Librairie_VB


    Public Class CopieFinEventArgs


        Inherits System.EventArgs


        Private privateStatutCopie As StatutCopie = StatutCopie.Aucun
        Public ReadOnly Property StatutCopie() As statutCopie
            Get
                Return (Me.privateStatutCopie)
            End Get
        End Property


        Private privateDuree As Integer = 0
        Public ReadOnly Property Duree() As Integer
            Get
                Return (Me.privateDuree)
            End Get
        End Property


        Public Sub New(ByVal statutCopie As StatutCopie, ByVal duree As Integer)
            Me.privateStatutCopie = statutCopie
            Me.privateDuree = duree
        End Sub


    End Class


End Namespace


Finalement, pour CopieEnCours, il faudrait fournir le nombre d’octets déjà copiés au moment de l’émission de l’événement. Aucune difficulté particulière ici. Par contre, on souhaite également donner la possibilité à l’abonné de pouvoir annuler la copie à tout le moment. L’utilisation de cet événement pour permettre l’annulation de l’opération est alors l’idéal car appelé très régulièrement durant le processus de copie. Voici ce que cela donne :


namespace Librairie_CS {


 public class CopieEnCoursEventArgs : System.EventArgs {
 
  private long position = 0;
  public long Position {


   get {


    return(this.position);
   }
  }


  private bool cancel = false;
  public bool Cancel {


   get {


    return(this.cancel);
   }
   set {


    this.cancel = value;
   }
  }


  public CopieEnCoursEventArgs(long position) {


   this.position = position;
  }
 }
}


Namespace Librairie_VB


    Public Class CopieEnCoursEventArgs


        Inherits System.EventArgs


        Private privatePosition As Long = 0
        Public ReadOnly Property Position() As Long
            Get
                Return (Me.privatePosition)
            End Get
        End Property


        Private privateCancel As Boolean = False
        Public Property Cancel() As Boolean


            Get
                Return (Me.privateCancel)
            End Get
            Set(ByVal Value As Boolean)
                Me.privateCancel = Value
            End Set
        End Property


        Public Sub New(ByVal position As Long)
            Me.privatePosition = position
        End Sub


    End Class


End Namespace


Notez que dans ce cas, la propriété Cancel doit être en lecture ET écriture pour donner une chance à l’abonné de pouvoir modifier sa valeur.


A ce niveau là, on a fait le plus dur (et ce n’était pas bien dur, il faut bien l’avouer 😉 ). Il nous reste à modifier le code de la fonction CopieFichier pour déclencher l’émission des événements comme il faut. Cela donne ceci :


public void CopieFichier(string cheminFichierDestination, bool ecrasement) {


 // On vérifie si le fichier de destination n’existe pas déjà
 if (File.Exists(cheminFichierDestination)) {


  // Si demandé, on écrase le fichier de destination
  if (ecrasement) {


   File.Delete(cheminFichierDestination);
  }
  else {


   throw new IOException(string.Format(“Le fichier de destination ‘{0}’ existe déjà.”, cheminFichierDestination));
  }
 }


 StatutCopie statutCopie = StatutCopie.Aucun;
 int dureeCopie = System.Environment.TickCount;


 try {


  statutCopie = StatutCopie.Succes;


  // On ouvre le fichier de destination en écriture
  using (FileStream fileStreamDestination = new FileStream(cheminFichierDestination, FileMode.Create, FileAccess.Write)) {


   // On ouvre le fichier d’origine en lecture
   using (FileStream fileStreamOriginal = new FileStream(this.cheminFichier, FileMode.Open, FileAccess.Read)) {


    // On informe l’abonné de l’imminence de la copie
    if (CopieDemarrage != null) {


     CopieDemarrage(this, new CopieDemarrageEventArgs(this.cheminFichier, cheminFichierDestination, fileStreamOriginal.Length));
    }


    // On lit le prochain octet
    int octet = fileStreamOriginal.ReadByte();
    while (octet != -1) {


     // On écrit l’octet
     fileStreamDestination.WriteByte((byte)octet);


     // On informe l’abonné de la progression de la copie
     if (CopieEnCours != null) {


      CopieEnCoursEventArgs copieEnCoursEventArgs = new CopieEnCoursEventArgs(fileStreamOriginal.Position);
      CopieEnCours(this, copieEnCoursEventArgs);


      // L’utilisateur a-t’il demandé l’annulation de la copie ?
      if (copieEnCoursEventArgs.Cancel) {


       statutCopie = StatutCopie.Annulation;
       break;
      }
     }


     octet = fileStreamOriginal.ReadByte();
    }


    fileStreamOriginal.Close();
   }
   fileStreamDestination.Close();
  }
 }
 catch {


  // On renvoit telle quelle l’exception qui a lieu,
  // l’objectif étant simplement de pouvoir indiquer
  // correctement le statut de la copie
  statutCopie = StatutCopie.Echec;
  throw;
 }
 finally {


  dureeCopie = System.Environment.TickCount – dureeCopie;


  // On informe l’abonné de la fin de la copie
  if (CopieFin != null) {


   CopieFin(this, new CopieFinEventArgs(statutCopie, dureeCopie));
  }
 }
}


Public Sub CopieFichier(ByVal cheminFichierDestination As String, ByVal ecrasement As Boolean)


 ‘ On vérifie si le fichier de destination n’existe pas déjà
 If File.Exists(cheminFichierDestination) Then


  ‘ Si demandé, on écrase le fichier de destination
  If ecrasement Then
   File.Delete(cheminFichierDestination)
  Else
   Throw New IOException(String.Format(“Le fichier de destination ‘{0}’ existe déjà.”, cheminFichierDestination))
  End If
 End If


 Dim statutCopie As statutCopie = statutCopie.Aucun
 Dim dureeCopie As Integer = System.Environment.TickCount


 Try


  statutCopie = statutCopie.Succes


  ‘ On ouvre le fichier de destination en écriture
  Dim fileStreamDestination As New FileStream(cheminFichierDestination, FileMode.Create, FileAccess.Write)


  ‘ On ouvre le fichier d’origine en lecture
  Dim fileStreamOriginal As New FileStream(Me.privateCheminFichier, FileMode.Open, FileAccess.Read)


  ‘ On informe l’abonné de l’imminence de la copie
  RaiseEvent CopieDemarrage(Me, New CopieDemarrageEventArgs(Me.privateCheminFichier, cheminFichierDestination, fileStreamOriginal.Length))


  ‘ On lit le prochain octet
  Dim octet As Integer = fileStreamOriginal.ReadByte()


  While octet <> -1
   ‘ On écrit l’octet
   fileStreamDestination.WriteByte(CType(octet, Byte))


   ‘ On informe l’abonné de la progression de la copie
   Dim copieEnCoursEventArgs As copieEnCoursEventArgs = New copieEnCoursEventArgs(fileStreamOriginal.Position)
   RaiseEvent CopieEnCours(Me, copieEnCoursEventArgs)


   ‘ L’utilisateur a-t’il demandé l’annulation de la copie ?
   If copieEnCoursEventArgs.Cancel Then
    statutCopie = statutCopie.Annulation
    Exit While
   End If


   octet = fileStreamOriginal.ReadByte()
  End While


  fileStreamOriginal.Close()
  fileStreamDestination.Close()


 Catch


  ‘ On renvoit telle quelle l’exception qui a lieu,
  ‘ l’objectif étant simplement de pouvoir indiquer
  ‘ correctement le statut de la copie
  statutCopie = statutCopie.Echec
  Throw


 Finally


  dureeCopie = System.Environment.TickCount – dureeCopie


  ‘ On informe l’abonné de la fin de la copie
  RaiseEvent CopieFin(Me, New CopieFinEventArgs(statutCopie, dureeCopie))


 End Try


End Sub


Ceci étant fait, il ne nous reste plus qu’à compiler l’ensemble de ce code dans une assembly (Librairie_CS.dll Librairie_VB.dll).


Enfin, pour illustrer l’utilisation de ces événements depuis une IHM, nous allons créer un projet de type Application Windows et déposer deux Label, deux TextBox, deux Button et une ProgressBar :



Puis voici le code client :


namespace ClientWindows_CS {


 public class Form1 : System.Windows.Forms.Form {


  …
  


  private Librairie_CS.GestionFichiers gestionFichiers = null;
  private float taille = 0;
  private bool cancel = false;


  private void buttonCopie_Click(object sender, System.EventArgs e) {
  
   gestionFichiers = new Librairie_CS.GestionFichiers(textBoxFichierSource.Text);


   gestionFichiers.CopieDemarrage += new Librairie_CS.CopieDemarrageEventHandler(gestionFichiers_CopieDemarrage);
   gestionFichiers.CopieEnCours += new Librairie_CS.CopieEnCoursEventHandler(gestionFichiers_CopieEnCours);
   gestionFichiers.CopieFin += new Librairie_CS.CopieFinEventHandler(gestionFichiers_CopieFin);


   gestionFichiers.CopieFichier(textBoxFichierDestination.Text, true);
  }


  private void buttonAnnuler_Click(object sender, System.EventArgs e) {


   cancel = true;
   buttonAnnuler.Enabled = false;
   Application.DoEvents();
  }


  private void gestionFichiers_CopieDemarrage(object sender, Librairie_CS.CopieDemarrageEventArgs e) {


   cancel = false;
   taille = Convert.ToSingle(e.TailleFichier);
   progressBar1.Minimum = 0;
   progressBar1.Maximum = 100;
   progressBar1.Value = 0;
   buttonAnnuler.Enabled = true;


   Application.DoEvents();
  }


  private void gestionFichiers_CopieEnCours(object sender, Librairie_CS.CopieEnCoursEventArgs e) {


   e.Cancel = cancel;
   progressBar1.Value = Convert.ToInt32(Convert.ToSingle(e.Position) / taille * 100D);


   Application.DoEvents();
  }


  private void gestionFichiers_CopieFin(object sender, Librairie_CS.CopieFinEventArgs e) {


   progressBar1.Value = 0;
   buttonAnnuler.Enabled = false;
   MessageBox.Show(this, string.Format(“Statut : {0} – La durée du traitement a été de {1}”, e.StatutCopie.ToString(), (new TimeSpan(0, 0, 0, 0, e.Duree)).ToString()), “Copie finie”);
  }

}


Public Class Form1
    Inherits System.Windows.Forms.Form


    Private WithEvents gestionFichiers As Librairie_VB.GestionFichiers
    Private taille As Single = 0
    Private cancel As Boolean = False


    Private Sub buttonCopie_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles buttonCopie.Click
        gestionFichiers = New Librairie_VB.GestionFichiers(textBoxFichierSource.Text)


        ‘ Autre moyen pour s’abonner aux évenements :
        ‘ ——————————————-
        ‘AddHandler gestionFichiers.CopieDemarrage, AddressOf gestionFichiers_CopieDemarrage
        ‘AddHandler gestionFichiers.CopieEnCours, AddressOf gestionFichiers_CopieEnCours
        ‘AddHandler gestionFichiers.CopieFin, AddressOf gestionFichiers_CopieFin


        gestionFichiers.CopieFichier(textBoxFichierDestination.Text, True)
    End Sub


    Private Sub ButtonAnnuler_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ButtonAnnuler.Click
        cancel = True
        ButtonAnnuler.Enabled = False
        Application.DoEvents()
    End Sub


    Private Sub gestionFichiers_CopieDemarrage(ByVal sender As Object, ByVal e As Librairie_VB.CopieDemarrageEventArgs) Handles gestionFichiers.CopieDemarrage
        cancel = False
        taille = Convert.ToSingle(e.TailleFichier)
        progressBar1.Minimum = 0
        progressBar1.Maximum = 100
        progressBar1.Value = 0
        ButtonAnnuler.Enabled = True


        Application.DoEvents()
    End Sub


    Private Sub gestionFichiers_CopieEnCours(ByVal sender As Object, ByVal e As Librairie_VB.CopieEnCoursEventArgs) Handles gestionFichiers.CopieEnCours
        e.Cancel = cancel
        progressBar1.Value = Convert.ToInt32(Convert.ToSingle(e.Position) / taille * 100D)

        Application.DoEvents()
    End Sub


    Private Sub gestionFichiers_CopieFin(ByVal sender As Object, ByVal e As Librairie_VB.CopieFinEventArgs) Handles gestionFichiers.CopieFin
        progressBar1.Value = 0
        ButtonAnnuler.Enabled = False
        MessageBox.Show(Me, String.Format(“Statut : {0} – La durée du traitement a été de {1}”, e.StatutCopie.ToString(), (New TimeSpan(0, 0, 0, 0, e.Duree)).ToString()), “Copie finie”)
    End Sub


End Class


A noter l’utilisation du mot-clé WithEvents en VB.NET lors de la déclaration de la variable gestionFichiers pour permettre d’utiliser la technique d’abonnement Handles :


Private Sub gestionFichiers_[Event](ByVal sender As Object, ByVal e As Librairie_VB.EventArgs) Handles gestionFichiers.[Event]


A noter également qu’il y’a une autre possibilité pour s’abonner à un événement en VB.NET :


AddHandler gestionFichiers.[Event], AddressOf gestionFichiers


et


Private Sub gestionFichiers_[Event](ByVal sender As Object, ByVal e As Librairie_VB.EventArgs)


 


Voilà, j’espère que cet article vous a été utile pour comprendre la notion Delegate/Event. Si vous avez des commentaires à faire sur cet article, positifs comme négatifs, n’hésitez pas en m’en faire part grâce à la section commentaire ci-dessous.


 


A bientôt pour un autre sujet technique !


 


(les exemples de code utilisés dans cet article sont en libre téléchargement ici)


[Initialement posté le 21/09/2004 à 21:52 ici]

Comments (24)

  1. Icem@n says:

    Bonjour,

    Un bien bel exemple 😉

    Juste une question pourquoi la durée est-elle en format int et non directement renvoyée en TimeSpan y a t’il une raison particulière à cela ? ou est-ce juste pour simplifier l’exemple ? (car c’est quand même exceptionnel que l’on tombe dans la limitation des 24.9 Jours ;)).

    En tout cas je garderais l’url dans un coin de mes favoris.

    Bien à vous,

    Icem@n

  2. Salut,

    Non c’était plus pour garder le format d’origine de l’information (TickCount).

    Après y avoir refléchi un peu plus (suite à votre commentaire), je pense que je referais pareil, c’est-à-dire utiliser le type simple (int) pour stocker le nombre de milli-seconds, puis l’utilisation de TimeSpan pour manipuler cette notion de durée de manière un peu plus riche quand j’en ai besoin.

    Merci en tout cas pour vos encouragements,

  3. Félicitations !

    Un article trés clair et bien commenté. Du miel en barre quoi :)

    Chapeau Bas 😉

    Bonne initiative de traduire les Exemples en VB et C#

    Ah si, juste un truc, à quand le prochain article ? :)

  4. Tinou says:

    Vraiment un bon article sur les événements et delegates ! Du concentré de bonheur …!

    Si tout le monde prenait le temps d’expliquer comme c’est fait ici, je crois que l’ignorance deviendrai rare !

  5. Delf says:

    Très bon article. Hop, je le save sur mon disque, il me sera utile.

  6. Rafix says:

    Bonjour, très interessant comme article. Mais lorsque je test l’application je trouve que c’est extrèmenent lent comme transfert, est ce normal?

  7. FC says:

    Super article!

    Félicitation,

    Cordialement

  8. yass says:

    Bravo pour cet article…

    je debute en vb.net…

    je voulais mettre en place des evenements… Ben j’ai trouvé mon bonheur…

    Bon vent pour la suite

  9. Yannick says:

    [quote]Bonjour, très interessant comme article. Mais lorsque je test l’application je trouve que c’est extrèmenent lent comme transfert, est ce normal?

    [/quote]

    Je n’ai pas testé l’exemple mais peut être peux tu tester de faire une notification de copieEnCours tous les 5 octets au lieu de tous les octets ? (ca divise par 5 le nombre de communication Controle<->IHM)

    Ceci dit je ne pense quand même pas que copier un fichier (même de 10Mo) puisse réellement ralentir un système….

    D’autres personnes ont-elles ressenties le probleme ?

    Ps : en passant félicitations à l’auteur, je me suis mis A c# y’a quelques semaines et ma première question au bout de dix minutes était comment implémenter ce type de communication (selon le modèle MVC, Pac-amodeus et compagnie)… J’ai galéré sur le net pour trouver ma réponse alors cet article est extrémement bien fait ! Je reviendrais !

  10. Merci pour tes encouragements !

    L’exemple que j’ai pris ne sert qu’à cela, c’est à dire un exemple. Je ne garantis pas que c’est la manière la plus rapide de copier un fichier :-)

    La copie d’un fichier était simplement un très bon concept pour illustrer les Events/Delegates.

  11. Yves says:

    Merci beaucoup, C super, ça m’a bien aidé pour mon projet!!!

  12. Mathieu says:

    Bonjour,

    Pourrais tu me dire si une dll peut s’abonner a un evenement

    j’ai besoin d’un moyen pour que deux programmes communiquent, un qui est resident en memoire et l’autre qui est lance par une commande shell d’un autre programme.

    Je pensais donc utiliser une dll dans le style de ce que tu as proposé et de l’abonner a un evenement de ce programme cette dll generera ensuite un evenement pour le second programme resident en memoire

    Cela est il possible ou il y a t il une autre solution ?

    Merci

  13. Mathieu says:

    J’ai trouve une solution sans passer par les dll

    En reflechissant, l’idee des dlln’est pas tres bonne car meme si c’est possible, je vais faire deux appels distincts (un par le prog shell et l’autre par le prog resident) je vais donc avoir deux instances (je sais pas si c’est le bon nom) de dll dans la memoire, ce n’est pas tres propre

    je vais donc utiliser une technique avec l’evenement filewatch

    mais je suis toujours interesse par votre avis sur la communication avec dll …

  14. Geoffroy says:

    Très bon article, j’y vois plus clair maintenant.

    Merci

  15. zouihed says:

    comment on peut une function event en vb.net dans une instruction if , j’ai un probleme avec ça , il me dit qu’il faut utilise raiseevent avent le fuction event , et quant je le fait il me donne une erreur sur aiseevrnt , si qqun a un exemple , je serai tres reconnaissant de m’en envoyer , merci

    mah_zouihed@yahoo.fr

  16. Christophe says:

    Pourquoi implémenter systématiquement des classes dérivées d’EventArgs pour passer des paramètres? Est-ce une raison technique ?

    J’ai choisi l’option de "caster" le sender pour obtenir l’émetteur et toutes ces infos. En utilisant des interfaces, on peut restreindre l’info disponible et garantir le couplage faible entre couche. Est-ce un mauvais choix ?

  17. Laurent says:

    Superbe,

    Merci pour ton exemple qui est super clair, je crois même que j’ai compris.

  18. Florent says:

    Merci beaucoup pour cet exemple. c’est vraiment très intéressant et très clair!!!

    j’aurai juste une questiom sur le passage de paramètre à la form.

    dans le sub qui prend en charge l’évènement "CopieEnCours" (dans la form):

    Private Sub gestionFichiers_CopieEnCours(ByVal sender As Object, ByVal e As Librairie_VB.CopieEnCoursEventArgs) Handles gestionFichiers.CopieEnCours

           e.Cancel = cancel

           progressBar1.Value = Convert.ToInt32(Convert.ToSingle(e.Position) / taille * 100D)

           Application.DoEvents()

       End Sub

    Comment se fait-il que la valeur du e.cancel soit renvoyé à l’instance gestionFichier étant donné que le "e" qui est un  CopieEnCoursEventArgs est passé par Valeur et pas par Référence?

    la valeur e.cancel est renvoyée et utilisée dans la sub "Public Sub CopieFichier" et prend bien en compte une possible modification de sa valeur(dans la classe GestionFichiers):

      Dim copieEnCoursEventArgs As copieEnCoursEventArgs = New copieEnCoursEventArgs(fileStreamOriginal.Position)

      RaiseEvent CopieEnCours(Me, copieEnCoursEventArgs)

      ‘ L’utilisateur a-t’il demandé l’annulation de la copie ?

      If copieEnCoursEventArgs.Cancel Then

    J’avoue ne pas bien comprendre comment cela est possible, pourtant cela fonctionne! Est-ce que tu pourrais m’éclairer?

  19. Pascal Belaud says:

    Coucou Florent,

    Merci pour tes encouragements !

    Alors en effet, le passage est fait par valeur (par défaut) mais là, ce qui est passé par valeur, ce ne sont pas les valeurs des propriétés de e, c’est le pointeur mémoire vers e ! Du coup, on se retrouve avec deux pointeurs (celui avant l’appel et celui à l’intérieur de l’appel), et ces deux pointeurs pointent justement vers les mêmes propriétés :-) C’est donc comme ça que les propriétés sont préservées.

    J’espère que je suis clair !

    Pascal

  20. Florent says:

    Merci beaucoup Pascal!

    là je comprends beaucoup mieux.

    encore bravo pour ton code vraiment très utile et très bien expliqué.

    Florent