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 MECI (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, en écrivant ces événements à la queue-leu-leu derrière l'instruction Handles. Cette technique 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 le traitement des 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 lorsque VB6 est devenu VB7, autrement dit VB.Net, (je parle bien du langage 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'un concept un peu étrange, qui évoquera de loin les défunts tableaux de contrôles, tout en s'en différenciant par certains aspects - mais qui, c'est son avantage, va se retrouver absolument à tous les coins de code. Donc, il va falloir au début un peu de gamberge pour piger le truc ; mais une fois que ce sera fait, ça n'arrêtera pas de servir. Le concept en question c'est celui de collection.
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 conteneur définit automatiquement une collection - et le contenu de ce conteneur rassemble donc les membres de cette collection. Par exemple, tout contrôle posé sur une Form devient aussi sec membre de la collection définie par la Form. En l'occurrence, comme toute collection définie par un contrôle conteneur, celle-ci porte le nom de Controls. Simplement puisqu'il s'agit de la Form, il n'est pas besoin de précision supplémentaire. Si l'on parle de la collection définie par le contrôle conteneur TrucMuche, alors il faudra préciser qu'il s'agit de :
TrucMuche.Controls
Remarque sylvestre :
Les collections fonctionnent de manière hiérarchique, comme une arborescence, exactement de la même façon que les répertoires et les sous-répertoires.
Ainsi, si sur une Form j'ai deux boutons et un GroupBox, et que dans ce GroupBox j'ai trois RadioButton, ces RadioButton font partie de la collection définie par la GroupBox... mais pas de celle définie par la Form, qui ne compte pour sa part que les deux boutons et la GroupBox.
Tout l'intérêt des collections, et c'est là un des points communs avec les tableaux, c'est que les contrôles devenant des membres d'un ensemble, on va pouvoir les traiter en écrivant des boucles, en balayant l'intégralité des membres d'une collection tout comme on peut balayer l'intégralité des éléments d'un tableau. Mais comment, vous demandez-vous, haletants ? Pour le savoir, il vous suffit de poursuivre votre lecture...
Remarque instructive :
Par conséquent, en cas de besoin, le moyen le plus simple pour regrouper des contrôles dans une collection consiste à les créer au sein d'un conteneur. Le Panel, du fait qu'il est invisible pour l'utilisateur, est un candidat idéal à ce rôle.
2. Désigner les contrôles par leur indice
2.1 Principes généraux et syntaxe
La manière la plus évidente (mais pas toujours la plus pratique, comme nous le verrons) de désigner les contrôles dans une collection, est de se référer à leur indice dans cette collection.
Car c'est un autre point commun entre les collections et les tableaux : tout membre d'une collection y possède un indice qui l'identifie de manière unique. Le truc, c'est qu'on ne choisit pas cet indice : VB le gère tout seul, comme un grand, avec une règle un peu étrange : les indices sont attribués dans l'ordre inverse de la création des contrôles. Autrement dit, le dernier contrôle créé dans une collection y possède l'indice zéro. Et si l'on crée un nouveau contrôle, l'ancien contrôle d'indice 0 devient l'indice 1, l'ancien indice 1 devient l'indice 2, etc. Il en va de même en sens inverse, en cas de suppression d'un contrôle : tous les membres de la collection sont alors renumérotés automatiquement par VB. Sans qu'on ait à s'occuper de quoi que ce soit, les indices commencent donc toujours à zéro, et il n'y a jamais de « trous » dans les indices.
Pour désigner un contrôle individuel dans une collection (et on a vu que tout contrôle, hormis la Form elle-même, est nécessairement membre d'une collection), il existe ainsi une alternative à la propriété Name employée jusqu'ici. Il s'agit de l'écriture suivante, qui traite des collections littéralement comme de tableaux :
NomCollection.Item(i)
En fait, lorsque la collection est définie par un contrôle conteneur, et que son nom se termine donc par Controls, on peut omettre la propriété Item et écrire directement :
NomCollection(i)
Ainsi, si je veux parler de ce qui est écrit sur le 8e contrôle (dans l'ordre décrété par VB) de la collection constituée par la Form, j'écrirai :
Controls(7).Text
Si à présent on traite d'un autre conteneur que la Form (par exemple, un Panel appelé Ensemble, et que l'on souhaite positionner le 4e contrôle de cette collection à 200 pixels du bord gauche de son conteneur, on écrira :
Ensemble.Controls(3).Left = 200
Tout cela ne pose aucune difficulté particulière.
2.2 Une boucle sur l'indice : For ... Next
Qui dit éléments indicés dit boucles, et possibilité d'effectuer un traitement systématique (un balayage) de l'ensemble. Pour cela, une propriété s'avère bien indispensable : il s'agit de Count, qui donne le nombre d'éléments d'une collection donnée. L'écriture de boucle de balayage typique est donc :
For i = 0 to Ensemble.Controls.Count - 1
   ... Ensemble.Controls(i) ...
Next i
Cette technique simple semble imparable, et on se demande bien pourquoi il faudrait se creuser davantage la tête. Eh bien, le problème avec cette approche, c'est que dans la boucle, on pourra seulement accéder à celles des propriétés qui sont des propriétés générales des contrôles. Les propriétés propres à une classe précise nous seront refusées. Par exemple, si je veux rendre une série de contrôles invisibles, aucun souci, Visible étant une propriété commune à tous les contrôles. On poura donc écrire sans aucun problème :
For i = 0 to Ensemble.Controls.Count - 1
   Ensemble.Controls(i).Visible = False
Next i
Admettons qu'en revanche, mon problème soit de cocher une série de CheckBox. Je serais tenté d'écrire, en toute logique :
For i = 0 to Ensemble.Controls.Count - 1
   Ensemble.Controls(i).Checked = True
Next i
Sauf que là, ça va coincer : le compilateur VB nous jette sans ménagements, arguant du fait que la propriété Checked ne s'applique pas à un objet de la classe Controls.
Damned, nous voilà coincés.
Enfin, pas tout à fait... (quel suspense dans ce cours, un vrai thriller).
3. Une boucle (presque) sans indice : For Each ... Next
Voici en effet qu'arrive une nouvelle structure de boucle, typique d'un langage objet, spécifiquement conçue pour permettre de parcourir une série d'ojets - et donc, de contrôles. Dans ce type de boucle, tout repose sur le fait que la variable qui va servir de compteur n'est 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 l'ensemble des contrôles de la collection. Le tout est de savoir quel doit être le type de ladite variable...
3.1 Le cas le plus simple (et le plus souhaitable)
Ce cas simple est celui où tous les contrôles de la collection appartiennent à une même classe. Par exemple, ce sont tous des boutons, ou des cases à cocher, etc. Dans ce cas, on va pouvoir créer une variable du type de la classe voulue, et s'en servir pour effectuer la boucle. Si l'on reprend l'exemple précédent, celui du cochage systématique d'une série de CheckBox, on aura :
Dim Truc As CheckBox
For Each Truc in Ensemble.Controls
   Truc.Checked = True
Next Truc
Cette écriture appelle plusieurs remarques.
  1. Truc est une simple variable, même si elle est d'un type objet. Autrement dit, quand nous la déclarons, nous créons un emplacement capable de pointer vers un objet existant ; nous ne créons pas un nouvel objet. Voilà pourquoi la déclaration de la variable ne comporte pas le constructeur New.
  2. du fait que Truc est déclarée en type Checkbox, le compilateur nous donne accès, par son intermédiaire, à toutes les propriétés et méthodes de cette classe.
  3. il faut savoir qu'en réalité, VB traduit cette instruction par une boucle gérée par un indice. Autrement dit, derrière toute boucle For Each ... Next se cache une boucle ordinaire For ... Next. Cela explique le pourquoi du comment de certaines erreurs, du genre « index out of range », c'est-à-dire « indice-qui-est-allé-trop-loin », qui ne manqueront pas de vous tomber sur le coin du museau durant certains exercices - le tout est de se souvenir de ce petit détail au moment voulu...
Cette technique est si simple et si pratique qu'on y aura recours aussi souvent que possible. Comme je le disais précédemment, on cherchera à constituer des collections homogènes, en particulier via un contrôle Panel, afin de s'ouvrir toutes les possibilités que cet agencement nous offre.
3.2 Le cas plus compliqué (mais qu'on ne peut pas toujours éviter)
Il s'agit bien sûr de la situation où une collection comprend des contrôles de classes différentes. Par exemple, en plus de nos cases à cocher, quelques boutons, zones de texte et labels. Là, évidemment, on a deux possibilités, mais qui posent deux problèmes symétriques.
  • soit on déclare notre variable de boucle dans la classe précise qui nous intéresse, en l'occurence Checkbox. Mais dans ce cas, la boucle provoque illico une erreur : en fait, dès que VB, au sein de la collection, tombe sur un contrôle d'une autre classe, il hurlera que notre variable ne peut en aucun cas désigner autre chose qu'une case à cocher - ce qui, après tout, est assez logique.
  • soit on déclare la variable de boucle dans le type le plus général, à savoir Control. Là, pas d'erreur... mais hélas, pas de propriété Checked accessible non plus, pour des raisons tout aussi évidentes.
Alors, serions-nous condamnés à un désespoir sans fin ? Nenni, vous vous doutez bien qu'avec le problème, arrive la solution.
Toute l'astuce va consister à effectuer la boucle via une variable de type général Controls (pas le choix). Mais au sein de la boucle, on détecte le type du contrôle pointé par la variable. S'il s'agit du type souhaité (case à cocher, en l'occurrence), on recopie alors la variable de boucle vers une variable de type Checkbox, et c'est cette dernière qui va nous donner accès aux propriétés voulues. Démonstration par l'exemple :
Dim Truc As Control
Dim Toto As CheckBox
For Each Truc in Ensemble.Controls
   If TypeOf Truc Is CheckBox Then
      Toto = Truc
      Toto.Checked = True
   End If
Next Truc
Elle est pas belle, la vie ?
Remarque « d'une pierre deux coups » (comme disait Bertin) :
Cette technique peut s'avérer tout aussi précieuse pour utiliser un Sender rétif. Cette variable étant obligatoirement déclarée en Control, on ne peut normalement, par son intermédiaire, avoir accès à bien des propriétés utiles. Là encore, il sufira de créer une variable dans le type voulu et de recopier Sender dans cette variable pour peu que son type s'y prête, et le tour sera joué.
On peut donc reprendre les deux derniers exercices et utiliser les connaissances que nous venons d'acquérir afin de faire subir au code une cure d'amaigrissement.
Exercice
Exécutable
Sources
Options
Sondage
4. Créer des collections par du code
Tous les contrôles conteneurs définissent automatiquement une collection. Mais toutes les collections ne sont pas définies par des contrôles conteneurs. En effet, il est possible de créer une collection uniquement par du code, et de regrouper ainsi des contrôles sans aucune espèce de relation avec leur disposition sur la Form. Soyons honnêtes, on n'a pas besoin de cette technique tous les jours, et en réalité, c'est même une solution assez rare. Mais ce n'est pas très compliqué, et cela va nous permettre de découvrir quelques instructions qui nous seront fort utiles dans d'autres circonstances. Alors, pourquoi se priver ?
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
Cette fois, Collection étant considéré comme un objet à part entière - et non comme une simple variable - on n'échappe pas à l'indispensable constructeur New. À part ça, comme vous le voyez, il n'y a vraiment pas de quoi fouetter un chat (si tant est qu'on doive jamais en arriver à de telles maltraitances envers ces braves petites boules de poils).
5.2 Remplir et vider une collection
Si l'on crée une collection par du code, cela signifie qu'a priori, elle est vide. Pour qu'elle contienne des contrôles, ceux-ci devront lui être rattachés via la méthode Add. Tiens, direz-vous ! Enfin une méthode ! Eh oui, et celle-ci réclame un argument (et un seul), le nom du contrôle qui doit être ajouté à la collection. On aura par exemple :
Ensemble.Add(Textbox1)
Ensemble.Add(Textbox2)
Ensemble.Add(Label5)
etc.
De même, on peut tout aussi facilement retirer un contrôle d'une collection par la méthode Remove qui, elle aussi, réclame le même argument pour les mêmes raisons :
Ensemble.Remove(Label5)
Il existe néanmoins un moyen plus rapide et radical de vider une collection : il consiste à employer la méthode Clear (sans arguments), qui purge purement et simplement une collection de l'ensemble de son contenu :
Ensemble.Clear
Tout se passe donc pour le mieux dans le meilleur des mondes microsoftiens possibles, et nous pouvons naturellement présumer que tout comme pour une collection définie par un conteneur, nous pouvons désigner les membres d'une collection définie par du code en utilisant leurs indices. Là, cependant, plus question d'utiliser la propriété Controls. En revanche, obligation de passer par Item. Cela donnera un machin du genre :
...
Ensemble.Item(i).Visible = True
...
Oui mais voilà, la bande à Bill avait décidé que l'oisiveté est la mère de tous les vices, et qu'un programmeur qui s'endort, c'est un programmeur qui est déjà à moitié mort. Alors, pour nous tenir éveillés, ils ont décidé un truc marrant : si les indices des collections définies par des conteneurs commencent en toute logique à zéro, les indices des collections définies par du code commencent pour leur part à 1 ! Amusant, non ? Alors, il n'y a là-dedans qu'une seule difficulté... c'est de s'en souvenir, et en pareil cas, de bien écrire :
For i = 1 to Ensemble.Count
6. Associer des événements à une procédure par du code
Tout le monde est encore avec moi ? Alors asseyez-vous confortablement, attachez bien vos ceintures, on va passer à la vitesse supérieure.
6.1 Branchements en cours d'exécution
Jusqu'ici, quand nous avons voulu associer un événement donné à une procédure, nous n'avons guère eu le choix : il fallait entrer de nos petits doigts vigoureux, dans la ligne de titre de la procédure, juste après l'instruction Handles, la liste des associations Objet.Action voulue. C'était jouable... tant qu'on ne voulait associer à une procédure donnée que deux, trois ou quatre événements. Mais s'il fallait en associer plusieurs dizaines ou centaines, on voit que cette technique deviendrait vite un enfer.
Or, depuis que nous savons ce qu'est une collection, nous savons aussi qu'il est possible de parcourir ces dizaines, ou ces centaines de contrôles, par une boucle. Jusque-là, nous n'avons envisagé ces boucles que dans le but de tripatouiller les propriétés de nos contrôles. Mais là où ça devient carrément excitant, c'est qu'il est également possible d'en profiter pour rattacher, par des instructions, des événements à des procédures. Wow.
L'instruction qui permet d'accomplr cet exploit est AddHandler, littéralement « ajouter une gestion ». Elle s'emploie selon la syntaxe suivante :
AddHandler NomObjet.Action, AddressOf NomProcédure
Imaginons ainsi que ma collection Ensemble (créée par du code) soit composée d'une série de boutons, et que je veuille « brancher » le clic sur tous ces boutons sur une procédure appelée Shiva. Cela donnera le code suivant :
Dim Truc As Button
For Each Truc in Ensemble
   AddHandler Truc.Click, AddressOf Shiva
Next Truc
Remarque orthographique :
Ne pas oublier la virgule après l'événement, et les deux « d » à AddressOf.
Sinon, patatras, VB ne comprend rien.
Une question capitale est celle de savoir à quel endroit du code doit figurer l'instruction Addhandler. Cela va sans dire (mais cela va mieux en le disant), elle doit obligatoirement être exécutuée avant qu'on ait besoin de ses effets. Autrement dit, elle doit être placée dans une procédure qui sera exécutée avant l'événement concerné par AddHandler. A priori, et sauf raison contraire (nous en verrons une excellente dès la partie suivante), une solution pratique est de mettre cette instruction quelque part dans la procédure Form.Load, dont on sait qu'elle se déclenchera dès le lancement de l'application.
Remarque « le fil rouge sur le bouton rouge »
Lorsqu'on « branche » ainsi un événement sur une procédure, il est essentiel de s'assurer que cette procédure a bien, au départ, été créée pour gérer le même type d'événements. En effet, dans le cas contraire, il y a aura non-correspondance entre le type du paramètre en entrée e attendu et celui qui aura été déclaré... et par conséquent, paf, une erreur. Une bonne manière de procéder consiste :
  1. à créer la procédure gérant le bon type d'événements par les voies habituelles
  2. à renommer la procédure pour éviter toute ambiguité (Bouton1_Click laisse entendre que seul le clic sur Bouton1 est géré)
  3. à effacer Handles et tout ce qui le suit
  4. à passer la ou les lignes de code AddHandler qui effectueront le branchement.
Moralité, plus c'est compliqué, plus il faut être méthodique pour éviter les ennuis.
6.2 Un indice pour identifier le coupable ?
Si nous avons, à la main ou par une boucle, branché plusieurs contrôles différents sur une même procédure (par exemple, des tas de boutons.click vers Shiva), il se posera évidemment le problème, à chaque exécution de Shiva, de savoir lequel, parmi tous ces boutons, vient de recevoir le clic.
Nous savons depuis longtemps récupérer son nom : puisque la variable Sender pointe sur le contrôle en question, son nom nous est donné par la propriété Sender.Name.
Oui, c'est bien gentil, direz-vous légitimement, mais bien davantage que son nom, on a souvent davantage besoin de connaître son indice dans la collection dont il fait partie. Dans ce cas, de deux choses l'une, selon la manière dont la collection qui rassemble nos boutons a été définie.
  • soit celle-ci est définie par un contrôle conteneur, par exemple un Panel appelé de manière très originale MonPanel. Dans ce cas, fastoche : la méthode IndexOf nous permet de récupérer directement l'indice d'un contrôle à partir de son nom, comme dans :
  • MonIndice = MonPanel.IndexOf(Sender)
  • soit la collection a été définie par du code... et là, patatras, la méthode IndexOf n'est pas disponible. Si on a besoin de l'indice, il ne reste plus qu'à programmer quelques lignes à la force du poignet, à l'huile du coude et à la sueur du front, du genre :
  • For i = 1 to Ensemble.Count
       If Ensemble.Item(i) = Sender Then
          MonIndice = i
       End If
    Next i
Bon, et si on se faisait quelques exos histoire de faire infuser tout cela ?
 
Exercice
Exécutable
Sources
Village de cases
Le maillon faible
7. Créer des contrôles dynamiquement
Nous en arrivons à l'apothéose, au couronnement, bref, à l'extase ultime. Je vous annonce donc la nouvelle sans plus attendre : il est possible de créer et de détruire des contrôles en cours d'exécution, via les instructions appropriées.
Cette nouvelle qui nous laisse sans voix nous ouvre néanmoins des perspectives grandioses, puisque elle nous libère de la nécessité de connaître à l'avance, avant même le démarrage de l'application, l'ensemble des contrôles dont celle-ci pourra avoir besoin. Si vous voulez un exemple, pensez au célèbre Démineur de Windows, où l'on propose au joueur différents modes (débutant, intermédiaire, avancé) pour lesquels varie le nombre de cases à déminer (respectivement 81, 256 et 480). Il va de soi que ces cases (en fait, des boutons), sont créés au moment où le joueur choisit son mode, c'est-à-dire alors que l'exécution de l'application est déjà lancée.
En fait, créer un nouveau contrôle par du code n'est pas beaucoup plus compliqué que créer une nouvelle variable : comme d'habitude, 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. Toto est un contrôle, mais un contrôle qui plane dans une sorte d'espace ethéré, et qui ne fait pas vraiment partie de notre application (il n'est d'ailleurs même pas visible à l'écran). Pour cela, il est indispensable que Toto soit rattaché à une collection. Celle-ci pourra par exemple être Controls, définie tout bêtement par la Form. Quant à l'instruction qui effectue ce rattachement, elle ne constitue en rien une surprise puisque nous retrouvons la désormais familière méthode Add :
Controls.Add(Toto)
Toto est dorénavant un contrôle comme un autre de la Form, sur lequel l'utilisateur va pouvoir agir... à condition que nous ayons prévu cette interaction. Autrement dit, il nous faut écrire autant d'instructions AddHandler que d'événements concernant Toto que nous souhaitons « brancher » sur différentes procédures.
Remarque enfonceuse de portes ouvertes :
Est-il besoin de le préciser ? Dans le cas où l'on a créé un contrôle par du code, les instructions AddHandler qui gèreront son comportement ne pourront figurer qu'après cette instruction de création (car pour que la machine comprenne qu'elle doit gérer une action sur un contrôle, encore faut-il que ce contrôle existe).
Du coup, l'instruction AddHandler ne se trouvera pas forcément dans la procédure Form.Load - 999 fois sur 1000, elle se trouvera juste après l'instruction ayant créé le contrôle, quelle que soit la procédure où celle-ci se trouve.
Profitons de l'occasion pour découvrir une autre instruction. Si, pour une raison ou pour une autre, on veut à un moment ou à un autre détruire le contrôle, il suffira de lui appliquer la méthode Dispose.
Voilà, pour résumer, un exemple de code qui accomplit les tâches suivantes :
  1. il crée 20 nouvelles Checkbox au sein d'un GroupBox appelé MonGroupBox (mais où va-t-il chercher tout ça ?)
  2. Il écrit à côté de chaque case « je suis la case numéro ... »
  3. il les dispose convenablement, les unes en-dessous des autres
  4. il branche le changement d'état de chacune de ces cases sur une procédure unique, ClicMesCases
Dim i As Integer
For i = 1 To 20
   Dim MaCase As New CheckBox
   MonGroupBox.Add(MaCase)
   MaCase.Left = 50
   MaCase.Top = i * 20
   MaCase.Width = 150
   MaCase.Text = "Je suis la case numéro" & i
   AddHandler MaCase.CheckedChanged, AddressOf ClicMesCases
Next i
Remarque la tronche à l'envers :
Si l'on se souvien bien de tout ce qui précède, on doit faire remarquer que dans la collection MonGroupBox, les cases possèdent un indice « inversé » par rapport au texte qui figure à leur côté : la case dite n°1 possède l'indice 20, la n°2 l'indice 19, etc. Car, faut-il le rappeler, les indices sont attribués automatiquement par VB dans l'ordre inverse de la création - plus exactement, de l'affectation des contrôles à la collection.
Ah, ce Bilou, quand même, quel plaisantin.
Et voilà. Bon, avec ça, on va pouvoir de très jolis noeuds à nos neurones.
 
Exercice
Exécutable
Sources
Damier

8. Remarque finale
Je terminerai ce chapitre par là où je l'ai commencé : en rappelant que les collections représentent un concept que l'on va retrouver absolument partout. Elles sont très loin de se limiter aux seuls contrôles inclus dans des conteneurs. En fait, en VB.Net, il y a une collection dès que l'on a affaire à une série d'éléments qui sont « inclus » dans un autre. Ainsi, peut-on évoquer en vrac :
  • les différentes Form au sein d'une application MDI (pensez à Word ou Excel, avec les différentes fenêtres « document » au sein de la fenêtre principale de l'application).
  • les éléments d'une liste
  • les menus, les sous-menus, et les sous-sous menus.
  • les éléments d'un Treeview, avec leurs sous-éléments, leurs sous-sous-éléments.
  • etc.