Apprendre le TreeView en Visual Basic

Maîtriser le composant TreeView

Jacques Malatier

Ce praticiel expose et explique les principales propriétés et méthodes du Treeview.

La première section traite d'un Treeview non dépendant des données. Après une courte approche théorique visant à introduire le vocabulaire, il y est expliqué comment ajouter des nœuds de même niveau, parents ou enfants, et comment les gérer.

La seconde section expose comment construire, utiliser et sauvegarder un Treeview dépendant des données. Les données sont issues d'une base Access jointe au projet. La construction de ce Treeview utilise une méthode récursive afin de parcourir tous les enregistrements de la table et de les positionner en fonction de leur position hiérarchique et de dessiner les nœuds.

La troisième section montre comment déplacer un nœud du treeview et comment modifier l'enregistrement correspondant dans la table Access.

Un projet Visual Basic sous-tend le praticiel.

Ce praticiel, sans être très difficile, demande quand même une connaissance minimum de la programmation en VB et de l'accès aux données avec ADO. Le lecteur est guidé pas à pas du début à la fin du code, pratiquement chaque ligne étant commentée. Il lui est cependant recommandé de mettre en œuvre de lui-même ce qui est exposé s'il veut s'approprier réellement les connaissances utilisées.

Un espace d'échanges vous est proposé sur le forum pour recevoir vos avis.
Commentez

- Titre : Apprendre le TreeView en Visual Basic

- Auteur : Jacques Malatier

- Parution : 13 février 2006

- Mise à jour : 27 février 2017

- Public visé :

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2017 Jacques Malatier. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- PRÉAMBULE

- AVANT-PROPOS

- PUBLIC CONCERNÉ

- CONFIGURATION REQUISE

- ORGANISATION DU PRATICIEL

- Conventions d'écritures

- Remerciements

- I. SECTION 1 - LES BASES DU TREEVIEW

- I-A. UN TREEVIEW, C'EST QUOI, ET POUR QUOI FAIRE ?

- I-B. LES NŒUDS (NODES)

- I-B-1. PROPRIÉTÉS

- I-B-2. MÉTHODES

- I-C. CONSTRUIRE UN TREEVIEW NON DÉPENDANT DE DONNÉES

- I-C-1. LES PRÉLIMINAIRES

- I-C-2. MÉTHODES DE BASE POUR CRÉER UN NŒUD

- I-C-2-a. SECTION DÉCLARATIONS

- I-C-2-b. CRÉATION DE L'ARBRE AU CHARGEMENT DE LA FEUILLE

- I-C-2-c. AJOUTER UN NŒUD DE MÊME NIVEAU HIÉRARCHIQUE

- I-C-2-d. AJOUTER UN NŒUD ENFANT

- I-C-2-e. CALCULER LA VALEUR DE LA CLÉ D'UN NOUVEAU NŒUD

- I-C-3. FONCTIONNALITÉS COMPLÉMENTAIRES

- I-C-3-a. AJOUTER UN NŒUD EN TÊTE D'UN NIVEAU HIÉRARCHIQUE

- I-C-3-b. AJOUTER UN NŒUD APRÈS LE NŒUD SÉLECTIONNÉ

- I-C-3-c. AJOUTER UN NŒUD AVANT LE NŒUD SÉLECTIONNÉ

- I-C-3-d. SUPPRIMER UN NŒUD SÉLECTIONNÉ

- I-D. LES ICÔNES DES FONCTIONS D'AJOUT ET DE SUPPRESSION

- I-E. CONCLUSION DE LA SECTION

- II. SECTION 2 - TREEVIEW DÉPENDANT

- II-A. PRÉLIMINAIRES

- II-B. LA BASE DE DONNÉES ET LA TABLE DES NŒUDS

- II-B-1. SÉLECTION OU CRÉATION ?

- II-B-2. CRÉATION DE LA BASE DE DONNÉES

- II-B-3. CRÉATION DE LA TABLE

- II-C. SAUVEGARDE DES NŒUDS DU TREEVIEW DANS LA TABLE

- II-C-1. SÉLECTION DE LA TABLE

- II-C-2. ÉCRITURE DANS LA TABLE

- II-C-2-a. LE NŒUD ROOT

- II-C-2-b. LES NŒUDS ENFANTS

- II-D. CONSTRUIRE UN TREEVIEW DÉPENDANT DES DONNÉES

- III. SECTION 3 - DÉPLACER DES NŒUDS

- III-A. DÉMARRER UN DRAG AND DROP (MOUSEMOVE)

- III-B. SÉLECTION DU NŒUD À DÉPLACER (MOUSEDOWN)

- III-C. DÉPLACEMENT DU NŒUD SÉLECTIONNÉ (DRAGDROP)

- III-D. FIN DU DÉPLACEMENT DU NŒUD SÉLECTIONNÉ (DRAGOVER)

PRÉAMBULE

AVANT-PROPOS

Ce praticiel s'inscrit dans la série des praticiels que j'ai déjà proposés sur développez. com(1) et… ailleurs(2). Comme pour l'ensemble de ces praticiels, la rédaction de celui-ci m'a été « inspirée » par le besoin que j'avais d'utiliser le composant Treeview, l'incompréhension (profonde) que j'avais de son utilisation, et l'absence de documentations et autres tutoriels qui me soit compréhensibles sur le sujet. Mais il est vrai qu'il faut m'expliquer longtemps…

J'ai donc pris mon courage à deux mains et avec celles qui me restaient, j'ai commencé à dépiauter la bête, à essayer de comprendre et à écrire d'une façon compréhensible(3), en tout cas par moi ce que je comprenais.

Le présent praticiel explique pas à pas le code du projet Didact-Treeview.vbp. Les deux éléments (projet VB et document Word) sont donc indissociables.

Je me suis inspiré de l'exemple de Microsoft « EXEMPLE : manipuler TREEVIEW.EXE et enregistrer les nœuds dans un contrôle Treeview », dont l'adresse est http://support.microsoft.com/default.aspx?scid=kb;fr;172272. Ce code est téléchargeable sur http://download.microsoft.com/download/vb60pro/install/1/w9xnt4/en-us/treeview.exe. J'ai toutefois largement remanié, complété et commenté ce code.

Cette démarche est à mon sens le meilleur moyen de s'approprier réellement un savoir (même incomplet…) et de le maîtriser. Aussi, je ne saurais trop vous inciter à ne pas vous contenter de la lecture du praticiel, ou à la copie de tout ou partie du code(4), mais à refaire pas à pas tout ce code. Ainsi, vous vous l'approprierez à votre tour…

PUBLIC CONCERNÉ

Ce praticiel s'adresse à des perfectionnants dans le langage Visual Basic. Cela implique que certaines portions du code n'y soient pas ou peu détaillées, ni du reste tout ce que je qualifierais « d'intendance ». Si ces explications vous font défaut, cela indique qu'il vous serait probablement préférable de commencer par l'ABC de VB… Vous pouvez également consulter utilement l'aide en ligne (MSDN).

Néanmoins, tout le code traitant directement du sujet annoncé et des éléments « actifs » permettant sa mise en œuvre sont, eux, traités en détail.

CONFIGURATION REQUISE

Nous utiliserons ADOX dans notre projet. Il est donc nécessaire d'avoir VB6 en version SP6 minimum. Vous pouvez télécharger cette version ici (version française)…

ORGANISATION DU PRATICIEL

Ce praticiel comprend trois sections :

- la première section expose les méthodes permettant de créer un nœud dans un treeview non dépendant de données en spécifiant sa position hiérarchique et sa position physique dans le niveau hiérarchique. Des boutons de commande et des icônes y sont créés pour ce faire.
Cette section ne présente pas de difficulté particulière. Après son étude, vous devriez pouvoir créer ex nihilo un treeview et ses nœuds ;

- dans la seconde section, nous créons et manipulons un treeview dépendant des données, c'est-à-dire dont les informations concernant les nœuds sont sauvegardées (et récupérées) dans (depuis) une base de données (ici une base Access).
Nous abordons dans cette section l'API ADOX pour ce qui est de la création de la base de données et de la table, et l'interface d'accès aux données ADO. Nous expliquons ces aspects, mais d'une façon relativement superficielle, bien que suffisante pour ce que nous voulons faire. Pour des compléments sur ces points, consultez mon praticiel sur l'accès aux données ADO(5) et les autres didacticiels de développez.com(6).
Nous traitons aussi dans cette section de l'utilisation du composant CommonDialog (boite de dialogue) afin de chercher, sélectionner ou sauvegarder une base de données.
Nous utiliserons une fonction récursive pour la sauvegarde des données depuis la base de données.
Vous pouvez sauter les passages traitant de la création de la base de données et de la table (ADOX et CommonDialog), car bien que complémentaire, ils n'apportent rien concernant directement le Treeview ;

- dans la troisième section, nous traitons des déplacements d'un nœud (Drag and drop).
Dans le projet VB, j'ai créé une feuille (TreeView_Test) qui permet de détailler concrètement le résultat de l'appui sur une des icônes de commande. Le code de cette feuille n'est pas détaillé dans le document Word du praticiel, mais il peut être des plus intéressant pour vous de l'étudier, car beaucoup des propriétés des nœuds y sont utilisées, ce dans un contexte différent du praticiel lui-même. Pour exécuter cette feuille, il faut la déclarer en tant qu'objet de démarrage dans les propriétés du projet.

Conventions d'écritures

Quelques conventions concernant la présentation du document :

Cette icône annonce un point important. La teneur de ce point important est présentée en italique.

Cette icône annonce un rappel ou une information complémentaire.

Cette icône annonce une remarque. La teneur de cette remarque est présentée en italique.

Cette icône renvoie à une autre partie du document, ou à une documentation externe, pour plus d'information. Il suffit de cliquer sur l'adresse proposée pour atteindre cette partie, si elle est incluse dans le document.

Remerciements

Je remercie tous ceux qui par leurs réponses à certaines de mes demandes sur le forum m'ont orienté vers certaines des solutions exposées ici, notamment en ce qui concerne les fonctions récursives. Ils se reconnaîtront.

Je remercie tout particulièrement Xo qui a assuré la relecture de l'ensemble du praticiel. Pour être particulièrement rétif à ce genre d'exercice, je sais à quel point c'est astreignant et rébarbatif.

Je remercie également Thierry Aim pour la génération du document sous format PDF.

I - SECTION 1 - LES BASES DU TREEVIEW

I-A - UN TREEVIEW, C'EST QUOI, ET POUR QUOI FAIRE ?

C'est un moyen très visuel de présenter des objets sous une forme hiérarchique, lesdits objets pouvant être notamment des données. On peut trouver ce type de présentation hiérarchique pour « afficher les titres d'un document, les entrées d'un index, les fichiers et les dossiers d'un disque, ou tout autre type d'information qu'il peut être utile de présenter sous la forme d'une liste hiérarchique » (MSDN). Un exemple classique de l'utilisation d'un Treeview est l'arborescence de l'explorateur Windows.

Un treeview est composé de nœuds organisés d'une façon hiérarchique. Il y a donc au moins un nœud racine, comprenant éventuellement un ou des nœuds enfants. Chaque nœud enfant peut lui-même contenir un ou des nœuds enfants dont il est le père, and so one…

Donc, si on veut bien en maîtriser la programmation, il convient de bien connaître les éléments d'un treeview, en l'occurrence les nœuds (objets Nodes), leurs propriétés et méthodes, et leur organisation.

I-B - LES NŒUDS (NODES)

Un nœud (Node) est un objet qui contient du texte et des images. Les nœuds sont organisés en arborescence, dans un treeview, comme ci-dessous. Comme déjà dit, un nœud peut être enfant d'un nœud d'un niveau supérieur et parent d'un nœud d'un niveau inférieur, exemple le nœud Child 5_. Le texte du nœud est, vous l'auriez deviné, « Child 5 » et l'image est le petit dossier jaune(7).

