|
Une remarque pertinente ? Une critique impertinente ? Un lynchage en règle ? Une invitation sous les tropiques ? ![]() Ecrivez-moi ! |
![]() |
||||
|
Conçu et enseigné tel qu'en lui même, avec pertes, fracas et
humour de qualité supérieure par Christophe Darmangeat dans le M2 PISE du Master SSAMECI (Université Paris 7) |
|||||
|
Partie 6
Les collections
Jusqu'à présent, nous avons toujours considéré les
contrôles individuellement. Un contrôle, un nom de contrôle, et au moins
une ligne de code. Dix contrôles
à traiter, au moins dix lignes de code. Cinquante contrôles à traiter, au
moins cinquante lignes. Pas moyen d'y couper.
La seule combine qui nous a permis, dans certaines
circonstances, de faire une entorse à ce principe, a été de brancher le
même événement survenant sur plusieurs contrôles, sur une procédure
événementielle unique, via l'instruction Handles. Ceci nous a également
permis de manipuler les propriétés du contrôle ayant appelé la procédure,
via le paramètre sender.
Ce que nous allons voir à présent, c'est comment on peut généraliser
les occasions de traiter les contrôles en série, et plus seulement pièce
par pièce.
1. La notion de Collection Dans les versions précédentes de Visual Basic,
existait un outil que tout programmeur débutant pouvait maîtriser en
quelques coups de cuiller à pot, et qui rendait des services
inestimables : les tableaux de contrôles. Cela fonctionnait de la même
manière que les tableaux de variables, et cela ouvrait les mêmes
possibilités.
Bref, c'était tellement simple, tellement intuitif et
tellement efficace que dans la dernière version de VB, à savoir VB.Net,
celle-là même que vous êtes en train d'étudier, bande de petits
veinards, eh bien les tableaux de contrôles ont tout simplement
disparu.
En lieu et place, nous voilà à présent pourvus d'une
série d'ustensiles et d'un bric à brac plus ou moins heureux et
maniable, dans lequel nous allons devoir piocher tant bien que mal en
fonction des circonstances. Au passage, si quelqu'un de chez Microsoft
comprend la logique de cette évolution du langage, et qu'il se sent
capable de me l'expliquer, qu'il ne se gêne surtout pas. Il a
néanmoins intérêt à être sacrément convaincant, parce qu'autant lui
dire tout de suite, ce n'est pas gagné.
Bref, exit les tableaux de contrôles, que nous
reste-t-il ? Un machin qui s'appelle les collections,
machin qui se rapproche furieusement des tableaux de contrôles...
sans jamais leur ressembler complètement.
Commençons par dire que par définition, sans même
que nous ayons besoin de faire quoi que ce soit pour cela, tout
contrôle posé sur une Form devient aussi sec un membre d'une
collection, en l'occurrence la collection Controls. C'est-à-dire
que ce contrôle devient le membre d'un
ensemble, que nous allons traiter comme tel, et sur lequel
nous allons pouvoir programmer des boucles. Car il est possible
de balayer l'intégralité des membres d'une collection par une boucle,
tout comme on peut balayer l'intégralité des éléments d'un tableau.
Sauf que tout n'est pas toujours exactement simple dans cette affaire...
2. Désigner les contrôles par leur indice La manière la plus évidente de désigner les contrôles
dans une collection, va être de se référer à leur indice dans cette
collection.
"Faudrait savoir !" Tel est le cri que
j'entends déjà sourdre parmi les masses estudiantines opprimées, suite
à la lecture de la phrase précédente. En effet, il y a à peine
quelques paragraphes de cela, j'expliquais que les tableaux de
contrôles n'existaient plus en VB.Net. Et là, je me ramène avec des
collections où les contrôles sont désignés par des indices...
Alors, incompétence manifeste ? Absence totale de conscience professionnelle ? Alzheimer précoce ? Schizophrénie
caractérisée ?
Rien de tout cela. Dans la collection Controls, les
contrôles possèdent certes un indice, et cela constitue un point
commun avec les tableaux. Mais il reste tout de même de sérieuses
différences :
En l'occurrence, pour balayer les contrôles d'une
Form, on pourra donc faire une boucle sur l'indice de ces contrôles
dans la collection Controls. Le premier indice est par définition 0.
Quant au dernier, nous pouvons le déduire de la propriété Controls.Count,
qui renvoie le nombre de contrôles dans la collection Controls.
Dans la forme la plus étendue du code, il faudra, pour accéder à
l'indice, passer par la propriété Item. Le
code permettant par exemple de rendre visible tous les contrôles d'une Form sera donc :
For i = 0 to Controls.Count - 1
Controls.Item(i).Visible = True Next i Le plupart des collections - car je le rappelle, il
existe bien d'autres collections que la collection Controls - autorisent toutefois
l'omission de la propriétés Item. On peut donc
souvent écrire en abrégé, et
pour le même résultat :
For i = 0 to Controls.Count - 1
Controls(i).Visible = True Next i Tous les contrôles membres d'un contrôle conteneur, tel Groupbox ou Panel, sont d'office membres de la collection que représente ce conteneur. Une ruse peut donc consister à placer sur la Form un conteneur (au besoin invisible) dans le seul but de pouvoir traiter les contrôles qui s'y trouvent comme une collection. Les contrôles membres d'un contrôle conteneur ne sont du même coup plus considérés comme membres de la collection Controls ! En fait, les contrôles d'une Form sont organisés en quelque sorte dans des collections de différents niveaux. Dit d'une autre manière, les différentes collections d'une Form représentent une véritable arborescence, dont la collection Controls n'est que la racine. Voilà une remarque à méditer longuement. Bon. Certes. Mais il y a d'autres moyens de parcourir
une collection...
3. La boucle For Each ... Next Cette structure de boucle permet spécifiquement de
parcourir une série de contrôles. Tout repose sur le fait que la
variable qui va servir de compteur à la boucle n'est ici plus un
nombre, comme dans toutes les boucles For ... Next
que nous avons écrites jusque là, mais que
cette variable représente directement un contrôle.
Autrement dit, la boucle va se servir d'une variable qui va désigner
successivement plusieurs contrôles. Donc d'une variable de type
Control.
Le code précédent pourra ici être réécrit comme :
For Each Truc in Controls
Truc.Visible = False Next Truc La variable Truc étant de type Control, sa
déclaration aura auparavant été :
Dim Truc as Control
En soi, et sur l'exemple choisi, cette technique
n'apporte rien de spécial par rapport à la précédente. Mais comme on
le verra plus loin, il y a des circonstances où elle révèlera des
avantages notables.
Il faut noter que dans le cas où la
collection parcourue ne comporte que des contrôles de la même classe, on
peut tout à fait déclarer la variable objet directement comme un membre de
la classe en question. Ainsi, au lieu d'avoir un :
Dim Truc as Control
On pourrait très bien déclarer Truc comme Label :
Dim Truc as Label
ou comme bouton :
Dim Truc as Button
...etc.
Encore une fois, cela suppose que tous les contrôles de la collection soient
bien de la classe spécifiée. Dans le cas contraire, il y aura forcément une
erreur à l'exécution...
4. Tester le type d'un contrôle Avançant à l'aveuglette parmi les membres divers et
variés de la collection Controls de la Form (ou parmi toute autre sous-collection,
telle celle d'un contrôle conteneur), nous pourrions bien avoir parfois
besoin d'un moyen pour identifier ne serait-ce que la classe des ces
différents membres. C'est possible grâce à l'instruction
Typeof ... Is
Par exemple, le code suivant met à blanc toutes les
zones de textes (et uniquement elles) d'une Form :
For Each bidule in Controls
If TypeOf bidule is Textbox Then bidule.Text = "" Endif Next bidule Jouons donc un peu...
On peut donc reprendre le dernier exercice proposé
(Options) et utiliser les connaissances que nous venons d'acquérir
afin de faire subir au code une cure d'amaigrissement.
5. Créer ses propres collections par du code Il y a la collection Controls, créée et gérée
automatiquement par VB, avec toutes les limites que nous venons de
découvrir. Il y a les collections que constituent automatiquement les
contrôles dits "conteneurs" (tels que GroupBox ou Panel).
Mais il existe aussi la possibilité de
créer ses propres
collections par du code. Bien que cela soit un peu fastidieux, c'est parfois le seul
moyen de gérer correctement certains problèmes.
5.1 Déclarer une collection La première chose à faire sera de déclarer la
collection, en lui donnant un nom (exactement comme on déclare une
variable, ou un tableau). Par exemple :
Dim Ensemble as New Collection()
5.2 Remplir et vider une collection Ensuite, il va falloir entrer dans cette collection,
un par un, tous les éléments qui devront en faire partie, via la
méthode Add. Par exemple :
Ensemble.Add(Me.Textbox1)
Ensemble.Add(Me.Textbox2) Ensemble.Add(Me.Label5) etc. Disons tout de suite qu'on peut, de même, retirer un
contrôle d'une collection par la méthode Remove :
Ensemble.Remove(Me.Label5)
Arrivés à ce stade, nous savons donc gérer comme bon
nous semble les membres d'une collection. Cela nous permet notamment
de pouvoir procéder sans problèmes à des balayages systématiques (par
exemple pour initialiser les valeurs de toute une série de
contrôles, pour les rendre visibles ou invisibles, etc.). Cela nous
permet également de désigner chaque contrôle membre de notre
collection par son indice dans cette collection, exactement comme nous
le faisions avec la collection prédéfinie Controls. Enfin... presque !
Les concepteurs de Visual Basic .Net ont décidé que si les indices des collections prédéfinies, telle Controls, commençaient en toute logique à zéro, les indices des collections créées par le programmeur commenceraient quant à eux à 1 ! Si le gars de chez Microsoft dont j'ai parlé tout à l'heure m'appelle, tant qu'il y est, qu'il prépare aussi un argumentaire pour m'expliquer cela. Et qu'il fasse preuve d'imagination... 5.3 "Brancher" les événements sur une procédure unique Passons. Un autre avantage considérable de disposer
de tableaux de contrôles, était de pouvoir traiter un même événement
survenant aux différents membres du tableau avec une seule procédure.
Dans l'état actuel de nos connaissances, c'est certes possible, via
l'instruction Handles qui figure dans les titres des procédures : il suffit de faire
suivre cette instruction Handles de la liste complète des membres de
la collection. C'est d'ailleurs cette technique que nous avons déjà eu
l'occasion d'utiliser dans plusieurs exercices, sans même pour autant
avoir créé de collection. Toutefois, cette combine a des limites :
La parade à ces problèmes tient dans la possibilité
de rattacher par du code, donc en cours d'exécution, tel événement
survenant à tel contrôle, à telle procédure existante. C'est le sens
de l'instruction AddHandler, qui donnera par exemple :
AddHandler Button2.Click, AddressOf MiseAJour
Je fais remarquer qu'il y a une virgule après
l'événement, et deux "d" à AddressOf. Sinon, bing, VB ne comprend
rien. Dans cet exemple, MiseAJour est le nom de la
procédure sur laquelle nous programmons le "branchement" de
l'événement Button2.Click.
Se pose la question de savoir où
doit figurer cette instruction Addhandler. Cela va sans dire (mais
cela va mieux en le disant), elle doit obligatoirement être située
dans une procédure qui sera exécutée avant l'événement concerné par
Addhandler. Si j'utilise Addhandler pour que le clic sur
Bouton2
déclenche la procédure MiseaJour, il faut obligatoirement que ce
Addhandler soit exécuté avant que l'utilisateur ait pu cliquer sur
Bouton2. Faute de quoi la procédure MiseaJour ne sera pas branchée
au clic sur Bouton2 et l'utilisateur aura beau massacrer le
bouton de sa souris, il ne se passera rien du tout.
On peut donc dire que l'instruction Addhandler devra figurer dans
une procédure qui, du point de vue de la chronologie de l'exécution
de l'application, se situera :
Nous sommes presque au bout de nos peines. Nous
savons à présent :
5.4 Identifier le contrôle qui a déclenché l'événement Il ne nous reste plus qu'un seul souci, mais de
taille. Imaginons que nous ayons 125 boutons sur notre Form, sur
lequel un clic mène infailliblement à une procédure unique. Comment
faire pour récupérer, au sein de cette procédure, le bouton précis qui
a provoqué l'événement (ce qui est souvent indispensable) ? Nous
disposons certes du paramètre Sender, que nous avons déjà utilisé lors
d'exercices précédents. Sender est une variable objet, qui contiendra
le nom du contrôle ayant déclenché l'événement.
Il y a alors de fortes chances pour que nous ayons besoin de savoir à quel indice dans la collection correspond
le contrôle désigné par cette variable Sender. Pour cela, nous
pouvons utiliser la méthode IndexOf, qui renvoie l'indice de
n'importe quel contrôle au sein de n'importe quelle collection...
enfin, presque. J'y reviens dans un instant. Ainsi, imaginons que nous ayons créé un Panel comprenant une série
de 100 boutons, pour lesquels une boucle a habilement renvoyé l'événement
Click vers une procédure Yaclic. Supposons enfin que nous
souhaitions afficher en titre de la Form l'indice du bouton sur
lequel l'utilisateur vient de cliquer. La procédure Yaclic aurait
alors la tête suivante :
Private Sub Yaclic(ByVal sender As
System.Object, ByVal e As System.EventArgs)
Dim i As Integer i = Panel1.Controls.IndexOf(sender) Me.Text = i End Sub Cette technique, simple et efficace, ne
fonctionnera toutefois pas au sein des collections que nous aurons
créées et remplies par du code, les collections créées par le programmeur ignorant la méthode
IndexOf ! Pour celles-ci, il faudra avoir recours à une voie
détournée : programmer une boucle qui compare, par exemple, la propriété Name
de chaque membre de la collection Macollec avec celui du sender, sur le
modèle suivant :
Private Sub Yaclic(ByVal sender As
System.Object, ByVal e As System.EventArgs)
Dim i As Integer For i = 1 to Macollec.Count If Macollec.Item(i).Name = sender.Name Then Me.Text = i Endif Next i End Sub En conclusion : VB.Net permet de faire tout ce que
nous permettaient les tableaux de contrôles des versions précédentes
de VB, mais souvent au prix d'un code plus compliqué. Et par-dessus
le marché, au lieu d'établir une règle simple fonctionnant dans
toutes les situations, les gars de chez Microsoft se sont amusés à
multiplier les exceptions. Comme quoi,
les Shadoks ont incontestablement inspiré Bill Gates lorsqu'ils
proclamaient solennellement : "En
essayant continuellement, on finit par réussir.
Donc : plus ça rate, plus on a de chance que ça marche"
6. Créer dynamiquement des contrôles J'y ai fait rapidement allusion un peu plus haut,
alors autant ne pas vous laisser dans les ténèbres de l'ignorance.
D'autant qu'une fois que vous aurez appris cette technique, vous aurez
à votre disposition un ensemble de mécanismes qui vous permettra de
faire face à bien des situations.
Alors voilà, l'affaire est toute bête, et tient en
peu de mots : il est possible de créer et de détruire des contrôles en
cours d'exécution, via les instructions appropriées.
Créer un nouveau contrôle par du code n'est pas plus compliqué
que créer une nouvelle variable : on retrouve le mot-clé Dim, et la
spécification de la classe du contrôle. Sans omettre le constructeur New,
indispensable dès que la déclaration porte sur autre chose qu'un type
simple. On aura ainsi une ligne du genre :
Dim Toto as New Button
Où Toto sera la propriété Name du nouveau bouton
créé. Mais à ce stade, nous n'avons créé qu'un bouton désincarné,
virtuel, si j'ose dire, et qui n'appartient à aucun ensemble de notre
application. Si l'on veut par exemple que le nouveau bouton soit
visible (et en général, c'est vivement recommandé), il est
indispensable de l'incorporer dans la collection Controls de la Form :
Controls.Add(Toto)
Toto est dorénavant un contrôle comme un autre de la Form. Si, pour libérer de l'espace, on veut à un moment où à un autre
détruire le contrôle, il suffira de lui appliquer la méthode Dispose.
Si l'on crée un contrôle dynamiquement, par du code, celui-ci se voit donc déclaré par l'instruction Dim au sein d'une procédure. Par conséquent, ce contrôle va être considéré par le langage comme une variable (objet) locale à la procédure. En toute logique, il sera donc impossible de faire référence à ce contrôle par son nom (sa propriété Name), dans une autre procédure ! La parade sera d'incorporer, comme nous l'avons fait, ce contrôle à la collection Controls de la Form (et éventuellement, à d'autres collections créées par nos soins). De là, nous pourrons accéder au contrôle, depuis n'importe quelle procédure, via son indice dans la collection... Ouf. Le seul détail qui nous reste à régler - mais par
rapport à ce qu'on s'est cogné jusque là, ça va être du cake - c'est
de savoir comment on va faire pour attribuer automatiquement des noms
(des propriétés Name) à toute une série de contrôles créées à la
chaîne par du code.
Pour cela, il suffit par exemple de concaténer un nom
(mettons, Toto), et un nombre généré par une boucle. De toutes façons,
l'essentiel est que les noms soient tous différents : le reste, on
s'en fiche un peu, puisqu'on ne se resservira des contrôles qu'en les
désignant par leur indice dans la collection dans laquelle nous les
aurons rangés.
Voilà, pour résumer, un code qui accomplit les tâches
suivantes :
Dim MesCases As New Collection
Private Sub Button1_Click (ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click Dim i As Integer For i = 1 To 20 Dim x As New CheckBox x.Name = "Macase" & Str(i) Controls.Add(x) x.Left = 50 x.Top = i * 20 x.Width = 150 x.Text = "Je suis une case" MesCases.Add(x) AddHandler x.CheckedChanged, AddressOf ClicMesCases Next i End Sub Pour gérer le
clic sur ces cases et afficher laquelle a déclenché l'événement, nous
retrouverons donc un code déjà vu, sur un mode un brin laborieux
(puisque l'emploi d'une collection personnalisée MesCases
nous interdit de récupérer directement l'indice via IndexOf).
Il va de soi que dans certaines situation, on pourra tout de même
optimiser un peu la boucle :
Private Sub ClicMesCases(ByVal sender As System.Object,
ByVal e As System.EventArgs)
Dim i, tut As Integer For i = 1 To MesCases.Count If MesCases(i).name = sender.name Then tut = MsgBox("vous avez cliqué sur la case n°" & i, vbOK) End If Next i End Sub Et voilà. Bon, avec ça, on va pouvoir
faire des noeuds très jolis (et très gros) à nos neurones.
7. Remarque finale
Si les collections ont beaucoup d'inconvénients,
elles possèdent néanmoins un avantage :c'est qu'elles représentent
un concept que l'on va retrouver à tous les coins de rues en VB.Net.
En fait, si j'ai jusqu'ici parlé uniquement des collections au sens
de série de boutons, de labels, etc. c'était parce qu'il fallait
bien prendre le problème par un bout. Mais en VB.Net, dès que l'on
a affaire à une série d'éléments qui sont "inclus" dans un autre
élément, on peut être sûr que le langage considère qu'il s'agit
d'une collection. Ainsi, je pourrais citer en vrac :
Tout cela, nous en reparlerons. Mais si vous avez compris le concept de collection et les outils avec lesquels on les manie, vous devriez vous en sortir sans trop de dégâts. |
||||