SOMMAIRE
I) GENERALITES
I.1) La classe "form1"; le formulaire principal
I.2) Des objets créés par visual studio
I.3) Les variables qui contiennent des références
I.4) Le fonctionnement du garbage collector
II) CREER SES PROPRES EVENEMENTS EN VB .NET
II.1) Rappel sur les procédures liées( aux évènements)
II.2) La procédure à suivre pour créer ses évènements
COURS VISUAL BASIC .NET 2005
I) GENERALITES
Ceci n'est pas un cours classique. Je veux dire que vous ne trouverez pas ici un cours qui vous apprendra visual basic .net du début à la fin. Ce cours s'adresse à des gens qui connaisse déjà ce langage, ou à des gens qui sont en train de l'apprendre sur un autre cours. Mon ambition est juste d'éclaircir certains points de visual basic .net qui ne sont pas précisés ailleurs, et qui sont très utiles pour comprendre véritablement VB. En apprenant moi-même Visual Basic, à certains moments il m'était difficile d'assimiler certaines choses à cause d'un manque d'explications données sur, par exemple, la façon dont se passent les choses réellement.
Ce cours est donc un fourre-tout, où vous trouverez des précisions sur un peu tous les sujets de visual basic. A vous donc d'y chercher ce dont vous avez besoin. Je le complèterai petit à petit.
I.1) La classe "form1"; le formulaire principal
Lorsque vous créez une windows application dans visual studio 2005, visual studio vous crée par défaut une classe qu'il appelle ( par défaut ) form1. Vous vous retrouvez donc avec un code de cette classe form1.
Cependant, une question intéressante est de s'étonner qu'une simple classe va créer et afficher un formulaire windows! Car en fait, créer une classe, c'est juste une déclaration d'une classe, et c'est tout. Cela ne fait pas plus que cela. J'imagine que beaucoup de débutants pensent que c'est ce code de la classe form1 qui, exécuté, va afficher le formulaire windows! Et bien non, ce n'est pas cela. Le code de la classe form1 sert uniquement à définir une classe, comme n'importe quelle définition de classe.
Visual studio va, en réalité, ajouter du code supplémentaire qui va, d'abord, créer un objet de cette classe. Donc un objet formulaire. Puis il va afficher cet objet. Il ajoute donc l'équivalent des lignes suivantes( ce code n'est pas situé à l'intérieur de la classe form1, mais dans le programme principal ):
Dim formu
as new form1
formu.show( )
Et voilà, voici l'explication de comment cela se fait qu'un formulaire s'affiche quand on exécute l'application!
I.1.1) Comment récupérer l'objet formulaire
Il existe un problème: on ne connaît pas le nom de cet objet formulaire, que VB crée de manière transparente! Il se peut qu'il l'appelle aussi "form1", car la propriété "name" de la classe form1, contient "form1". Mais cela ne nous avance pas beaucoup, car quand on tape "form1" dans l'éditeur(EDI), visual croit qu'on parle de la classe form1. Donc aucun moyen ( à ma connaissance ) d'accéder aux membres d'instance( c'est-à-dire aux membres non shared ) de l'objet formulaire, en dehors de la classe form1. Car dans la classe form1, on peut accéder aux membres d'instance car on se trouve dans la classe de l'objet formulaire.
La solution que j'ai trouvée est de déclarer une variable globale dans un module standard.
Par exemple, dans un module standard module1, on crée une variable globale appelée "formu_pr"( formulaire principal). On crée le module standard avec add new item, et en choisissant module.
Puis on déclare la variable globale:
Public formu_pr
as form1
Ainsi, on déclare une variable globale formu_pr, qui est capable de contenir un objet de la classe form1.
On peut dire aussi que formu_pr est une référence sur un objet de la classe form1. C'est-à-dire que formu_pr contient l'adresse d'un objet de la classe form1. Cette variable contiendra l'adresse du formulaire principal.
Ensuite, il suffit d'ajouter dans le form1_load: formu_pr = me .
Ainsi, dès le démarrage du programme, la variable formu_pr sera initialisée avec l'objet formulaire principal. Car, à l'exécution, visual va d'abord créer un objet formulaire( code caché):
dim formu
as new form1.
Puis il va faire( code caché):
formu.show( )
Le formu.show va exécuter d'abord formu.load( avant d'afficher le formulaire à l'écran). Donc il va initialiser notre variable globale: formu_pr = formu( car me=formu, ici). Donc formu_pr contiendra bien, dès l'exécution du programme, l'objet formulaire principal!
Désormais, dans toute notre application, on pourra utiliser formu_pr comme bon nous semblera, afin d'accéder aux membres non partagés( non shared) du formulaire principal, en dehors de la classe form1. Simplement en faisant formu_pr.propriété( par exemple formu_pr. size ), ou formu_pr.méthode( par exemple formu_pr.activate( ) ) . Sans notre variable formu_pr, il nous aurait été impossible d'accéder à la propriété size du formulaire, ou à sa méthode activate( en dehors de la classe form1).
I.2) Des objets créés par visual studio
Il existe un point intéressant à souligner. Certaines méthodes de visual basic .net créent des objets elles-mêmes. Puis elles nous renvoient la référence de cet objet créé( afin qu'on puisse en profiter, par exemple le lire). Donc, dans ce cas, il n'y a pas de new à faire! Car c'est visual basic qui va faire ce new. Je suppose que le débutant ne sait jamais quand il doit faire un new ou pas. En comprenant ceci, cela l'aidera beaucoup. C'est en comprenant cela qu'on sait alors s'il faut faire le new( donc la création d'objet) ou pas.
Prenons un exemple: la fonction shared system.enum.getvalues .
L'info bulle jaune de l'environnement de visual studio nous indique: Public Shared Function GetValues(enumType As System.Type) As System.Array .
C'est donc une méthode de la classe enum. Cette méthode est partagée, c'est à dire qu'elle est accessible sans avoir d'objet, en faisant system.enum.GetValues . Cette fontion sert à donner, pour une certaine énumération, toutes les valeurs possibles de cette énumération. Par exemple, une énumération EtatVoiture
Enum EtatVoiture
ARRET
DEMARRE
ROULE
FREINE
End Enum
Puis, à un autre endroit:
Dim etat
as EtatVoiture
Lorsqu'on va faire :
Dim tab
as system.array
tab = system.enum.GetValues( etat.GetType( ) )
console.writeline( tab(0).tostring( ) )
Ceci va afficher "ARRET" dans la console.
tab(0) est de type EtatVoiture. Donc en faisant tab(0).tostring( ), on applique la méthode tostring() particulière à la classe enum. Tab(0).tostring( ) vaut dont une string "ARRET".
Ce qui est à retenir de cet exemple, c'est que le programmeur n'a pas dû créer le tableau tab avec un new. Il a juste créé une variable référence, qui contient l'adresse du tableau en mémoire. Il n'a pas fait de new dans Dim tab as system.array.
On remarque donc que c'est la méthode GetValues qui a créé un objet de la classe system.array . Elle a donc fait le new elle-même!
Il est important de prendre conscience qu'il arrive que certaines fonctions ou procédures de visual basic créent des objets toutes seules. Ce qui peut paraître un peu étrange. Il ne faut donc pas faire de new dans ce cas.
I.3) Les variables qui contiennent des références
Prenons l'exemple suivant:
Dim voit
as new voiture
Bien sûr, on peut affirmer que "voit" est un objet voiture, qui a été créé avec new( par exemple à l'adresse 10000, adresse purement fantaisiste).
Mais "voit" est aussi une variable locale qui est créée, et qui a été initialisée avec la référence à l'objet voiture, c'est à dire l'adresse de l'objet voiture, soit 10000.
Donc, sous le nom "voit", il y a deux choses distinctes
- "voit" est l'objet de la classe voiture, situé à l'adresse 10000
- "voit" est une variable locale, qui est créée par visual basic, et à laquelle il affecte le nombre 10000.
Selon le contexte, on parle soit de l'un, soit de l'autre. Mais il faut bien comprendre que "voit" peut désigner deux choses distinctes. Le programmeur, notamment débutant, n'a pas toujours conscience de cela. Et il ne distingue pas clairement, dans son esprit, ces deux éléments. Souvent, il ne pense qu'à la voiture "voit" elle-même, sans avoir compris qu'il existe également une variable locale qui a été créée, et à qui on a affecté la référence de l'objet "voit".
I.4) Le fonctionnement du garbage collector
I.4.1) Définition générale
Le langage visual basic 2005 .net possède un garbage collector. C'est une technologie qui nous permet de ne pas avoir à libérer( comme en langage C, par exemple) la mémoire qu'on nous a allouée dynamiquement( c'est à dire lors d'un new ).
Ce système évite les oublis de libération de mémoire, qui sont un souci constant pour le programmeur C, et qui peuvent être catastrophiques.
De plus, certaines classes, qui sont l'objet d'allocation importante de mémoire, comme la classe image, possèdent une méthode dispose. Cette méthode permet de libérer nous-même la mémoire, sans avoir à attendre le garbage collector.
Par conséquent, le système utilisé par visual basic quant à la libération de la mémoire cumule les avantages des deux façons de procéder pour libérer la mémoire.
I.4.2) Fonctionnement détaillé
Cependant, une question se pose, qui me laissait perplexe. D'accord, le garbage collector libère la mémoire dont on n'a plus besoin. Mais comment s'y prend t-il pour savoir qu'on n'a plus besoin de cette mémoire, étant donné qu'on ne lui demande jamais de la libérer?
Le principe est le suivant. Prenons l'exemple qui suit.
public sub form1_load( ... )
ma_procedure( ) 'Appel de ma_procedure
'Après l'appel, la variable locale 'voit' n'existe plus
'Donc l'objet voit, n'étant plus référencé par aucune
'variable de référence, est détruit par le garbage
'collector
end sub
Public sub ma_procedure( )
Dim voit as new voiture( )
End Sub
Dans la sub ma_procedure, un objet de la classe voiture est créé en mémoire. Puis une variable locale voit est créée, et elle est initialisée avec la référence de l'objet voiture. Dans form1_load, on appelle ma_procedure. Après l'appel, la variable locale voit est détruite( car on quitte la sub qui l'a créée ). Et l'objet de la classe voiture n'est alors plus référencé par aucune variable. Et il ne pourra plus être utilisé, si on ne sait pas où il se trouve! Le garbage collector peut alors en déduire que c'est un objet qui n'est plus utilisé, et il peut le détruire.
Conclusion: le garbage collector détruit les objets qui ne sont plus référencés par aucune variable de référence.
Remarque: il est aussi possible de détruire certains objets 'manuellement'. L'avantage est que la libération de la mémoire se fait plus rapidement, dès qu'on le demande. Et on sait aussi quand elle a lieu. Ceci est le cas des objets qui ont une méthode dispose( ). Ainsi, la classe image a une méthode dispose( ), qui, si elle est appelée, libère l'espace mémoire qui était utilisé par l'image. Visual basic a prévu une méthode dispose pour les gros objets, comme les images. La classe system.windows.forms.control( celle dont hérite tous les contrôles) possède une méthode dispose( ) également.
II) CREER SES PROPRES EVENEMENTS EN Visual Basic .NET
Il est bon de savoir créer ses propres évènements, afin de pouvoir faire sa propre programmation évènementielle.
Il est possible de réaliser cela en Visual Basic .net, mais aussi en C# et en c++ .net . Voici les différentes étapes à suivre.
Prenons l'exemple d'une classe voiture, qui crée des objets voiture. On veut pouvoir disposer d'un évènement FREINE, qu'on déclenchera au freinage d'une voiture.
II.1) Rappel sur les procédures liées( aux évènements)
On peut observer très facilement, par rapport aux lignes générées automatiquement par visual studio en cas d'évènement windows, que la procédure liée est de la forme suivante
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Ceci est donc une procédure qui sera appelée en cas de click sur le bouton1.
Le 'Handles' est un mot-clé spécifique à VB, qui permet d'indiquer qu'on souhaite lier cette procédure à l'évènement click du Button1. 'Button1.Click' veut dire: l'évènement Click de l'objet Button1. Car l'objet Button1 possède un membre event( nous verrons bientôt ce qu'est un membre event), qui s'appelle Click, mais qui n'est pas visible en VB par le programmeur de l'objet Button1.
Cette procédure Button1_Click possède deux paramètres.
- sender, qui est un objet. sender est l'objet qui a émis l'évènement. L'émetteur de l'évènement peut y mettre ce qu'il veut. C'est juste une information pour la procédure liée, pour la renseigner. Ici, le sender est l'objet button1, donc on n'a pas l'impression que ce paramètre est utile. Mais parfois, il est nécessaire. Par exemple, dans notre cas, on pourrait y mettre la voiture qui freine.
- L'argument e est un objet, qui encapsule toutes les informations intéressantes concernant l'évènement. Par exemple, dans le cas d'un appui sur une touche, le code de la touche pressée.
Dans notre cas, cela peut être les circonstances au moment du freinage( pluie, soleil, etc), la vitesse au moment de freiner, etc.
Cet argument doit forcément être d'une classe qui hérite de la classe System.EventArgs . Par exemple, dans le cas de l'évènement KeyDown:
Private Sub Form1_KeyDown(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles Me.KeyDown
Le e ici est de la classe KeyEventArgs, qui est une classe qui hérite de la classe EventArgs. La classe KeyEventArgs est spécialement adaptée pour cet évènement. Elle possède notamment un attribut e.KeyCode, qui est le code de la touche pressée. Ce KeyCode ne figure pas, par contre, dans la classe EventArgs. Cet objet e est là pour aider la procédure liée. Pour lui donner des informations que l'évènement KeyDown seul, ne suffit pas à donner( il nous indique juste qu'une touche a été enfoncée). Le e est donc nécessaire.
II.2) La procédure à suivre pour créer ses évènements
Pour créer ses propres évènements, la façon de procéder est composée de plusieurs parties.
- Créer sa propre classe EventArgs
- Se créer une classe EmetEvent, qui permettra de créer un objet émetteur d'évènements.
- Procéder à quelques déclarations et initialisations, à l'extérieur, par exemple dans la classe Form1( votre classe de formulaire principal). Par exemple se créer un objet émetteur d'évènements.
Nous étudierons un exemple complet, celui des objets voiture et de l'évènement FREINE.
II.2.1) Créer sa propre classe EventArgs
Nous devons tout d'abord nous créer notre propre classe EventArgs, qui tient compte des particularités de notre évènement. Faisons la dans un module de classe appelé VoitEventArgs.vb
Public Class VoitEventArgs
Inherits System.EventArgs
Public type_freinage As String 'pluie, etc
Public vitesse_fr As Integer 'vitesse au moment de freiner
Public Sub New( )
End Sub
Public Sub New(ByVal type_fr AsString)
Me.type_freinage = type_fr
End Sub
End Class
Notre classe VoitEventArgs hérite de la classe System.EventArgs, bien sûr.
Puis nous avons les 2 propriétés type_freinage et vitesse_fr, qui vont donner au programmeur des renseignements sur l'évènement FREINE. Enfin nous avons 2 constructeurs de la classe. L'objet e nous concernant sera donc une instanciation de la classe VoitEventArgs.
II.2.2) Créer une classe EmetEvent
Créons cette classe dans un module de classe EmetEvent.vb .
Cette classe servira à créer un objet émetteur d'évènement. Cet objet sera utilisé pour émettre l'évènement FREINE. Et un de ses membres sera un event FREINE.
On ne peut émettre des évènements qu'à partir de la classe qui contient le membre event. C'est pour cela que pour émettre des évènements FREINE, nous prévoirons des méthodes à l'intérieur de l'objet émetteur d'évènements. Ainsi la méthode emet_freine_pluie émet un évènement FREINE, avec un e.type_freinage à "pluie".
On pourra appeler cet méthode de n'importe où, donc émettre des évènements de n'importe où.
Le constructeur de EmetEvent commence par créer un objet e_voit( une des propriétés de EmetEvent).
VoitEventHandler est en réalité une classe qui hérite de la classe System.EventHandler . Les objets EventHandler contiennent les adresses des procédures liées aux évènements. Ainsi le framework saura quelles méthodes appeler dans le cas du déclenchement de l'évènement.
Les procédures liées mises dans les objets de la classe EventHandler doivent avoir obligatoirement 2 paramètres: un sender de type object, et un objet e d'une classe dérivée de la classe EventsArgs. Et ces procédures doivent être des sub, par conséquent ne rien retourner.
Cependant, le VoitEventHandler est déclaré dans la classe comme une procédure "déléguée". Et on fait une sorte de déclaration de son prototype. Cette notion de déléguée est juste une vue de l'esprit, pour simplifier les choses. VoitEventHandler n'est pas une procédure en réalité, mais un objet( quand la classe sera instanciée) contenant toutes les adresses des procédures liées. On peut voir VoitEventHandler comme UNE procédure déléguée, représentant toutes les procédures liées. Et on déclare ainsi le prototype de cette procédure déléguée. Grâce à cet déclaration de prototype, le framework saura le prototype de toutes les procédures liées( qui auront toutes ce prototype).
Je vous conseille de voir l'objet de la classe VoitEventHandler comme un objet de la classe System.EventHandler( un 'traiteur' d'évènements ); et de ne pas voir VoitEventHandler comme une procédure, car ce n'en est pas une. D'ailleurs les membres event, tel FREINE, sont déclarés comme des objets de la classe EventHandler( dans notre cas de la classe VoitEventHandler).
On déclare ensuite un membre event. Event n'est pas un type, c'est un genre de membre. Il y a les attributs, les méthodes, et les events! . On appelle cet event: FREINE. Cet event est un objet de la classe VoitEventHandler. Ainsi, un évènement, est juste un objet traiteur d'évènements, qui contient les références de toutes les procédures liées. Ce qui est logique. Attention, en Visual Basic, les membres event, même public, ne sont pas visibles( par exemple en utilisant intellisense, vous ne les verrez pas). Mais ils sont tout-de-même présents.
Enfin, on se prévoit des méthodes, telles emet_freine_pluie, qui sauront capables d'émettre des évènements de n'importe où dans notre programme. Et avec un objet e rempli d'une manière qui nous arrange. Emet_freine_pluie, par exemple, non seulement émet un évènement FREINE, mais en plus fournit un e avec le champ e.type_freinage = "pluie". On pourra appeler par exemple emet_freine_pluie à partir de la form1!
RaiseEvent est spécifique à VB. Il permet de déclencher un évènement, ici FREINE.
Public Class EmetEvent
Public Delegate Sub VoitEventHandler(ByVal sender AsObject, ByVal e As VoitEventArgs)
Public Event FREINE As VoitEventHandler
Public e_voit As VoitEventArgs = New VoitEventArgs( )
Public Sub New( )
End Sub
Public Sub emet_freine_pluie(ByVal sender AsObject)
e_voit.type_freinage = "pluie"
RaiseEvent FREINE(sender, Me.e_voit)
End Sub
Public Sub emet_freine_soleil(ByVal sender As Object)
e_voit.type_freinage = "soleil"
RaiseEvent FREINE(sender, Me.e_voit)
End Sub
End Class
II.2.3) Les déclarations à l'extérieur
- Se créer un objet émetteur d'évènement, de la classe EmetEvent, par exemple comme attribut de la classe Form1.
Public WithEvents Emetteur_Ev_Voit As New EmetEvent( )
Il est nécessaire de déclarer en WithEvents l'objet Emetteur_Ev_Voit. Etant donné que c'est un objet capable de déclencher des évènements, VB .net oblige à le déclarer avec le mot clé 'WithEvents'.
Puis, dans le form1_load(ou autre), si cela n'a pas été fait comme ci-dessus::
this.Emetteur_Ev_Voit = newEmetEvent( );
- Dans le cas où vous souhaitez lier "à la main" votre procédure, sans utiliser le "handles" de VB( sinon ne faites rien):
Dans le form1_load, par exemple, ajouter notre évènement à notre objet de la classe VoitEventHandler( donc au membre event FREINE).
AddHandler Emetteur_Ev_Voit.FREINE, AddressOf Me.ma_sub_liee
On ajoute( "Add") à notre membre FREINE, qui est un objet VoitEventHandler, l'adresse de la procédure liée, c'est donc un pointeur de procédure.
Vous pouvez ajouter, de cette manière, autant de procédures liées que vous le souhaitez.
- Ensuite, on peut déclencher les évènements comme bon nous semble, en appelant les méthodes de la classe EmetEvent
Me.Emetteur_Ev_Voit.emet_freine_pluie(Me)
le 'Me' en paramètre correspond au formulaire principal de la classe form1, dans mon exemple. On aurait pu mettre la voiture qui freine.
- Et bien sûr, il faut se faire ses procédures liées, exactement de la même manière que pour les évènements windows.
Public Sub ma_sub_liee(ByVal sender As System.Object, ByVal e As VoitEventArgs) Handles Emetteur_Ev_Voit.FREINE
Console.WriteLine("1 évènement émis")
Console.WriteLine("e.type_freinage: " & e.type_freinage)
Console.WriteLine("le sender est:" & sender.ToString( ))
End Sub