Fig. 1 - Exemple de TreeView

Les nœuds d'un treeview sont réunis dans la collection Nodes, collection qui s'utilise comme toutes les collections… Ainsi, un Node de la collection Nodes est indiqué suivant la syntaxe

treeview.Nodes.Item(index)

… ou plus simplement :

treeview.Nodes (index)

Voir MSDN pour plus d'informations (Node, objet ; Nodes, collection, etc.)

I-B-1 - PROPRIÉTÉS

Un objet Node est doté de diverses propriétés dont je n'expose(8) ici que les principales, en tout cas celles dont nous aurons besoin dans ce praticiel. Commençons par les propriétés qui déterminent et/ou renvoient une référence à un objet Node.

- FirstSibling : renvoie une référence au premier fils d'un objet Node dans un contrôle TreeView (MSDN). Le premier fils est l'objet Node qui apparaît à la première position d'un niveau d'une hiérarchie de nœuds.

Il me semble que la définition donnée par MSDN est quelque peu sujette à induire en erreur. En effet, cette propriété ne fait pas textuellement appel « au premier fils d'un objet Node », c'est-à-dire au premier nœud du niveau hiérarchique suivant tel que l'on pourrait le comprendre si on s'arrête à la première lecture. Il faut bien lire la suite (en remarque…) : « le premier fils est l'objet Node qui apparaît à la première position d'un niveau hiérarchique ».

Donc, le code TreeView1.Nodes(mnIndex).FirstSibling.Index ne renvoie pas l'index du premier nœud fils de nœud d'index mnIndex, mais l'index du premier nœud du niveau hiérarchique auquel appartient ledit nœud indexé mnIndex.

Ainsi, TreeView1.Nodes(2).FirstSibling.Text renvoie « Last 1_ » dans l'exemple de TreeView figure 1, et non « Child 3_ ».

- LastSibling : renvoie une référence au dernier fils d'un objet Node dans un contrôle TreeView (MSDN). Le dernier fils est l'objet Node qui apparaît à la dernière position d'un niveau d'une hiérarchie de nœuds. La même remarque que pour la propriété FirstSibling s'impose donc. Dans notre exemple, TreeView1.Nodes(2).FirstSibling.Text renvoie « Last 2_ ». De même, TreeView1.Nodes(1).FirstSibling.Text renvoie également « Last 2_ ».

- Parent : renvoie ou définit l'objet parent d'un objet Node (MSDN). Là, pas d'ambiguïté, le parent, c'est le parent…

Si l'objet Node n'a pas de parent, le code renvoie une erreur. Nous nous servirons d'ailleurs plus avant (section 2 D) de ce cas d'espèce. Dans notre exemple, le code TreeView1.Nodes(i).Parent.Text renverrait une erreur pour i=1 ou 2.

- Root : renvoie une référence à l'objet Node racine d'un objet Node sélectionné. Le nœud racine est le premier nœud d'une branche. Si j'osais, je dirais que c'est un nœud tout en haut d'une branche du treeview (ce que je trouve marrant pour une racine).

- Children : renvoie le nombre d'objets Node fils contenus dans un objet Node (MSDN).

- Child : renvoie une référence au premier fils d'un objet Node dans un contrôle TreeView (MSDN), c'est-à-dire le premier nœud du niveau hiérarchique immédiatement inférieur au nœud en cours.

- Previous : renvoie une référence au fils précédent d'un objet Node (MSDN). Mais, il est préférable de préciser que cette propriété renvoie une référence du nœud de même niveau qui précède le nœud en cours. Donc, si le nœud en cours est le premier nœud d'un niveau, la propriété renvoie une erreur.

- Next : renvoie une référence à l'objet Node fils suivant de l'objet Node d'un contrôle TreeView (MSDN). La même remarque que pour la propriété Previous. La propriété renvoie la référence du nœud suivant de même niveau que le nœud en cours, ou une erreur s'il n'y a pas de nœud suivant dans le même niveau hiérarchique.

- Count : renvoie le nombre d'objets contenus dans une collection (MSDN) (en l'occurrence ici la collection Nodes).

- Expended : renvoie ou définit une valeur qui détermine si un objet Node dans un contrôle TreeView est actuellement développé ou réduit (MSDN). Pas de remarque particulière.

- Index : renvoie ou définit un nombre qui identifie de manière unique un objet dans une collection (MSDN). Les valeurs d'index sont définies suivant l'ordre de création des objets Node, comme pour toute collection.

- Le premier nœud a pour index la valeur 1, la collection Nodes étant de base 1.

- La valeur de la propriété index peut être modifiée en cas de tri.

- Le terme « création » est quelque peu imprécis. J'utilise le terme « chargement ».

- Item : Renvoie un membre donné d'un objet Collection en fonction de sa position ou de sa clé (MSDN). La syntaxe est object.Item(index) dans laquelle object est un nœud et index est la référence de l'objet, référence pouvant être une expression numérique (correspondant à la propriété Index précédente) ou une expression de chaîne de caractères (correspondant à la propriété Key suivante).

- Key : Renvoie ou définit une chaîne qui identifie de manière unique un membre dans une collection (MSDN).

Même si cela semble être le cas dans notre exemple, la propriété Key n'a strictement rien à voir avec la propriété Index. Toutes deux identifient sans aucune ambiguïté un objet Node, mais la propriété Key est déterminée une fois pour toutes lors de la création d'un nœud et ne peut être modifiée, alors que la propriété Index est déterminée automatiquement au chargement de l'arbre.

L'utilisation d'un nombre en string (par exemple "1") génère une erreur. Si vous souhaitez utiliser un nombre en tant que clé, il faut lui ajouter au moins un caractère (par exemple "1a ou 1_".
Voir à ce sujet http://support.microsoft.com/kb/204054.

- Text : renvoie ou définit le texte contenu dans un objet (MSDN). C'est en fait une partie de ce qui sera affiché au niveau d'un nœud (l'autre partie étant éventuellement l'image qui aura été définie).

- Selected : renvoie ou définit une valeur qui détermine si un objet est sélectionné (MSDN).

- Image : renvoie ou définit une valeur qui détermine l'objet ListImage d'un contrôle ImageList à associer à un autre objet (MSDN). Généralement, on utilise cette propriété pour afficher la petite croix au niveau de chaque nœud.

Ces propriétés seront utilisées largement dans l'application support. Elles seront donc exposées et expliquées dans leur contexte dans les différents chapitres de ce praticiel. J'ai joint au projet une feuille (TreeView_Test) qui met en œuvre ces différentes propriétés et qui montre visuellement leur effet.

I-B-2 - MÉTHODES

Les méthodes que nous utiliserons sont exposées et utilisées § I.C.2 et I.C.3 de cette section.

I-C - CONSTRUIRE UN TREEVIEW NON DÉPENDANT DE DONNÉES

I-C-1 - LES PRÉLIMINAIRES

Nous allons ici construire un treeview ex nihilo, c'est-à-dire sans utiliser de données existantes (ni les sauvegarder du reste), uniquement avec du code. L'objectif est simplement d'apprendre à créer un treeview avec les nœuds voulus dans les positions physique et hiérarchique voulues, sans s'embarrasser de difficultés superflues compte tenu du fait que nous débutons en la matière.

Créez donc un projet, de nom Didact_Treeview, et un formulaire de nom Treview_Manuel. Ajoutez au projet le composant Microsoft Windows Common Controls (ici version 6.0 SP6) qui contient le composant Treeview. Insérez le treeview dans le formulaire. Insérez également le composant ImageList, présent dans la barre d'outils(9), qui contiendra les images (icônes) qui seront affichées en regard de chaque item du treeview. Liez le treeview à la liste d'images (boite de propriétés du treeview, comme ci-dessous, accessible par son menu contextuel).

Fig.1 - Liaison du treeview avec la liste d'images

Vous obtenez à peu près ceci.

Fig.2 - Création de la fenêtre

Il nous reste à mettre les images voulues dans la liste. Ouvrez sa fenêtre de propriétés (menu contextuel), et insérez les images contenues dans le dossier Images du projet exemple. Vous obtenez à peu près ceci.

Fig.3 - Création de la liste d'images

Voilà, nous en avons terminé avec les préliminaires. Passons maintenant aux choses sérieuses.

I-C-2 - MÉTHODES DE BASE POUR CRÉER UN NŒUD

Précisons qu'un objet Node (un nœud) fait partie d'une collection Nodes et qu'il se manipule à l'aide des méthodes d'une collection standard. Dans les procédures suivantes, nous utiliserons abondamment la méthode Add de la collection Nodes, puisque notre objet sera de créer des nouveaux nœuds dans un arbre.

Avec les procédures ci-après, nous construisons un treeview non dépendant de données, simplement en ajoutant des nœuds et en définissant leur position hiérarchique. Je vous conseille fortement d'étudier l'ensemble de ces trois procédures, puis de les exécuter pas à pas pour en constater les effets. C'est à mon sens la meilleure façon de comprendre le mécanisme, en tout cas, c'est celle que j'ai employée…

I-C-2-a - SECTION DÉCLARATIONS

Pensons avant tout à nos déclarations de variables et autres objets. Vous retrouverez ces variables tout au long du document.

Code 1 - Section de déclarations

N°

Code

Commentaires

1

Option Explicit

2

Dim cat As New ADOX.Catalog

Catalogue ADOX.

3

Dim rsNoeuds As ADODB.Recordset

Recordset.

4

Dim mnIndex As Integer

Contiendra l'index d'un nœud.

5

Dim moDragNode As Object

Un item pouvant être déplacé.

6

Dim boFlagEC As Boolean

Drapeau indiquant si une opération de drag and drop est en cours.

7

Dim objDragNode As Object

Contiendra la référence à un nœud en cours de déplacement.

8

Dim tblDrag(3, 3) As String

Tableau qui contiendra diverses informations sur un nœud déplacé.

Code 1

Option Explicit
Dim cat As New ADOX.Catalog
Dim rsNoeuds As ADODB.Recordset
Dim mnIndex As Integer
Dim moDragNode As Object
Dim boFlagEC As Boolean
Dim objDragNode As Object
Dim tblDrag(3, 3) As String

I-C-2-b - CRÉATION DE L'ARBRE AU CHARGEMENT DE LA FEUILLE

Cette première procédure (Form_Load) fait appel à deux procédures complémentaires (cmdLast_Click et cmdChild_Click) afin d'ajouter des nœuds à l'arbre en définissant leur position physique (où placer le nœud) et hiérarchique (quelle est sa position hiérarchique par rapport à un autre nœud : même niveau ou enfant). Ces deux procédures utilisent une fonction (GetNextKey) pour déterminer la valeur de la clé de chaque nœud créé.

Code 2 - Construction d'un arbre (Sub Form_Load)

N°

Code

Commentaires

1

Private Sub Form_Load()

Remplissage d'un treeview.

2

Set moDragNode = Nothing

3

cmdLast_Click

Appel à la procédure pour ajouter un nœud de même niveau que le nœud sélectionné.

4

cmdLast_Click

Bis repetita. On veut le remplir cet arbre, non ? Et puis, cela nous permet d'étudier la méthode d'ajout et de voir le résultat.

5

TreeView1.Nodes(1).Selected = True

On sélectionne le nœud d'index 1 qui devient donc le nœud en cours.

6

cmdChild_Click

Appel à la procédure pour ajouter un nœud enfant au nœud en cours.

7

cmdChild_Click

Bis repetita.

8

TreeView1.Nodes(2).Selected = True

9

cmdChild_Click

10

TreeView1.Nodes(5).Selected = True

11

cmdChild_Click

12

End Sub

Code 2

Private Sub Form_Load()
Set moDragNode = Nothing
cmdLast_Click
cmdLast_Click
TreeView1.Nodes(1).Selected = True
cmdChild_Click
cmdChild_Click
TreeView1.Nodes(2).Selected = True
cmdChild_Click
TreeView1.Nodes(5).Selected = True
cmdChild_Click
End Sub

I-C-2-c - AJOUTER UN NŒUD DE MÊME NIVEAU HIÉRARCHIQUE

La procédure ci-après ajoute un nœud après le nœud en cours (sélectionné) et au même niveau hiérarchique. Si aucun nœud n'est sélectionné, le nouveau nœud est ajouté en dernière position de la hiérarchie précédemment utilisée (voir explication concernant la position hiérarchique d'un nœud).

