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)            

 
 
A lire :

Le communisme primitif
n'est plus ce qu'il était


Aux origines de
l'oppression des femmes

Mon premier livre : 464 pages à propos des sociétés primitives, des sociétés préhistoriques, de la famille, des rapports entre les sexes et de bien d'autres choses encore. On est certes fort loin de l'informatique, mais avouez que ça n'est pas toujours plus mal.

Prix public : 20 €.

Disponible (entre autres) à la FNAC.

Critiques, réactions et discussions liées à l'ouvrage sur mon blog.
Ne manquez surtout pas
mon groupe de rock, les fabuleux
Midnight Jokers !
Jouer du rock c'est bien,
jouer de la country,
c'est bien aussi !
Écoutez aussi les improbables
Moonlight Swampers !
Vous aimez les petites bêtes qui vivent sous l'eau ?
Faites donc un tour vers mes photos sous-marines
 
  
 
 
 
Partie 7
Encore des contrôles (les listes)
 
Reprenons à présent notre tour d'horizon des différents contrôles proposés par VB.Net. Bon, les Button, c'est fait. Les Label et les Textbox, on les a vues aussi, hmmm... les cases, ça y est... que reste-t-il ? Oh, tiens, ben oui :

1. La classe Listbox
La Listbox est à l'ensemble des classes de listes ce qu'est la 2 CV aux automobiles : un prototype ancien et basique, mais solide, rustique, et qu'on a toujours plaisir à voir en service :
 La Listbox, par définition :
  • contient exclusivement des items de type String.
  • graphiquement, se présente toujours sous une forme "développée" (ce n'est pas une liste déroulante).
  • impose un choix parmi les items proposés (ce n'est pas une liste modifiable, où l'on peut entrer une valeur qui n'est pas proposée dans la liste)
  • possède ou non une barre de défilement verticale (voire horizontale), selon le nombre d'éléments présents dans la liste et la place disponible pour les afficher (l'apparition et la disparition des barres de défilement sont gérées de manière automatique par VB, même si on peut toujours bidouiller cela via certaines propriétés).
Pour les Listbox, on va trouver plusieurs propriétés concernant le mode d'affichage, dont entre autres :
  • Sorted : propriété booléenne qui indique si la liste est automatiquement triée ou non.
  • Multicolumn : propriété booléenne qui permet d'afficher les items sur plusieurs colonnes.
Et une propriété fondamentale pour son comportement :
  • SelectionMode : qui  indique si l'on autorise ou non la multisélection des items de la liste.
En ce qui concerne la gestion des items de la ListBox, il faut avoir à l'esprit une chose, de laquelle se déduisent toutes les autres :
Théorème :
Un item d'une Listbox est une chaîne de caractères, membre de la collection Items membre de la Listbox.
Par conséquent, ce que nous avons appris au chapitre précédent sur les collections s'applique de plein droit aux items des Listbox. Comme quoi, on dira ce qu'on voudra, mais au niveau de l'articulation pédagogique, on sent bien que ce cours est drôlement bien goupillé et qu'on a pas affaire à un amateur. Enfin, moi, ce que j'en dis, hein...
Pour balayer les éléments d'une Listbox du premier au dernier, nous pourrons donc utiliser leurs numéros d'indice. Attention toutefois, Listbox n'autorise pas l'omission de la propriété Items, qu'il faudra donc stipuler en toutes lettres. Pour passer l'ensemble des éléments d'une Listbox en majuscules, on pourra donc écrire :
Dim i As Integer
For i = 0 To ListBox1.Items.Count - 1
  ListBox1.Items(i) = ListBox1.Items(i).ToUpper
Next i
Remarque de bon ton :
Dans une Listbox, les items sont numérotés à partir de l'indice zéro
On pourra également, dans d'autres circonstances, utiliser la boucle For Each ... Next, par exemple pour récupérer tout le contenu de la liste dans une seule chaîne MaListe :
Dim element As Object
Dim MaListe As String
MaListe = ""
For Each element In ListBox1.Items
  MaListe = MaListe & element
Next element
On peut, comme dans n'importe quelle collection, ajouter un élément dans une liste via la méthode Add :
Listbox1.Items.Add("Nouvel élément")
Pour supprimer un élément, on n'a que l'embarras du choix. La méthode Remove demandera de fournir l'élément lui-même...
Listbox1.Items.Remove("Midnight Jokers")
...tandis que la méthode RemoveAt demandera un indice :
Listbox1.Items.RemoveAt(5)
Enfin, Clear procèdera au nettoyage complet de la liste, en supprimant tous les éléments d'un seul coup d'un seul :
Listbox1.Items.Clear
Le but d'une liste, c'est de permettre à l'utilisateur d'en sélectionner un ou plusieurs items. Cette action de l'utilisateur affectera les propriétés :
  • SelectedItem : qui désigne sous forme de chaîne de caractères l'élément actuellement sélectionné. Si aucun élément n'est sélectionné, cette propriété vaut une chaîne vide.
  • SelectedIndex : qui désigne par son indice l'élément actuellement sélectionné. Si aucun élément n'est sélectionné, cette propriété vaut -1.
Lorsqu'une sélection multiple est possible sur une Listbox, ces deux propriétés renvoient alors des collections, dans lesquelles on peut partir à la pêche pour récupérer les différents éléments.
En ce qui concerne les événements, on peut bien sûr gérer les Listbox par l'événement Click. Mais un événement propre est disponible, qui ne se déclenche qu'en cas de changement de l'item sélectionné : il s'agit de SelectedIndexChanged. Cet événement  est à la fois plus restrictif et plus large que le Click. Plus restrictif, car il ne se déclenche pas en cas de clic sur un item déjà sélectionné. Plus large, car il détectera un changement de sélection survenant y compris suite à une manoeuvre au clavier.
Bon, c'est pas tout ça, mais on va pouvoir utiliser notre science toute neuve :
Cet exercice n'est pas spécialement facile. Afin de graduer la difficulté, on pourra commencer par en créer une version qui ne se préoccupe pas de la multisélection. Une fois que c'est au point, on introduira la case à cocher, et on modifiera le code là où c'est nécessaire.
Exercice
Exécutable
Sources
Inventaire
2. La classe Combobox
Il s'agit d'une classe-fille à la fois de la classe Listbox et de la classe Textbox. Elle en hérite donc l'essentiel des propriétés. En gros, on peut considérer qu'une ComboBox, c'est une liste modifiable et/ou déroulante :
L'aspect de la ComboBox dépend de la valeur donnée à la propriété DropDownStyle :
  • Dropdown : signifie que la zone est modifiable et déroulante
  • DropDownList : la liste est déroulante, mais non modifiable
  • Simple : la liste est modifiable, mais non déroulante
Je rappelle que le caractère déroulant d'une Combobox n'influence que son aspect, alors que son caractère modifiable influence son comportement (peut-on ou non y entrer autre chose qu'un item de la liste).

3. La classe CheckedListBox
Cette classe est une espèce de croisement entre la Listbox et la Checkbox. De la première, elle hérite toutes les propriétés hormis celles liées à la multisélection. Cette classe affiche une liste dans laquelle chaque élément est représenté avec une case à cocher :
A noter que :
  • La propriété ThreeDCheckBoxes indique si ces cases sont en relief
  • l'événement ItemCheck est provoqué par le changement d'état (cochage ou décochage) d'un élément
  • les méthodes GetItemCheckState et SetItemCheckState permettent respectivement d'accéder et de modifier à l'état d'un élément (parmi Checked, Unchecked et Indeterminate).
  • les propriétés CheckedItems et CheckedIndices désignent respectivement les collections CheckedListBox.CheckedItemCollection (collection des items) et CheckedListBox.CheckedIndexCollection (collection des index) des éléments cochés ou indéterminés de la liste.
Comme ça, ça a l'air compliqué, mais c'est plus laborieux que vraiment méchant.
4. La classe ImageList
Ce contrôle n'a aucun intérêt par lui-même. Mais associé à d'autres (que nous verrons dans un instant), il fait un malheur.
Son rôle consiste à être en quelque sorte un tableau d'images pour que d'autres contrôles viennent puiser dedans. Il s'agit d'un contrôle invisible. Ce n'est pas le seul, mais c'est le premier que nous rencontrons. Encore une fois, il ne sert que d'espace de stockage. D'ailleurs, lorsque que nous le sélectionnons dans la boîte à outils, nous voyons tout de suite qu'il ne se pose pas sur la Form, mais à côté.
On remplit le contrôle ImageList par sa propriété Images, qui désigne une collection (et à laquelle s'appliquent donc les méthodes Add et Remove, si on veut en manipuler le contenu par du code). La propriété ColorDepth permet de régler la qualité de codage des images en question, et roule Raoul : on accède ensuite aux images que contient ImageList par la propriété ImageIndex.
Ayé, on a fait le tour de ce petit contrôle.

5. La classe Listview
Il s'agit d'une liste pouvant présenter les informations qu'elle contient de quatre manières possibles, imitant parfaitement le volet droit de l'explorateur Windows (grandes icônes, petites icônes, liste, détails). Ces modes d'affichage sont réglés par la propriété View. On peut également faire apparaître une case à cocher devant chaque item via la propriété booléenne Checkboxes.
Le maniement de cette classe étant assez particulier, je vous renvoie en cas de besoin à un ouvrage spécialisé (pour l'heure, le courage me manque de rédiger cette partie ; on a beau être enseignant, c'est-à-dire surhomme, on n'en a pas moins ses petits moments de faiblesse).

6. La classe Treeview
Remarque préventive :
La présentation de la classe Treeview et des exemples de code qui vont avec constitue indéniablement un des moments les plus pénibles de ce cours qui en compte pourtant beaucoup.
Aussi difficile à lire pour vous qu'elle l'a été à écrire pour moi, cette partie pourra être ignorée par tous ceux qui n'en ont pas un besoin absolu, ou qui n'égayent pas leurs longues soirées d'hiver en s'adonnant aux pratiques masochistes.
"Back to the trees !" criait le pithécanthrope réactionnaire qui refusait la bipédie dans Pourquoi j'ai mangé mon père. Ben, à ce qu'il semble, après quelques millions d'années d'évolution ayant culminé dans le VB.Net, il est effectivement temps d'y retourner.
Dans les arbres, nous allons pouvoir faire plein de choses. En effet, les arbres sont une manière extrêmement pratique de structurer les données, et au moment même où vous lisez ces lignes, je suis certain que vous avez déjà en tête plusieurs exemples de situations où des données informatiques sont organisées - et peuvent donc être représentées - sous forme d'arbre. L'exemple le plus tartignol - c'est évidemment celui que je choisirai - est celui d'un disque dur, avec ses répertoires contenant d'autres répertoires, qui contiennent d'autres répertoires, etc. Et d'ailleurs, si vous voulez imaginer à quoi ressemble un contrôle Treeview, il suffit de penser à la partie gauche de l'explorateur de Windows. Ce n'est pas pour rien.
Alors, le Tree d'un Treeview, c'est quoi ? C'est un sac de noeuds, au propre comme au figuré.
Chaque noeud (Node, en anglais, objet de la classe TreeNode) fait partie d'une collection (TreeNodeCollection) des noeuds qui ont le même noeud parent que lui (tous les sous-répertoires immédiats d'un même répertoire).
Dans l'autre sens, chaque noeud pointe sur une collection, celle des noeuds dont il est lui-même le père (tous ses sous-répertoires immédiats).
Manipuler un Treeview, cela revient donc à manipuler des collections de noeuds ! Ce qui signifie :
  • ajouter un noeud dans une collection, via Add (qui l'ajoute à la fin) ou Insert (qui l'ajoute à l'emplacement spécifié)
  • supprimer un noeud d'une collection, via les trois méthodes déjà rencontrées à propos des listes : Remove, RemoveAt et Clear.
Côté événements, les Treeview proposent plusieurs possibilités originales. On peut notamment gérer le fait qu'un noeud soit déployé, par les événements BeforeExpand ou AfterExpand (selon qu'on veuille agir juste avant ou juste après le déploiement du noeud). On peut également gérer le fait qu'un noeud soit replié, avec les événements BeforeCollapse et AfterCollapse (même différence entre les deux).
Remarque de bon sens :
L'exemple de programmation qui va suivre est pour ainsi dire complètement idiot, car comme nous le verrons plus loin, il existe un moyen beaucoup plus rapide de parvenir au résultat dont nous allons laborieusement accoucher ici.
Mais, et les mauvais esprits n'ont qu'à bien se tenir, cet exemple possède une vertu pédagogique, dans la mesure où il nous permettra de mettre le doigt dans des techniques un peu... euh, disons... stimulantes.
Bon, on sait maintenant presque assez pour s'amuser à fabriquer une réplique grandeur nature de la partie gauche de l'explorateur Windows. Pour cela, il nous manque encore quelques petits détails. En particulier, il nous faut un moyen de récupérer, à partir d'un répertoire donné, la liste des sous-répertoires qu'il contient. Ça, ça ne s'invente pas, et c'est pour cela qu'existe la fonction :
Directory.GetDirectories(répertoire)
qui renvoie, sous forme d'un tableau de chaînes, l'ensemble des sous-répertoires du répertoire passé en argument. Sauf que pour que ça marche, il ne faudra pas oublier d'écrire, tout en haut du programme :
Imports System.IO
Parce que sinon, le compilateur ne sait pas qui est ce Directory dont vous lui parlez, encore moins quelle est cette méthode GetDirectories qui lui est attachée, et vous flanquera illico une erreur de compilation. Rappelez-vous, les espaces de noms, nous en avons déjà parlé...
Allons-y maintenant pour de bon. Notre code va s'articuler autour d'une petite procédure que nous appellerons chaque fois que nous en aurons besoin pour mettre à jour notre Treeview. Cette procédure que j'appelle Explor :
  • réclame comme paramètre entrant (ByVal) le noeud dans lequel on veut chercher et afficher les sous-répertoires
  • purge les sous-noeuds existants par un Clear
  • ajoute comme sous-noeud de ce noeud tous les éléments fournis par GetDirectories (c'est-à-dire tous les sous-répertoires), via une boucle For Each.
La voici, la voilà :
Private Sub Explor(ByVal Node As TreeNode)
  Node.Nodes.Clear()
  Dim s As String
  For Each s In Directory.GetDirectories(Node.FullPath)
    Node.Nodes.Add(Path.GetFileName(s))
  Next s
End Sub
Il faut remarquer que s récupère à chaque fois le nom complet du répertoire (avec le chemin). D'où l'emploi de la fonction Path.GetFileName, qui purge le chemin et nous restitue le nom du répertoire proprement dit.
Et d'une.
Et de deux, il faut qu'en cas de clic sur un noeud, notre procédure Explor soit déclenchée pour chacun des sous-répertoires du noeud en question (on a en quelque sorte à chaque fois un coup d'avance : on affiche dans l'arbre les répertoires situés deux niveaux en-dessous du répertoire actif : comme cela, parmi les sous-répertoires directs du sous-répertoire actif, le Treeview affichera différemment ceux qui contiennent quelque chose et ceux qui ne contiennent rien. On aura donc :
Private Sub TreeView1_AfterExpand(ByVal sender As Object, ByVal e As System.Windows.Forms.TreeViewEventArgs) Handles TreeView1.AfterExpand
  Dim z As TreeNode
  For Each z In e.Node.Nodes
    Explor(z)
  Next z
End Sub
Il ne manque plus qu'un détail : amorcer la pompe lors du chargement de la Form, en donnant comme point de départ la racine du lecteur C:, et en considérant que le premier - et le seul - noeud présent dans notre Treeview porte l'index zéro :
Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
  TreeView1.Nodes.Add("c:\")
  Explor(TreeView1.Nodes.Item(0))
End Sub
Avec tout cela, on obtient un joli explorateur qui fonctionne, en nous donnant un truc dans ce genre :
Remarque pertinente :
Bien évidemment, on pourrait programmer l'affaire très différemment, par exemple en remplissant d'avance, une bonne fois pour toutes, le Treeview avec tous les répertoires du disque dur. Mais ça ne serait pas plus facile à programmer, et cela s'avèrerait en réalité moins performant.
Pour que notre explorateur soit parfait, nickel-chrome comme ceux vus à la TV, il lui manque toutefois un petit détail : qu'à chaque noeud soit associé non seulement le petit "+" ou la simple ligne, mais également l'icône du répertoire ouvert ou fermé, selon les circonstances.
C'est ici que le contrôle ImageList, dont nous avons parlé juste avant, va nous rendre un fier service. Il nous suffit d'aller attraper quelque part deux jolies images, l'une d'un répertoire ouvert, l'autre d'un répertoire fermé, et de les mettre dans un contrôle ImageList.
Ensuite, associons notre contrôle ImageList au Treeview, par la propriété... ImageList de ce dernier.
A présent, modifions légèrement notre procédure Explor, en ajoutant deux instructions :
Private Sub Explor(ByVal Node As TreeNode)
  Node.Nodes.Clear()
  Dim s As String
  Dim z As TreeNode
  For Each s In Directory.GetDirectories(Node.FullPath)
    z = Node.Nodes.Add(Path.GetFileName(s))
    z.ImageIndex = 1
    z.SelectedImageIndex = 0
  Next s
End Sub
Il ne reste plus qu'à faire de même dans la procédure Form1_Load, et le tour est joué :
Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
  Dim z As TreeNode
  z = TreeView1.Nodes.Add("c:\")
  Explor(z)
  z.ImageIndex = 1
  z.SelectedImageIndex = 0
End Sub
C'est tout de suite plus classieux, hein ?
Remarque de puriste prudent :
A proprement parler, et pour qui a été élevé dans les canons de l'algorithmique rigoriste, les lignes du genre :

z = Node.Nodes.Add(Path.GetFileName(s))

sont une pure profanation. En effet, elles impliquent qu'on puisse à la fois appliquer une méthode (Add en l'occurrence) et affecter une variable, le tout d'un même élan. VB.Net autorise donc ce genre d'écriture hérétique, de même qu'il permet, nous en avons déjà parlé, d'affecter une variable tout en la déclarant
On peut même trouver sous la plume de certains programmeurs des lignes proprement sataniques, qui consistent tout à la fois à déclarer une variable, à l'affecter et à appliquer une méthode :

Dim z as TreeNode = Node.Nodes.Add(Path.GetfileName(s))

Alors, ce type d'écriture est un peu comme les voitures qui roulent à 250 km/h : c'est en vente libre, et c'est bien pratique pour gagner du temps, mais si on veut rester en vie, on n'est pas obligé de les acheter, et encore moins de s'en servir. A bon entendeur...