Code 3 - Ajout d'un nœud de même niveau en dernière position(Sub cmdLast_Click)

N°

Code

Commentaires

1

Private Sub cmdLast_Click()

Ajoute un nœud de même niveau que le nœud en cours, ou en dernière position si aucun nœud n'est sélectionné.

2

Dim skey As String

3

skey = GetNextKey()

Appel à la fonction qui retournera la valeur de la clé pour le nouveau nœud.

4

On Error GoTo myerr Gestion d'erreur.

5

TreeView1.Nodes.Add TreeView1.SelectedItem.Index, tvwLast, skey, "Last " & skey, 1, 2

Si le treeview n'a aucun nœud sélectionné, cette ligne génère une erreur 91, valeur utilisée par la gestion d'erreur (étiquette myerr:) afin de déterminer l'action à mener. Pour une explication détaillée du code de cette ligne, voyez ci-après.

6

Exit Sub

Sortie de la procédure s'il n'y a pas d'erreur.

7

myerr:

Étiquette pour la gestion d'erreur.

8

TreeView1.Nodes.Add , tvwLast, skey, "Last " & skey, 1, 2

Ajoute un nœud racine en dernière position, aucun nœud n'étant sélectionné.

9

Exit Sub

Fin de la gestion d'erreur.

10

End Sub

Code 3

Private Sub cmdLast_Click()
Dim skey As String
skey = GetNextKey()
On Error GoTo myerr Gestion d'erreur.
TreeView1.Nodes.Add TreeView1.SelectedItem.Index, tvwLast, skey, "Last " & skey, 1, 2
Exit Sub
myerr:
TreeView1.Nodes.Add , tvwLast, skey, "Last " & skey, 1, 2
Exit Sub
End Sub

La ligne de code 5 du tableau ci-dessus demande quelques explications, même si l'aide en ligne vous serait sans aucun doute suffisante. Ce code ajoute un nœud à l'arbre suivant la syntaxe object.Add(relative, relationship, key, text, image, selectedimage).

Tableau 1 - Paramètres de la méthode Add

Éléments

Description

Application

objet

Expression d'objet qui correspond à un objet figurant dans la rubrique « Application ».

TreeView1.Nodes

relative

Facultatif. Numéro d'index ou clé d'un objet Node existant. La relation entre le nouveau nœud et ce nœud existant figure dans l'argument suivant, relationship.

TreeView1.SelectedItem.Index
C'est ici l'index du nœud sélectionné.

relationship

Facultatif. Spécifie l'emplacement relatif de l'objet Node, comme indiqué dans la section Valeurs.

Ici, tvwLast
(voir explication ci-après). La valeur par défaut de cet argument est tvwNext.

key

Facultatif. Chaîne unique permettant de récupérer l'objet Node à l'aide de la méthode Item.

Ici la valeur renvoyée par la fonction GetNextKey (ligne 3).

text

Chaîne qui apparaît dans l'objet Node.

Ici "Last " & skey
ainsi on pourra lire directement dans l'arbre l'action ayant généré le nœud et la clé dudit nœud.

image

Facultatif. Index d'une image au sein d'un contrôle ImageList associé.

Rappelez-vous que nous avons en effet créé une liste d'images avec deux images(10). 'image d'index 1 (dossier fermé) sera associée aux nœuds non sélectionnés…

Selectedimage

Facultatif. Index d'une image dans un contrôle ImageList associé et qui est affichée lorsque l'objet Node est sélectionné.

… et l'image d'index 2 (dossier ouvert) au nœud sélectionné.

Valeurs de l'argument relationship

Nous allons retrouver souvent par la suite le paramètre relationship qui peut prendre les valeurs suivantes :

tvwLast : valeur indiquant que le nouveau nœud sera placé en dernière position du même niveau que celui du nœud spécifié dans l'argument relative (comme dans code 2). Tout nœud ajouté par la suite le sera au même niveau que celui qui vient d'être ajouté, en l'absence d'une nouvelle sélection de nœud(11) ;

tvwChild : valeur indiquant que le nouveau nœud sera placé en tant qu'enfant du nœud spécifié dans l'argument relative (comme dans le code 3) ;

tvwFirst : valeur indiquant que le nouveau nœud sera placé en première position du même niveau que celui du nœud spécifié dans l'argument relative (comme dans code 5) ;

tvwNext : valeur indiquant que le nouveau nœud sera placé à la suite du nœud spécifié dans l'argument relative. tvwPrevious : valeur indiquant que le nouveau nœud sera avant le nœud nommé dans l'argument relative (comme dans code 7).

I-C-2-d - AJOUTER UN NŒUD ENFANT

Avec la procédure suivante, nous ajoutons un nœud enfant, c'est-à-dire de niveau hiérarchique immédiatement inférieur, au nœud sélectionné.

Code 4 - Ajout d'un nœud enfant (Sub cmdChild_Click)

N°

Code

Commentaires

1

Private Sub cmdChild_Click()

Ajoute un nœud enfant du nœud en cours.

2

Dim oNodex As Node

Déclaration d'une variable objet Node.

3

Dim skey As String

Déclaration d'une variable string qui contiendra la valeur calculée de la clé pour le nouveau nœud.

4

Dim iIndex As Integer

Déclaration d'une variable integer qui contiendra la valeur d'index de l'item sélectionné dans l'arbre.

5

On Error GoTo myerr

Gestion d'erreur.

6

iIndex = TreeView1.SelectedItem.Index

S'il n'y a pas de nœud sélectionné, la ligne de code génère une erreur 91 qui sera traitée par la gestion d'erreur (étiquette myerr:).

7

skey = GetNextKey()

Appel de la fonction qui retournera la valeur calculée de la clé pour le nouveau nœud.

8

Set oNodex = TreeView1.Nodes.Add(iIndex, tvwChild, skey, "Child " & skey, 1, 2)

9

oNodex.EnsureVisible

Le nouveau nœud enfant est visible.

10

Exit Sub

Fin de la procédure s'il n'y a pas d'erreur.

11

myerr:

Étiquette de la gestion d'erreur.

12

MsgBox ("Vous devez sélectionner un nœud pour ajouter un nœud enfant" & vbCrLf & _
"Si le treeview est vide, cliquez sur le bouton de commande Add Last pour créer le premier nœud.")

Affiche un message informant l'utilisateur de la marche à suivre.

13

Exit Sub

Fin de la gestion d'erreur.

14

End Sub

Code 4

Private Sub cmdChild_Click()
Dim oNodex As Node
Dim skey As String
Dim iIndex As Integer
On Error GoTo myerr
iIndex = TreeView1.SelectedItem.Index
skey = GetNextKey()
Set oNodex = TreeView1.Nodes.Add(iIndex, tvwChild, skey, "Child " & skey, 1, 2)
oNodex.EnsureVisible
Exit Sub
myerr:
MsgBox ("Vous devez sélectionner un nœud pour ajouter un nœud enfant" & vbCrLf & "Si le treeview est vide, cliquez sur le bouton de commande Add Last pour créer le premier nœud.")
Exit Sub
End Sub

I-C-2-e - CALCULER LA VALEUR DE LA CLÉ D'UN NOUVEAU NŒUD

La procédure suivante est de première importance(12), car elle détermine la valeur de la clé pour le nœud en voie de création.

Code 5 - Calcul de la valeur de la clé d'un nouveau nœud (Function GetNextKey)

N°

Code

Commentaires

1

Private Function GetNextKey() As String

La fonction retourne une nouvelle valeur de clé pour chaque nœud ajouté au TreeView (jusqu'à 999 nœuds).
Chaque nœud nécessite une clé unique. Si vous permettez aux utilisateurs de supprimer un nœud, il n'est pas possible d'utiliser [nbre de nœuds + 1] pour calculer la valeur de la clé d'un nouveau nœud.

2

Dim sNewKey As String

3

Dim iHold As Integer

Déclaration d'une variable qui contiendra la valeur d'index du nouveau nœud.

4

Dim i As Integer

5

On Error GoTo myerr

Gestion d'erreur.

6

iHold = Val(TreeView1.Nodes(1 ).Key)

La variable contient ainsi la valeur 1, donc l'index du premier nœud. Cette ligne de code génère l'erreur #35600 s'il n'y a aucun nœud dans le treeview et c'est donc le traitement d'erreur qui interviendra.

7

i = TreeView1.Nodes.Count

La variable i reçoit le nombre de nœuds de la collection Nodes. On pourra ainsi y faire référence pour atteindre le dernier nœud de la collection selon son index.

8

iHold = Val(TreeView1.Nodes(i).Key)

Nous avons ici la valeur de la clé du dernier nœud de la collection…

9

iHold = iHold + 1

… et là la valeur de la clé du nœud en voie de création.

10

sNewKey = CStr(iHold) & "_"

La variable est affectée de la concaténation de la conversion en string du contenu de iHold (fonction CStr) et du caractère de soulignement.

11

GetNextKey = sNewKey

La fonction présente retourne la valeur ainsi calculée comme étant la valeur de la clé du nouveau nœud.

12

Exit Function

OUF !!!

13

myerr:

Gestion d'erreur.

14

GetNextKey = "1_"

Le Treeview étant vide, il est retourné la valeur "1_" pour la clé du premier nœud.

15

Exit Function

16

End Function

Code 5

Private Function GetNextKey() As String
Dim sNewKey As String
Dim iHold As Integer
Dim i As Integer
On Error GoTo myerr
iHold = Val(TreeView1.Nodes(1 ).Key)
i = TreeView1.Nodes.Count
iHold = Val(TreeView1.Nodes(i).Key)
iHold = iHold + 1
sNewKey = CStr(iHold) & "_"
GetNextKey = sNewKey
Exit Function
myerr:
GetNextKey = "1_"
Exit Function
End Function

Pourquoi n'utilisons-nous pas directement TreeView1.Nodes.Count+1 pour calculer la valeur de la clé ? C'est le nœud (si, je l'ose…) de l'histoire. Réfléchissez-y un petit peu…

Vous avez trouvé ? Avez-vous seulement cherché ? Oui ? Good… Donc pas la peine de s'appesantir ! Si ? Bon…

En fait, l'index du dernier nœud de la collection Nodes ne peut servir de valeur de clé, car il y aurait risque de duplication de valeur de clé, ce qui est interdit et provoquerait une erreur. En effet, il peut y avoir des trous dus à la suppression de nœuds. Par exemple, dans une collection de trois nœuds (d'index 1,2 et 3 et de clé "1", "2" et "3"), si nous supprimons le nœud indexé 3, nous n'avons plus qu'une collection de deux nœuds. La valeur de TreeView1.Nodes.Count serait donc égale à 2, l'index du dernier nœud serait donc 2… mais si nous incrémentons cette valeur de 1 pour calculer la clé du nouveau nœud, nous aurions une valeur de clé de 2+1=3.

Or, cette valeur de clé est déjà affectée au nœud d'index actuel 2 (mais à l'origine le troisième nœud). Le tableau ci-dessous le montre bien.

Tableau 2 - Évolution des valeurs de clés et d'index

À l'origine

Après suppression

Index

Clé

Count

Index

Clé

Count

1

1

1

1

1

1

2

2

2

3

3

2

3

3

3

Par contre, les valeurs de clé des nœuds ne sont nullement affectées de la suppression de nœuds. Elles restent ce qu'elles ont été à l'origine, et la valeur de clé du dernier nœud de la collection est donc forcément la plus élevée des valeurs. Si nous l'incrémentons de 1, la valeur de clé obtenue sera forcément une nouvelle valeur.

I-C-3 - FONCTIONNALITÉS COMPLÉMENTAIRES

Avec ce que nous avons vu jusqu'à maintenant, vous pouvez d'ores et déjà ajouter des nœuds à un niveau hiérarchique ou enfant. Il y a cependant quelques méthodes un peu plus raffinées permettant de préciser le niveau hiérarchique et la position physique d'un nœud. C'est ce que nous allons explorer maintenant.

I-C-3-a - AJOUTER UN NŒUD EN TÊTE D'UN NIVEAU HIÉRARCHIQUE

Cette procédure est le pendant de la procédure Sub cmdLast_Click, à la différence près qu'elle permet d'ajouter un nœud en tête du niveau hiérarchique en cours (celui du nœud sélectionné) au lieu de le placer en queue.

Code 6 - Ajout d'un nœud de même niveau en première position (Sub cmdFirstt_Click)

N°

Code

Commentaires

1

Private Sub cmdFirst_Click()

Ajoute un nœud en tête du niveau du nœud en cours (paramètre tvwFirst).

2

Dim skey As String

3

Dim iIndex As Integer

4

On Error GoTo myerr

Gestion d'erreur.

5

iIndex = TreeView1.SelectedItem.Index

Affectation à la variable de la valeur de l'index de l'item sélectionné.
Cette ligne de code génère une erreur 91 si aucun nœud n'est sélectionné dans le treeview.

6

skey = GetNextKey()

La fonction appelée retourne la valeur de la clé pour le nouveau nœud.

7

TreeView1.Nodes.Add iIndex, tvwFirst, skey, "First " & skey, 1, 2

Nous retrouvons la même instruction Add dont seul le paramètre qui change est le paramètre relative qui prend ici la valeur tvwFirst (voir explications §I.C.2.c).

8

Exit Sub

9

myerr:

Traitement des erreurs.

10

MsgBox ("You must select a Node to do an Add First" & vbCrLf _
& "If the TreeView is empty us Add Last to create the first node")

Affiche un message demandant à l'utilisateur de sélectionner un nœud. C'est le traitement de l'erreur 91 éventuellement générée ligne 5.

11

Exit Sub

12

End Sub

Code 6

Private Sub cmdFirst_Click()
Dim skey As String
Dim iIndex As Integer
On Error GoTo myerr
iIndex = TreeView1.SelectedItem.Index
skey = GetNextKey()
TreeView1.Nodes.Add iIndex, tvwFirst, skey, "First " & skey, 1, 2
Exit Sub
myerr:
MsgBox ("You must select a Node to do an Add First" & vbCrLf _
& "If the TreeView is empty us Add Last to create the first node")
Exit Sub
End Sub

I-C-3-b - AJOUTER UN NŒUD APRÈS LE NŒUD SÉLECTIONNÉ

Cette procédure ajoute un nœud au même niveau après le nœud sélectionné. Toutes ces procédures se ressemblent, que cela en est lassant, n'est-il pas ?

Code 7 - Ajout d'un nœud après le nœud sélectionné, au même niveau (cmdNext_Click)

N°

Code

Commentaires

1

Private Sub cmdNext_Click()

Ajoute un nœud après le nœud en cours, de même niveau.

2

Dim skey As String

3

Dim iIndex As Integer

4

On Error GoTo myerr

Gestion d'erreur.

5

iIndex = TreeView1.SelectedItem.Index

Affectation à la variable de la valeur de l'index de l'item sélectionné.
Cette ligne génère une erreur 91 si aucun nœud n'est sélectionné.

6

skey = GetNextKey()

La fonction appelée retourne la valeur de la clé pour le nouveau nœud.

7

TreeView1.Nodes.Add iIndex, tvwNext, skey, "Next " & skey, 1, 2

Même litanie que pour les procédures précédentes. Ici, l'argument relative est tvwNext (voir explications §I.C.2.c).

8

Exit Sub

9

myerr:

Étiquette gestion d'erreur.

10

MsgBox ("You must select a Node to do an Add Next" & vbCrLf _
& "If the TreeView is empty us Add Last to create the first node")

Affiche un message demandant à l'utilisateur de sélectionner un nœud.

11

Exit Sub

12

End Sub

Code 7

Private Sub cmdNext_Click()
Dim skey As String
Dim iIndex As Integer
On Error GoTo myerr
iIndex = TreeView1.SelectedItem.Index
skey = GetNextKey()
TreeView1.Nodes.Add iIndex, tvwNext, skey, "Next " & skey, 1, 2
Exit Sub
myerr:
MsgBox ("You must select a Node to do an Add Next" & vbCrLf _
& "If the TreeView is empty us Add Last to create the first node")
Exit Sub
End Sub

I-C-3-c - AJOUTER UN NŒUD AVANT LE NŒUD SÉLECTIONNÉ

Cette procédure ajoute un nœud au même niveau avant le nœud sélectionné. Que dire de plus… On va présenter le code après la description des différents éléments.

Code 8 - Ajout d'un nœud avant le nœud sélectionné, au même niveau (cmdPrevious_Click)

N°

Code

Commentaires

1

Private Sub cmdPrevious_Click()

Ajoute un nœud avant le nœud sélectionné, au même niveau.

2

Dim skey As String

3

Dim iIndex As Integer

4

On Error GoTo myerr

5

iIndex = TreeView1.SelectedItem.Index

Affectation à la variable de la valeur de l'index de l'item sélectionné.
Cette ligne de code génère une erreur 91 si aucun nœud n'est sélectionné.

6

skey = GetNextKey()

La fonction renvoie la valeur de la clé du nouveau nœud.

7

TreeView1.Nodes.Add iIndex, tvwPrevious, skey, "Previous " & skey, 1, 2

Cette fois, c'esttvwPrevious… (voir explications §I.C.2.c).

8

Exit Sub

9

myerr:

Gestion d'erreur.

10

MsgBox ("Vous devez sélectionner un nœud pour pouvoir en ajouter un avant." & vbCrLf _
& "Si le TreeView est vide utilisez Add Last pour créer le premier nœud.")

Affiche un message demandant à l'utilisateur de sélectionner un nœud.

11

Exit Sub

12

End Sub

Code 8

Private Sub cmdPrevious_Click()
Dim skey As String
Dim iIndex As Integer
On Error GoTo myerr
iIndex = TreeView1.SelectedItem.Index
skey = GetNextKey()
TreeView1.Nodes.Add iIndex, tvwPrevious, skey, "Previous " & skey, 1, 2
Exit Sub
myerr:
MsgBox ("Vous devez sélectionner un nœud pour pouvoir en ajouter un avant." & vbCrLf _
& "Si le TreeView est vide utilisez Add Last pour créer le premier nœud.")
Exit Sub
End Sub

Dans les paragraphes précédents ($I.C.3.a à $I.C.3.c), nous avons écrit à peu près le même code. Il y a deux différences entre ces procédures, dans la fonction add de chacune (voir les lignes 7 des dites procédures) :

- le nœud en cours, identifié par la valeur du paramètre relative de la fonction add ;

- la position du nœud ajouté par rapport au nœud en cours, position déterminée par la valeur du paramètre relationship de la même fonction add.

Dès lors, vous en avez déduit tout naturellement qu'il est possible de remplacer ces trois procédures par une seule, à laquelle on passerait éventuellement en argument les valeurs de ces propriétés. Je dis bien « éventuellement », car ces paramètres sont optionnels. Et « éventuellement » se traduit par l'argument « optional » dans la déclaration de la procédure.

Nous aurions alors une procédure « Ajouter_Noeud » déclarée ainsi :

Private Ajouter_Noeud(optional varRelative as variant, optional intRelation as integer)

Le type de l'argument varRelative est « variant », car la propriété « relative » peut contenir un numéro d'index ou une clé.

I-C-3-d - SUPPRIMER UN NŒUD SÉLECTIONNÉ

Ça sent la fin, en tout cas pour ledit nœud…

Code 9 - Suppression d'un nœud et de tous ses enfants (cmdRemove_Click)

N°

Code

Commentaires

1

Private Sub cmdRemove_Click()

Suppression du nœud sélectionné et de ses enfants le cas échéant.

2

Dim iIndex As Integer

3

On Error GoTo myerr

4

iIndex = TreeView1.SelectedItem.Index

Affectation à la variable de la valeur de l'index de l'item sélectionné.
Cette ligne de code génère une erreur 91 si aucun nœud n'est sélectionné.

5

TreeView1.Nodes.Remove iIndex

Supprime le nœud en cours.

6

Exit Sub

7

myerr:

Gestion d'erreur.

8

MsgBox ("Vous devez sélectionner un nœud pour le supprimer.")

Message pour avertir l'utilisateur…

9

Exit Sub

10

End Sub

Code 9

Private Sub cmdRemove_Click()
Dim iIndex As Integer
On Error GoTo myerr
iIndex = TreeView1.SelectedItem.Index
TreeView1.Nodes.Remove iIndex
Exit Sub
myerr:
MsgBox ("Vous devez sélectionner un nœud pour le supprimer.")
Exit Sub
End Sub

I-D - LES ICÔNES DES FONCTIONS D'AJOUT ET DE SUPPRESSION

Nous allons nous offrir un petit supplément, en l'occurrence des icônes nous permettant de travailler dans notre arbre. Ces icônes(13) appellent les fonctions ayant été définies par le groupe des six boutons de commandes de la frame 1.

N°

Code

Commentaires

1

Private Sub cmdArbre_Click(Index As Integer)

Un classique Select Case qui ne nécessite pas d'explication. Dans le cas contraire, je ne saurais trop vous inciter à commencer par le commencement, i.e. des didacticiels ou praticiels qui traitent des débuts (par exemple dans « Accéder aux données ADO », l'utilisation d'un Select Case est expliquée pas à pas pour les boutons de navigation).

2

Select Case Index

3

Case 0

4

cmdFirst_Click

5

Case 1

6

cmdLast_Click

7

Case 2

8

cmdNext_Click

9

Case 3

10

cmdPrevious_Click

11

Case 4

12

cmdChild_Click

13

Case 5

14

cmdRemove_Click

15

End Select

16

End Sub

Code

Private Sub cmdArbre_Click(Index As Integer)
Select Case Index
Case 0
cmdFirst_Click
Case 1
cmdLast_Click
Case 2
cmdNext_Click
Case 3
cmdPrevious_Click
Case 4
cmdChild_Click
Case 5
cmdRemove_Click
End Select
End Sub

I-E - CONCLUSION DE LA SECTION

Voilà. Nous avons appris dans cette section de quoi construire et manipuler un treeview et ses nœuds. C'est un bon début. Cependant, un composant qu'il faut redessiner à chaque lancement est un quelque peu étriqué, pour tout dire totalement inutilisable. C'est bien pour apprendre, mais il faudrait pouvoir sauvegarder le treeview dans son état après utilisation. C'est ce que nous allons explorer dans la section suivante.

II - SECTION 2 - TREEVIEW DÉPENDANT

II-A - PRÉLIMINAIRES

Cette section comprend des aspects ne concernant pas directement les treeview, mais les aspects de liaisons aux données avec ADOX. J'ai toutefois décidé de les inclure (donc de les expliquer quelque peu) dans ce praticiel afin qu'il se suffise à lui-même.

Déclarez dans les références du projet Microsoft ActiveX Data Objects 2.x Library (ici 2.8), puisque nous allons utiliser ADO. Ajoutez également le composant CommonDialog depuis la barre d'outils.

II-B - LA BASE DE DONNÉES ET LA TABLE DES NŒUDS

II-B-1 - SÉLECTION OU CRÉATION ?

Les créations de la base de données et de la table sont réalisées dans une sous-procédure (appelée lignes 26 et 27). Une fois la base et la table créées et sélectionnées, il est fait appel, ligne 28, à la procédure d'écriture dans la table des informations concernant les nœuds du treeview, ce qui se rapporte directement à notre sujet. Vous pouvez donc sauter ce paragraphe, ce qui serait à mon avis dommage, car nous y utilisons ADOX qui reste assez peu usité. L'utilisation des boites de dialogue est également intéressante si vous n'êtes pas encore confirmé dans VB…

Cette procédure demande à l'utilisateur si la base de données existe déjà. Dans ce cas, l'utilisateur la sélectionnera et précisera le nom de la table des nœuds. Si la base n'existe pas, l'utilisateur pourra la créer et créer aussi la table (appel à des sous-procédures).

Code 10 - Procédure pour atteindre la table des nœuds

N°

Code

Commentaires

1

Sub SaveToTable()

2

Dim sResponse As String

Pour la réponse aux messages.

3

Dim strBaseName As String

Pour le nom de la base de données.

4

Dim sTabName As String

Pour le nom de la table.

5

Dim i As Integer

C'est sûrement un compteur, ça.

6

sResponse = MsgBox ("La base de données dans laquelle le treeview doit être sauvegardé existe-t-elle ?", vbYesNo)

Affiche un message pour savoir s'il va falloir créer une base de données (sur une réponse « non »).

7

If sResponse = vbYes Then

La réponse est affirmative. La base existe donc.

8

CommonDialog1.Filter = "Access Database(*.MDB)|*.mdb"

On utilise alors la propriété filter de la boite de dialogue CommonDialog1 pour n'y afficher que les bases Access du répertoire en cours.

9

CommonDialog1.ShowOpen

Affiche la boite de dialogue "Ouvrir…). L'utilisateur pourra sélectionner le répertoire et la base de données à atteindre.

10

strBaseName = CommonDialog1.FileName

On sauvegarde le nom de la base dans la variable.

11

If Len(strBaseName) > 0 Then

Si le nom de la base a été défini…

12

strTabName = SelectTab(strBaseName)

… la fonction SelectTab définit et renvoie le nom de la table…

13

If strTabName <> "" Then

… mais on vérifie toutefois que le nom n'est pas vide.

14

Call WriteToTable(strBaseName, strTabName)

… pour appeler la suite.

15

Else

Le nom est vide. On sort. On aurait pu avertir l'utilisateur avec un message…

16

Exit Sub

17

End If

18

Else

Le nom de la base n'a pas été défini. On affiche un message d'avertissement et on sort de la procédure.

19

MsgBox ("Aucune base n'a été définie !")

20

Exit Sub

21

End If

22

ElseIf strResponse = vbNo Then

Il n'y a pas de base déjà créée (réponse au message de la ligne 6. Oui, je sais, faut suivre ). Il faut donc la créer.

23

CommonDialog1.Filter = "Access Database(*.MDB)|*.mdb"

On utilise la boite de dialogue exactement comme on l'a fait pour la base de données (lignes 8 à 10). La seule différence est l'utilisation de la méthode ShowSave en place de ShowOpen (ligne 9).

24

CommonDialog1.ShowSave

25

strBaseName = CommonDialog1.FileName

26

strTabName = InputBox("Saisissez le nom de la table dans laquelle seront sauvegardés les nœuds." & _
"Par défaut, sans saisie de votre part, le nom utilisé sera 'Nœuds'", , "Nœuds")

On demande à l'utilisateur le nom de la table dans laquelle devront être sauvegardées les informations sur les nœuds.
Le nom par défaut de la table est « Nœuds » et le titre de la boite est « Nœuds ». Quelle originalité !

27

Call CreateDatabase(strBaseName)

La procédure créera la base Access dont le nom est transmis en paramètre.

28

Call CreateTable(strBaseName, strTabName)

La procédure créera la table dont le nom est transmis en paramètre.

29

Call WriteToTable(strBaseName, strTabName)

La procédure enregistrera les informations sur les nœuds du treeview dans la base et la table transmises en paramètres.

30

Else

… au cas où. Mais je ne vois pas très bien ce que pourrait être cet « else ». Toujours est-il que dans ce cas, on s'en va…

31

Exit Sub

32

End If

33

End Sub

Code 10

Sub SaveToTable()
Dim sResponse As String
Dim strBaseName As String
Dim sTabName As String
Dim i As Integer
sResponse = MsgBox ("La base de données dans laquelle le treeview doit être sauvegardé existe-t-elle ?", vbYesNo)
If sResponse = vbYes Then
CommonDialog1.Filter = "Access Database(*.MDB)|*.mdb"
CommonDialog1.ShowOpen
strBaseName = CommonDialog1.FileName
If Len(strBaseName) > 0 Then
strTabName = SelectTab(strBaseName)
If strTabName <> "" Then
Call WriteToTable(strBaseName, strTabName)
Else
Exit Sub
End If
Else
MsgBox ("Aucune base n'a été définie !")
Exit Sub
End If
ElseIf strResponse = vbNo Then
CommonDialog1.Filter = "Access Database(*.MDB)|*.mdb"
CommonDialog1.ShowSave
strBaseName = CommonDialog1.FileName
strTabName = InputBox("Saisissez le nom de la table dans laquelle seront sauvegardés les nœuds." & _
"Par défaut, sans saisie de votre part, le nom utilisé sera 'Nœuds'", , "Nœuds")
Call CreateDatabase(strBaseName)
Call CreateTable(strBaseName, strTabName)
Call WriteToTable(strBaseName, strTabName)
Else
Exit Sub
End If
End Sub

Dans notre approche, nous n'avons pas voulu trop nous perdre dans des détails. Il est évident que dans le cadre d'une application réelle, il faudrait probablement aborder avec plus de rigueur la recherche et la création de la table de données et de la table. Nous avons fait un minimum dans ce sens au cours des procédures suivantes.

II-B-2 - CRÉATION DE LA BASE DE DONNÉES

Cette procédure est appelée depuis la procédure SaveToTable précédente (§II.B.1) si l'utilisateur a spécifié que la base n'existe pas.

On la créée donc…

Notez, comme déjà annoncé, l'utilisation d'un catalogue ADOX.

Code 11 - Création de la base de données

N°

Code

Commentaires

1

Sub CreateDatabase()

Création de la base de données.

2

On Error GoTo CreateDatabaseError

Gestion d'erreur.

3

Dim cat As New ADOX.Catalog

Déclaration d'un objet Catalog.

4

cat.Create "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & strBaseName

Création du catalogue. La méthode Create crée et ouvre un nouvel objet Connection ADO selon la chaîne de connexion spécifiée.

5

Set cat = Nothing

Destruction du catalogue. On pourrait le garder ouvert, mais il est si facile de le rappeler quand nous en aurons besoin que nous préférons le fermer pour ne pas laisser des miettes.

6

Exit Sub

Sortie de la procédure, sinon le code d'erreur s'exécuterait toujours.

7

CreateDatabaseError:

Étiquette du code d'erreur.

8

Set cat = Nothing

Destruction du catalogue.

9

If Err <> 0 Then

S'il y a une erreur différente de 0, affichage de l'erreur.

10

MsgBox Err.Source & "-->" & Err.Description, , "Error"

11

End If

12

End Sub

Code 11

Sub CreateDatabase()
On Error GoTo CreateDatabaseError
Dim cat As New ADOX.Catalog
cat.Create "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & strBaseName
Set cat = Nothing
Exit Sub
CreateDatabaseError:
Set cat = Nothing
If Err <> 0 Then
MsgBox Err.Source & "-->" & Err.Description, , "Error"
End If
End Sub

II-B-3 - CRÉATION DE LA TABLE

La procédure ci-dessous crée la table dans laquelle seront sauvegardées les informations sur les nœuds du treeview. Il convient de se rappeler la « structure » d'un nœud afin de bien faire la relation avec les colonnes de la table. Elle apparaît clairement dans la syntaxe utilisée pour ajouter un nœud au treeview ci-après.

object.Add(relative, relationship, key, text, image, selectedimage)

Cette table est composée de cinq colonnes :

- ID_Noeud : cette colonne contiendra les index des nœuds (leur clé). Cela correspond à la propriété « relative » de la syntaxe. Comme l'index d'un nœud doit être unique, ce sera donc tout naturellement l'index de la table ;

- Nom_Noeud : cette colonne contiendra le « nom » des nœuds qui apparaît dans l'arbre. Cela correspond à la propriété « text » de la syntaxe ;

- ID_NoeudParent : cette colonne contiendra l'ID du nœud parent auquel le nœud est « attaché ». Cela correspond à la propriété « relationship » de la syntaxe ;

- Image : cette colonne contiendra l'index (dans la liste d'images) de l'image du nœud lorsqu'il n'est pas sélectionné ;

- Selected_Image : cette colonne contiendra l'index (dans la liste d'images) de l'image du nœud lorsqu'il est sélectionné.

Code 12 - Création de la table et de ses colonnes

N°

Code

Commentaires

1

Sub CreateTable(strBaseName As String, strTabName As String)

La procédure reçoit en paramètre le nom de la table tel qu'il a été défini par l'utilisateur (ligne 25 code 10).

2

On Error GoTo CreateTableError

Traitement d'erreur.

3

Dim tbl As New Table

4

cat.ActiveConnection = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & strBaseName

Ouverture du catalogue.

5

tbl.Name = strTabName

Affectation de son nom à la table.

6

tbl.Columns.Append "ID_Noeud", adWChar, 10 'index du nœud

Création (ajout) des colonnes de la table, selon la syntaxe ADOX.

7

tbl.Columns.Append "Nom_Noeud", adWChar, 20

8

tbl.Columns.Append "ID_NoeudParent", adWChar, 10

9

tbl.Columns.Append "Image", adInteger

10

tbl.Columns.Append "Selected_Image", adInteger

11

cat.Tables.Append tbl

Création (ajout) de la table.

12

Set cat.ActiveConnection = Nothing

Nettoyage.

13

Set cat = Nothing

14

Set tbl = Nothing

15

Exit Sub

16

CreateTableError:

Étiquette de traitement d'erreur.

17

Set cat = Nothing

18

Set tbl = Nothing

19

If Err <> 0 Then

20

MsgBox Err.Source & "-->" & Err.Description, , "Error"

Description de l'erreur.

21

End If

22

End Sub

Code 12

Sub CreateTable(strBaseName As String, strTabName As String)
On Error GoTo CreateTableError
Dim tbl As New Table
cat.ActiveConnection = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & strBaseName
tbl.Name = strTabName
tbl.Columns.Append "ID_Noeud", adWChar, 10 'index du nœud
tbl.Columns.Append "Nom_Noeud", adWChar, 20
tbl.Columns.Append "ID_NoeudParent", adWChar, 10
tbl.Columns.Append "Image", adInteger
tbl.Columns.Append "Selected_Image", adInteger
cat.Tables.Append tbl
Set cat.ActiveConnection = Nothing
Set cat = Nothing
Set tbl = Nothing
Exit Sub
CreateTableError:
Set cat = Nothing
Set tbl = Nothing
If Err <> 0 Then
MsgBox Err.Source & "-->" & Err.Description, , "Error"
End If
End Sub

II-C - SAUVEGARDE DES NŒUDS DU TREEVIEW DANS LA TABLE

II-C-1 - SÉLECTION DE LA TABLE

Dans un premier temps, il est nécessaire de sélectionner la table dans laquelle les informations concernant les nœuds du treeview vont être sauvegardées. En effet, il est assez probable qu'il y ait plusieurs tables dans la base dans la vraie vie.

Nous allons donc parcourir la collection des tables du catalogue de ladite base (merci ADOX) et afficher leur nom dans une boite de dialogue. Lorsque le nom de la table ad hoc apparaîtra, l'utilisateur le signalera. La fonction renverra alors le nom de la table.

Code 13 - Sélection de la table pour sauvegarder les informations sur les nœuds

N°

Code

Commentaires

1

Function SelectTab(strBaseName As String)

Sélection de la table qui contient les informations sur les nœuds.

2

Dim MyTable As ADOX.Table

3

Dim strResponse As String

4

cat.ActiveConnection = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & strBaseName

Ouverture du catalogue (rappelez-vous que cela ouvre aussi la connexion à la base).

5

For Each MyTable In cat.Tables

Création de la liste des tables de la base de données.

6

If MyTable.Type = "TABLE" Then

On ne veut parcourir que les tables utilisateur, pas les
tables SYSTEM ou GLOBAL TEMPORARY (voir éventuellement « Référence de l'API ADOX 2.5 »).

7

strResponse = MsgBox("Est-ce que la table '" & _
MyTable.Name & "' est celle contenant les informations sur les nœuds ?", vbYesNoCancel)

Affichage d'un nom de table et réponse de l'utilisateur.

8

If strResponse = 6 Then

6 correspond à vbYes. C'est donc la bonne table.

9

SelectTab = MyTable.Name

La procédure renvoie le nom de la table.

10

Exit Function

Terminé…

11

ElseIf strResponse = 2 Then

2 correspond à un abandon. On affiche donc un message confirmant cet abandon, et on… abandonne.

12

MsgBox "Abandon de la procédure !"

13

Exit Function

14

End If

15

End If

16

Next MyTable

On passe à la table suivante. Eh oui, rappelez-vous qu'on est dans une boucle démarrée ligne 5.

17

Toute la collection a été parcourue sans que l'utilisateur ne sélectionne le nom d'une table. On l'en informe et on sort de la fonction.

18

MsgBox "Aucune table n'a été sélectionnée."

19

Exit Function

20

End Function

Code 13

Function SelectTab(strBaseName As String)
Dim MyTable As ADOX.Table
Dim strResponse As String
cat.ActiveConnection = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & strBaseName
For Each MyTable In cat.Tables
If MyTable.Type = "TABLE" Then
strResponse = MsgBox("Est-ce que la table '" & _
MyTable.Name & "' est celle contenant les informations sur les nœuds ?", vbYesNoCancel)
If strResponse = 6 Then
SelectTab = MyTable.Name
Exit Function
ElseIf strResponse = 2 Then
MsgBox "Abandon de la procédure !"
Exit Function
End If
End If
Next MyTable
MsgBox "Aucune table n'a été sélectionnée."
Exit Function
End Function

II-C-2 - ÉCRITURE DANS LA TABLE

II-C-2-a - LE NŒUD ROOT

Nous avons maintenant tous les éléments pour sauvegarder les informations concernant les nœuds du treeview dans une table. Il ne reste donc… qu'à le faire. Y-a-qu'à.

Notez que la procédure WriteToTable ci-dessous n'est appelée que depuis la procédure SaveToTable, et par aucune autre. Donc elle n'est exécutée qu'une seule fois… mais en plusieurs étapes (elle est appelée depuis une boucle).

Code 14 - Sauvegarde des informations des nœuds du treeview dans la table

N°

Code

Commentaires

1

Sub WriteToTable(strBaseName As String, strTabName As String)

La procédure reçoit en argument le nom de la base et le nom de la table.

2

Set rsNoeuds = New ADODB.Recordset

Déclaration d'un l'objet recordset.

3

Dim i As Integer

4

Dim iTmp As Integer

5

Dim iIndex As Integer

6

rsNoeuds.CursorLocation = adUseClient

On définit la propriété CursorLocation du recordset à « adUseClient ». Un curseur côté client autorise plus de fonctionnalités qu'un curseur externe. Essayez de mettre cette ligne en commentaire et lancez le programme, vous comprendrez tout de suite…

7

rsNoeuds.Open cat.Tables(strTabName).Name, cat.ActiveConnection, _
adOpenDynamic, adLockOptimistic, adCmdTable

Création du recordset. On utilise encore ici le catalogue.
Cela permet de s'y habituer un petit peu.

8

If rsNoeuds.RecordCount > 0 Then

Suppression des enregistrements pouvant être dans la table. Si on laissait des enregistrements précédents, le treeview ferait sans aucun doute un peu désordre pour le moins. Et si c'est le même arbre que l'on sauvegarde, on aurait des doublons, et Access n'aimerait pas.

9

rsNoeuds.MoveFirst

Pour être sûr de démarrer la boucle qui suit au premier enregistrement.

10

Do While rsNoeuds.EOF = False

Tant que la fin du recordset n'est pas atteinte…

11

rsNoeuds.Delete

on supprime l'enregistrement en cours…

12

rsNoeuds.MoveNext

… et on passe au suivant.

13

Loop

14

End If

15

mnIndex = TreeView1.Nodes(1 ).Root.Index

Index du premier nœud parent de l'arborescence.

16

iIndex = TreeView1.Nodes(mnIndex).FirstSibling.Index

On détermine maintenant l'index du premier fils (FirstSibling.Index) du premier nœud de la première hiérarchie, et on sauvegarde sa valeur dans la variable iIndex. Le contenu de cette variable évoluera (ligne 39) pour prendre la valeur de l'index des nœuds enfants éventuels du nœud racine en cours.

17

iTmp = iIndex

On affecte la valeur de iIndex à la variable iTmp. Ainsi, le contenu de la variable iTmp reflétera toujours la valeur de l'index du nœud racine. Il servira ligne 28 de référence afin de savoir s'il y a d'autres nœuds enfants pour le nœud racine en cours.

18

rsNoeuds.AddNew

Création du nouvel enregistrement.

19

rsNoeuds("ID_NoeudParent") = "0_"

C'est le nœud racine d'une branche. Il n'a donc pas de nœud parent, d'où son ID = 0_. Rappelez-vous que la collection nodes est une collection de base 1…

20

rsNoeuds("ID_Noeud") = TreeView1.Nodes(iIndex).Key

On écrit dans la base les valeurs respectives des propriétés du nœud.

21

rsNoeuds("Nom_Noeud") = TreeView1.Nodes(iIndex).Text

22

rsNoeuds("Image") = TreeView1.Nodes(iIndex).Image

23

rsNoeuds("Selected_Image") = TreeView1.Nodes(iIndex).SelectedImage

24

rsNoeuds.Update

Enregistrement.

25

If TreeView1.Nodes(iIndex).Children > 0 Then

Si le nœud indexé iIndex (le nœud en cours) possède des enfants (la propriété children donne le nombre de nœuds enfants), on appelle la procédure pour écrire et sauvegarder les valeurs de leur propriété dans la base.

26

WriteChild iIndex

27

End If

28

While iIndex <> TreeView1.Nodes(iTmp).LastSibling.Index

Tant que l'index du premier nœud enfant (déterminé ligne 16) du nœud en cours est différent de l'index du dernier nœud enfant du même nœud en cours.
Tant que l'index iIndex est différent de l'index du dernier nœud enfant du nœud indexé itemp (oui, je sais, c'est duuurrrr, faut s'accrocher), on enregistrera les nœuds dans la boucle.

29

rsNoeuds.AddNew

On crée un nouvel enregistrement pour le nœud enfant…

30

rsNoeuds("ID_NoeudParent") = "0_"

… et on écrit la valeur des propriétés du nœud enfant dans la table…

31

rsNoeuds("Nom_Noeud") = TreeView1.Nodes(iIndex).Next.Text

32

rsNoeuds("ID_Noeud") = TreeView1.Nodes(iIndex).Next.Key

33

rsNoeuds("Image") = TreeView1.Nodes(iIndex).Next.Image

34

rsNoeuds("Selected_Image") = TreeView1.Nodes(iIndex).Next.SelectedImage

35

rsNoeuds.Update …puis on enregistre.

36

If TreeView1.Nodes(iIndex).Next. Children > 0 Then

Si le nœud suivant (next) celui en cours (iIndex) contient lui-même des nœuds enfants (children>0)…

37

WriteChild TreeView1.Nodes(iIndex).Next.Index

… on appelle la procédure WriteChild pour traiter ce prochain nœud enfant…

38

End If

39

iIndex = TreeView1.Nodes(iIndex).Next.Index

On passe au nœud suivant, dont on sauvegarde l'index dans la variable iIndex. N'oublions pas que nous sommes dans une boucle (commençant ligne 28).

40

Wend

41

End Sub

OUF…, mais il faut voir la procédure WriteChild avant que d'être vraiment soulagé.

Code 14

Sub WriteToTable(strBaseName As String, strTabName As String)
Set rsNoeuds = New ADODB.Recordset
Dim i As Integer
Dim iTmp As Integer
Dim iIndex As Integer
rsNoeuds.CursorLocation = adUseClient
rsNoeuds.Open cat.Tables(strTabName).Name, cat.ActiveConnection, _
adOpenDynamic, adLockOptimistic, adCmdTable
If rsNoeuds.RecordCount > 0 Then
rsNoeuds.MoveFirst
Do While rsNoeuds.EOF = False
rsNoeuds.Delete
rsNoeuds.MoveNext
Loop
End If
mnIndex = TreeView1.Nodes(1 ).Root.Index
iIndex = TreeView1.Nodes(mnIndex).FirstSibling.Index
iTmp = iIndex
rsNoeuds.AddNew
rsNoeuds("ID_NoeudParent") = "0_"
rsNoeuds("ID_Noeud") = TreeView1.Nodes(iIndex).Key
rsNoeuds("Nom_Noeud") = TreeView1.Nodes(iIndex).Text
rsNoeuds("Image") = TreeView1.Nodes(iIndex).Image
rsNoeuds("Selected_Image") = TreeView1.Nodes(iIndex).SelectedImage
rsNoeuds.Update
If TreeView1.Nodes(iIndex).Children > 0 Then
WriteChild iIndex
End If
While iIndex <> TreeView1.Nodes(iTmp).LastSibling.Index
rsNoeuds.AddNew
rsNoeuds("ID_NoeudParent") = "0_"
rsNoeuds("Nom_Noeud") = TreeView1.Nodes(iIndex).Next.Text
rsNoeuds("ID_Noeud") = TreeView1.Nodes(iIndex).Next.Key
rsNoeuds("Image") = TreeView1.Nodes(iIndex).Next.Image
rsNoeuds("Selected_Image") = TreeView1.Nodes(iIndex).Next.SelectedImage
rsNoeuds.Update .puis on enregistre.
If TreeView1.Nodes(iIndex).Next. Children > 0 Then
WriteChild TreeView1.Nodes(iIndex).Next.Index
End If
iIndex = TreeView1.Nodes(iIndex).Next.Index
Wend
End Sub

II-C-2-b - LES NŒUDS ENFANTS

La procédure WriteToTable ci-dessus fait appel ligne 26 à la procédure WriteChild ci-dessous pour enregistrer dans la table les nœuds enfants.

La procédure WriteChild est une procédure récursive, c'est-à-dire qui s'appelle elle-même. Cela permet de parcourir dans un premier temps tous les nœuds d'un treeview pour un niveau hiérarchique donné (boucle For de la ligne 5 du code 15 ci-dessous).

Dans cette boucle, si on rencontre alors un nœud i qui possède des enfants, la procédure s'appelle (et donc s'exécute de nouveau et c'est reparti pour un tour) pour parcourir alors tous les nœuds du niveau hiérarchique immédiatement inférieur au ie nœud en cours. Si au cours de ce second passage, un nœud de ce second niveau possède des enfants, c'est reparti pour un troisième tour, and so one. Une fois arrivé au dernier nœud du dernier niveau du nœud racine, on remonte d'un niveau, on passe au nœud suivant et le tout recommence, et encore and so one. Ainsi, toutes les branches de l'arbre sont parcourues.

Heureusement que c'est l'ordinateur qui fait tout ce travail…

Code 15 - Sauvegarde des informations des nœuds enfants dans la table

N°

Code

Commentaires

1

Private Sub WriteChild(ByVal iNodeIndex As Integer)

Écriture des nœuds enfants dans la table. C'est une procédure récursive pour parcourir dans une boucle tous les nœuds enfants. Elle identifie l'index du nœud ayant les enfants.
La procédure reçoit l'index du nœud parent en cours.

2

Dim i As Integer

3

Dim iTempIndex As Integer

4

iTempIndex = TreeView1.Nodes(iNodeIndex).Child.FirstSibling.Index

La variable reçoit l'index du premier nœud fils du nœud parent en cours. Il y a forcément un nœud enfant, car la procédure n'est appelée que dans ce cas (code 14 - Ligne 26).

5

For i = 1 To TreeView1.Nodes(iNodeIndex).Children

On parcourt (dans une boucle) tous les nœuds enfants du nœud parent en cours.

6

rsNoeuds.AddNew

Création d'un enregistrement concernant le nœud enfant dans le recordset.

7

rsNoeuds("ID_NoeudParent") = TreeView1.Nodes(iTempIndex).Parent.Key

Le champ ID_NoeudParent reçoit la valeur de la clé du nœud parent.

8

rsNoeuds("ID_Noeud") = TreeView1.Nodes(iTempIndex).Key

Le champ ID_Noeud reçoit la valeur de la clé du champ enfant en cours.

9

rsNoeuds("Nom_Noeud") = TreeView1.Nodes(iTempIndex).Text

Le champ Nom_Noeud reçoit la valeur de la propriété Text du champ enfant en cours.

10

rsNoeuds("Image") = TreeView1.Nodes(iTempIndex).Image

Le champ Image reçoit la valeur de l'index de l'image contenue dans la liste d'images correspondante à un nœud non sélectionné.

11

rsNoeuds("Selected_Image") = TreeView1.Nodes(iTempIndex).SelectedImage

Le champ Selected_Image reçoit la valeur de l'index de l'image contenue dans la liste d'images correspondante à un nœud sélectionné.

12

rsNoeuds.Update

Mise à jour du recordset.

13

If TreeView1.Nodes(iTempIndex).Children > 0 Then

Si le nœud en cours possède des nœuds enfants, la procédure s'appelle elle-même (récursivité). Elle se transmet l'index du nœud du niveau en cours, celui obtenu ligne 4 de cette procédure. On n'est jamais si bien servi que par soi-même.

14

WriteChild (iTempIndex)

15

End If

16

If i <> TreeView1.Nodes(iNodeIndex).Children Then

Si ce n'est pas le dernier nœud enfant du nœud en cours, on passe au nœud enfant suivant.

17

iTempIndex = TreeView1.Nodes(iTempIndex).Next.Index

18

End If

19

Next i

20

End Sub

Code 15

Private Sub WriteChild(ByVal iNodeIndex As Integer)
Dim i As Integer
Dim iTempIndex As Integer
iTempIndex = TreeView1.Nodes(iNodeIndex).Child.FirstSibling.Index
For i = 1 To TreeView1.Nodes(iNodeIndex).Children
rsNoeuds.AddNew
rsNoeuds("ID_NoeudParent") = TreeView1.Nodes(iTempIndex).Parent.Key
rsNoeuds("ID_Noeud") = TreeView1.Nodes(iTempIndex).Key
rsNoeuds("Nom_Noeud") = TreeView1.Nodes(iTempIndex).Text
rsNoeuds("Image") = TreeView1.Nodes(iTempIndex).Image
rsNoeuds("Selected_Image") = TreeView1.Nodes(iTempIndex).SelectedImage
rsNoeuds.Update
If TreeView1.Nodes(iTempIndex).Children > 0 Then
WriteChild (iTempIndex)
End If
If i <> TreeView1.Nodes(iNodeIndex).Children Then
iTempIndex = TreeView1.Nodes(iTempIndex).Next.Index
End If
Next i
End Sub

II-D - CONSTRUIRE UN TREEVIEW DÉPENDANT DES DONNÉES

C'est bien beau de sauvegarder les propriétés des nœuds, mais il ne faut pas perdre de vue que c'est pour les récupérer afin de reconstruire l'arbre. C'est ce que nous allons faire.

Code 16 - Construction d'un Treeview dépendant des données

N°

Code

Commentaires

1

Private Sub cmdLoad_Click()

Demande à l'utilisateur de sélectionner la base de données et la table dans laquelle les informations sur les nœuds sont sauvegardées et redessine l'arbre.

2

Dim strBaseName As String

Chemin de la base de données.

3

Dim strTabName As String

Nom de la table.

4

Dim ndNodex As Node

Variable objet faisant référence à un nœud.

5

Dim intImage As Integer

Index de l'image d'un nœud dans la liste d'images.

6

Dim intSelectedImage As Integer

Index de l'image dans la liste d'images lorsque le nœud est sélectionné.

7

Dim i As Integer

8

intImage = 0

9

intSelectedImage = 0

10

CommonDialog1.Filter = "Access Database(*.MDB)|*.mdb"

C'est exactement ce que nous avons traité pour la sauvegarde (cf. §II.B et §II.C). Je ne vais pas recommencer les explications quand même.

11

CommonDialog1.ShowOpen

12

strBaseName = CommonDialog1.FileName

13

If Len(strBaseName) > 0 Then

14

strTabName = SelectTab(strBaseName)

15

If strTabName = "" Then

16

MsgBox ("Aucune base n'a été définie !")

17

Exit Sub

18

End If

19

End If

20

TreeView1.Nodes.Clear

Efface tous les nœuds de la table. Autrement les nœuds s'ajouteraient et votre arbre aurait un frère siamois.

21

Set rsNoeuds = New ADODB.Recordset

On crée le recordset avec les propriétés voulues. Mais là encore, cela a déjà été traité précédemment…

22

rsNoeuds.CursorLocation = adUseClient

23

rsNoeuds.Open cat.Tables(strTabName).Name, cat.ActiveConnection, _
adOpenDynamic, adLockOptimistic, adCmdTable

24

If rsNoeuds.RecordCount > 0 Then

il y a des enregistrements dans la table.

25

rsNoeuds.MoveFirst

Pour être sûr de commencer au premier enregistrement.

26

Do While rsNoeuds.EOF = False

Tant que la fin du recordset n'est pas atteinte…

27

intImage = rsNoeuds.Fields("Image")

28

intSelectedImage = rsNoeuds.Fields("Selected_Image")

29

If Trim(rsNoeuds.Fields("ID_NoeudParent")) = "0_" Then

Tous les nœuds racines ont la valeur '0_' dans le champ ID_NoeudParent, ce par construction (cf. code 14, ligne 19).

30

Set ndNodex = TreeView1.Nodes.Add(, 1, _
Trim(rsNoeuds.Fields("ID_Noeud")), Trim(rsNoeuds.Fields("Nom_Noeud")), _
intImage, intSelectedImage)

On affecte à la variable objet le nœud en cours (dans la boucle Do While démarrée ligne 25 qui parcourt le recordset). La variable objet, en l'occurrence un nœud, contient toutes les caractéristiques dudit nœud.
La fonction Trim est là pour s'assurer que les paramètres ne commencent ni ne finissent par un espace.

31

Else

Là, on traite tous les nœuds qui ne sont pas des nœuds racines.

32

Set ndNodex = TreeView1.Nodes.Add(Trim(rsNoeuds.Fields("ID_NoeudParent")), tvwChild, _
Trim(rsNoeuds.Fields("ID_Noeud")), Trim(rsNoeuds.Fields("Nom_Noeud")), _
intImage, intSelectedImage)

C'est pratiquement presque le même code que ligne 29 précédente. Méfiance, j'ai bien dit « presque ». Alors quelle est la différence et pourquoi ? Je vous laisse chercher (et trouver. Si vous ne trouvez pas, reportez-vous à l'explication ci-après).

33

ndNodex.EnsureVisible

Développe le treeview pour que tous les nœuds soient visibles.

34

End If

35

rsNoeuds.MoveNext

On passe à l'enregistrement suivant de la boucle démarrée ligne 25.

36

Loop

37

End If

38

rsNoeuds.Close

Fermeture du recordset.

39

End Sub

Code 16

Private Sub cmdLoad_Click()
Dim strBaseName As String
Dim strTabName As String
Dim ndNodex As Node
Dim intImage As Integer
Dim intSelectedImage As Integer
Dim i As Integer
intImage = 0
intSelectedImage = 0
CommonDialog1.Filter = "Access Database(*.MDB)|*.mdb"
CommonDialog1.ShowOpen
strBaseName = CommonDialog1.FileName
If Len(strBaseName) > 0 Then
strTabName = SelectTab(strBaseName)
If strTabName = "" Then
MsgBox ("Aucune base n'a été définie !")
Exit Sub
End If
End If
TreeView1.Nodes.Clear
Set rsNoeuds = New ADODB.Recordset
rsNoeuds.CursorLocation = adUseClient
rsNoeuds.Open cat.Tables(strTabName).Name, cat.ActiveConnection, _
adOpenDynamic, adLockOptimistic, adCmdTable
If rsNoeuds.RecordCount > 0 Then
rsNoeuds.MoveFirst
Do While rsNoeuds.EOF = False
intImage = rsNoeuds.Fields("Image")
intSelectedImage = rsNoeuds.Fields("Selected_Image")
If Trim(rsNoeuds.Fields("ID_NoeudParent")) = "0_" Then
Set ndNodex = TreeView1.Nodes.Add(, 1, _
Trim(rsNoeuds.Fields("ID_Noeud")), Trim(rsNoeuds.Fields("Nom_Noeud")), _
intImage, intSelectedImage)
Else
Set ndNodex = TreeView1.Nodes.Add(Trim(rsNoeuds.Fields("ID_NoeudParent")), tvwChild, _
Trim(rsNoeuds.Fields("ID_Noeud")), Trim(rsNoeuds.Fields("Nom_Noeud")), _
intImage, intSelectedImage)
ndNodex.EnsureVisible
End If
rsNoeuds.MoveNext
Loop
End If
rsNoeuds.Close
End Sub

Vous avez trouvé ? Cela ne m'étonne pas ! … Donc je confirme.
Dans le premier cas (lignes 28 et 29), nous avons un nœud root, donc pas de parent pour ce nœud.

III - SECTION 3 - DÉPLACER DES NŒUDS

Notre TreeView est maintenant opérationnel. Mieux, nous comprenons comment il fonctionne et pouvons le reproduire. On a fait un bon chemin, mais il serait intéressant de pouvoir définir une autre position pour un nœud donné. En clair, un drag and drop serait le bienvenu. Qu'à cela ne tienne, nous allons le réaliser.

III-A - DÉMARRER UN DRAG AND DROP (MOUSEMOVE)

La procédure est déclenchée par le déplacement de la souris. Nous voulons que le déplacement soit notifié par le clic de la touche gauche de la souris et le maintien de la touche CTRL enfoncé. Le seul contrôle de la souris peut en effet entraîner des actions non souhaitées, beaucoup d'utilisateurs l'utilisant inconsciemment.

Code 17 - Démarrer un drag and drop

N°

Code

Commentaires

1

Private Sub TreeView1_MouseMove(Button As Integer, Shift As Integer, x As Single, y As Single)

La procédure reçoit en paramètre l'état du bouton (touche droite, gauche, milieu), la touche du clavier éventuellement enfoncée (MAJ, CTRL ou ALT) et la position du pointeur (abscisse et ordonnée).

2

If Button = vbLeftButton And Shift = vbCtrlMask Then

On contrôle la touche souris appuyée (ici la gauche) et la touche clavier enfoncée (ici CTRL).

3

boFlagEC = True

Si les deux conditions sont OK, on affecte la valeur True au drapeau. Ce drapeau nous servira par la suite.

4

TreeView1.DragIcon = TreeView1.SelectedItem.CreateDragImage

La méthode CreateDragImage définit une image spécifique pour l'icône du nœud, indiquant qu'elle est en cours de déplacement.

5

TreeView1.Drag vbBeginDrag

L'opération de déplacement du nœud sélectionné démarre.

6

End If

7

End Sub

Code 17

Private Sub TreeView1_MouseMove(Button As Integer, Shift As Integer, x As Single, y As Single)
If Button = vbLeftButton And Shift = vbCtrlMask Then
boFlagEC = True
TreeView1.DragIcon = TreeView1.SelectedItem.CreateDragImage
TreeView1.Drag vbBeginDrag
End If
End Sub

III-B - SÉLECTION DU NŒUD À DÉPLACER (MOUSEDOWN)

L'évènement MouseDown se produit lorsque l'utilisateur appuie sur un bouton de la souris. La vraie justification de la procédure évènementielle ci-dessous n'est pas, comme on peut le penser au premier abord, de mettre en surbrillance le nœud à déplacer sélectionné, mais d'en sauvegarder la référence dans la variable objet objDagNode (ligne 4) afin de s'en servir ultérieurement.

Rappelez-vous que cette variable objet a été déclarée dans la section de déclaration de la feuille et est donc accessible depuis n'importe quelle procédure de ladite feuille.

Code 18 - Sélection du nœud à déplacer

N°

Code

Commentaires

1

Private Sub TreeView1_MouseDown(Button As Integer, Shift As Integer, x As Single, y As Single)

L'évènement MouseDown se produit lorsque l'utilisateur appuie sur un bouton de la souris.
La procédure évènementielle reçoit en paramètre le bouton actionné, la touche clavier éventuellement enfoncée (eh, allez-y mollo, c'est fragile ces bestiaux) et les coordonnées du pointeur (abscisse et ordonnée).

2

Set TreeView1.DropHighlight = TreeView1.HitTest(x, y)

La propriété DropHightlight définit (ou renvoie) une référence au nœud mis en surbrillance. La propriété HitTest quant à elle renvoie (ou définit) une référence au nœud situé aux coordonnées x et y. Le résultat est donc la mise en surbrillance du nœud sur lequel l'utilisateur a cliqué.

3

If Not TreeView1.DropHighlight Is Nothing Then

Cependant, l'utilisateur peut cliquer dans le Treeview ailleurs que sur un nœud. On vérifie donc si un nœud a bien été sélectionné (mis en surbrillance).

4

TreeView1.SelectedItem = TreeView1.HitTest(x, y)

Le nœud sélectionné est maintenant celui sur lequel l'utilisateur a cliqué pour le déplacer.

5

Set objDragNode = TreeView1.SelectedItem

On affecte à la variable objet la référence au nœud sélectionné.

6

If boFlagEC = True Then

Si le nœud n'a pas de parent, l'instruction suivante générerait une erreur. Donc, dans ce cas, on la saute et la cellule du tableau ne contiendra aucune valeur.

7

tblDrag(1, 1) = TreeView1.SelectedItem.Parent

Enregistrement dans le tableau du nœud parent du nœud qui va être déplacé. On utilisera cette information pour afficher des informations sur le nœud déplacé (code 19 ligne 9 et suite).

8

End If

9

End If

10

Set TreeView1.DropHighlight = Nothing

Plus aucun nœud n'est mis en surbrillance. En fait, aucun nœud n'est maintenant sélectionné.

11

End Sub

Code 18

Private Sub TreeView1_MouseDown(Button As Integer, Shift As Integer, x As Single, y As Single)
Set TreeView1.DropHighlight = TreeView1.HitTest(x, y)
If Not TreeView1.DropHighlight Is Nothing Then
TreeView1.SelectedItem = TreeView1.HitTest(x, y)
Set objDragNode = TreeView1.SelectedItem
If boFlagEC = True Then
tblDrag(1, 1) = TreeView1.SelectedItem.Parent
End If
End If
Set TreeView1.DropHighlight = Nothing
End Sub

III-C - DÉPLACEMENT DU NŒUD SÉLECTIONNÉ (DRAGDROP)

L'utilisateur fait glisser le nœud qu'il a sélectionné en maintenant toujours la touche CTRL enfoncée.

Code 19 - Déplacement du nœud sélectionné

N°

Code

Commentaires

1

Private Sub TreeView1_DragDrop(Source As Control, x As Single, y As Single)

2

On Error GoTo DragErreur

Gestion d'erreur.

3

Dim msg As String

4

If TreeView1.DropHighlight Is Nothing Then

Si aucun nœud n'a été sélectionné ou si un nœud a été déplacé à une position invalide, on met le drapeau à faux et on s'en va…

5

boFlagEC = False

6

Exit Sub

7

Else

8

Set objDragNode.Parent = TreeView1.DropHighlight

La variable objet contient la référence au nœud déplacé (code 17 ligne 4). Là, on définit la propriété Parent de ce nœud comme étant le nœud sélectionné.

9

tblDrag(0, 0) = "Nœud déplacé"

On indique les libellés dans la première colonne du tableau.
Ces libellés seront affichés dans le message qui suit ligne 14.

10

tblDrag(0, 1) = "Ancien nœud parent"

11

tblDrag(0, 2) = "Nouveau nœud parent"

12

tblDrag(1, 0) = objDragNode

La case du tableau reçoit la référence au nœud en cours de déplacement…

13

tblDrag(1, 2) = objDragNode.Parent

… et celle de son nouveau nœud parent. Rappelons que la référence à son ancien nœud parent a été sauvegardée dans le tableau (code 17 ligne 5).

14

MsgBox tblDrag(0, 0) & " - " & tblDrag(1, 0) & vbCrLf & _
tblDrag(0, 1) & " - " & tblDrag(1, 1 ) & vbCrLf & _
tblDrag(0, 2) & " - " & tblDrag(1, 2)

Affichage d'un message indiquant le nœud déplacé, son ancien nœud parent et son nouveau. C'est plutôt un exercice de style…

15

Set TreeView1.DropHighlight = Nothing

Plus de nœud mis en surbrillance…

16

boFlagEC = False

… le drapeau passe à faux…

17

Set objDragNode = Nothing

… plus rien dans la variable objet…

18

Exit Sub

… pas de doute, c'est la fin de la procédure.

19

End If

20

DragErreur:

Étiquette erreur.

21

Const CircularError = 35614

Le code d'erreur sauvegardé dans la constante signifie une erreur circulaire.

22

If Err.Number = CircularError Then

23

msg = "Un nœud ne peut être lui-même son propre fils."

L'utilisateur a replacé le nœud sur lui-même. Quel étourdi.

24

If MsgBox(msg, vbExclamation & vbOKCancel) = vbOK Then

On affiche le message avec un point d'exclamation et deux boutons (OK et Cancel).

25

boFlagEC = False

On efface tout et on s'en va. L'utilisateur devra recommencer l'opération de drag and drop.

26

Set TreeView1.DropHighlight = Nothing

27

Exit Sub

28

End If

29

End If

30

End Sub

Code 19

Private Sub TreeView1_DragDrop(Source As Control, x As Single, y As Single)
On Error GoTo DragErreur
Dim msg As String
If TreeView1.DropHighlight Is Nothing Then
boFlagEC = False
Exit Sub
Else
Set objDragNode.Parent = TreeView1.DropHighlight
tblDrag(0, 0) = "Nœud déplacé"
tblDrag(0, 1) = "Ancien nœud parent"
tblDrag(0, 2) = "Nouveau nœud parent"
tblDrag(1, 0) = objDragNode
tblDrag(1, 2) = objDragNode.Parent
MsgBox tblDrag(0, 0) & " - " & tblDrag(1, 0) & vbCrLf & _
tblDrag(0, 1) & " - " & tblDrag(1, 1 ) & vbCrLf & _
tblDrag(0, 2) & " - " & tblDrag(1, 2)
Set TreeView1.DropHighlight = Nothing
boFlagEC = False
Set objDragNode = Nothing
Exit Sub
End If
DragErreur:
Const CircularError = 35614
If Err.Number = CircularError Then
msg = "Un nœud ne peut être lui même son propre fils."
If MsgBox(msg, vbExclamation & vbOKCancel) = vbOK Then
boFlagEC = False
Set TreeView1.DropHighlight = Nothing
Exit Sub
End If
End If
End Sub

III-D - FIN DU DÉPLACEMENT DU NŒUD SÉLECTIONNÉ (DRAGOVER)

C'est la véritable fin du Drag and Drop. L'utilisateur cesse d'appuyer sur la souris. Celle-ci pleure d'être abandonnée (pardon, je me suis trompé de mélo).

Code 20 - Déplacement du nœud sélectionné

N°

Code

Commentaires

1

Private Sub TreeView1_DragOver(Source As Control, x As Single, y As Single, State As Integer)

Procédure évènementielle DragOver.

2

If boFlagEC = True Then

On vérifie si on est bien dans une opération de drag and drop.

3

Set TreeView1.DropHighlight = TreeView1.HitTest(x, y)

Le nœud en surbrillance correspond à la position de la souris.

4

End If

5

End Sub

Code 20

Private Sub TreeView1_DragOver(Source As Control, x As Single, y As Single, State As Integer)
If boFlagEC = True Then
Set TreeView1.DropHighlight = TreeView1.HitTest(x, y)
End If
End Sub

Voilà, c'est fini. Il ne vous reste plus qu'à utiliser le composant TreeView dans vos applications, enfin si vous en avez besoin…

Pour ma part, je l'ai utilisé entre autres dans le cadre d'une gestion de clientèle doublée d'une facturation, et cela a été très apprécié des utilisateurs.

Alors, à votre clavier, et bons développements.

(1)https://jacma.developpez.com/

(2)Ils seront progressivement intégrés, quand j'en aurai le temps, ou, soyons honnête, le courage.

(3)Je ne peux qu'espérer que cela sera aussi compréhensible pour vous.

(4)À ce sujet, je vous rappelle que ce code est réalisé dans le cadre d'un praticiel, et qu'à ce titre, il ne peut être réellement opérationnel. Toute utilisation de tout ou partie de ce code ne peut être faite que sous votre propre responsabilité (c'est un avertissement de rigueur).

(5)https://jacma.developpez.com/

(6)https://vb.developpez.com/

(7)La mise en œuvre en sera détaillée par la suite.

(8)En fait, c'est MSDN qui expose… Je me contente d'apporter éventuellement quelques précisions qui peuvent aider à comprendre rapidement ce que j'ai mis longtemps à comprendre moi-même.

(9)Le composant ImageList est également, comme le composant TreeView,t dans Microsoft Common Controls.

(10)Il pourrait y en avoir plus, associées par exemple aux différents niveaux de l'arbre. Mais c'est une autre histoire…

(11)Ce qui est généralement le cas. Nous ne le préciserons donc plus dans cette remarque.

(12)Non que les autres ne le soient pas, elles aussi, mais celle-ci l'est tout particulièrement.

(13)Les icônes en question sont dans le zip du projet VB, dossier Images.