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).
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 DCONSTRUIRE UN TREEVIEW DÉPENDANT DES DONNÉES) 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 laquelleobject
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.2MÉTHODES DE BASE POUR CRÉER UN NŒUD et I.C.3FONCTIONNALITÉS COMPLÉMENTAIRES 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).
Vous obtenez à peu près ceci.
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.
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.
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é. |
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éé.
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 |
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 explicationAJOUTER UN NŒUD DE MÊME NIVEAU HIÉRARCHIQUE concernant la position hiérarchique d'un nœud).
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 |
|
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 |
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 |
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).
Éléments |
Description |
Application |
---|---|---|
objet |
Expression d'objet qui correspond à un objet figurant dans la rubrique « Application ». |
TreeView1 |
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 |
relationship |
Facultatif. Spécifie l'emplacement relatif de l'objet Node, comme indiqué dans la section Valeurs. |
Ici, tvwLast |
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 |
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é.
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 |
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 & _ |
Affiche un message informant l'utilisateur de la marche à suivre. |
13 |
Exit Sub |
Fin de la gestion d'erreur. |
14 |
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.
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). |
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 |
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.
À 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.
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é. |
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.cAJOUTER UN NŒUD DE MÊME NIVEAU HIÉRARCHIQUE). |
8 |
Exit Sub |
|
9 |
myerr: |
Traitement des erreurs. |
10 |
MsgBox ("You must select a Node to do an Add First" & vbCrLf _ |
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 |
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 ?
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é. |
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.cAJOUTER UN NŒUD DE MÊME NIVEAU HIÉRARCHIQUE). |
8 |
Exit Sub |
|
9 |
myerr: |
Étiquette gestion d'erreur. |
10 |
MsgBox ("You must select a Node to do an Add Next" & vbCrLf _ |
Affiche un message demandant à l'utilisateur de sélectionner un nœud. |
11 |
Exit Sub |
|
12 |
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.
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é. |
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.cAJOUTER UN NŒUD DE MÊME NIVEAU HIÉRARCHIQUE). |
8 |
Exit Sub |
|
9 |
myerr: |
Gestion d'erreur. |
10 |
MsgBox ("Vous devez sélectionner un nœud pour pouvoir en ajouter un avant." & vbCrLf _ |
Affiche un message demandant à l'utilisateur de sélectionner un nœud. |
11 |
Exit Sub |
|
12 |
End Sub |
Dans les paragraphes précédents ($I.C.3.aAJOUTER UN NŒUD EN TÊTE D'UN NIVEAU HIÉRARCHIQUE à $I.C.3.cAJOUTER UN NŒUD AVANT LE NŒUD SÉLECTIONNÉ), 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…
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é. |
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 |
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 |
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).
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 |
|
strBaseName = CommonDialog1.FileName |
||
26 |
strTabName = InputBox("Saisissez le nom de la table dans laquelle seront sauvegardés les nœuds." & _ |
On demande à l'utilisateur le nom de la table dans laquelle devront être sauvegardées les informations sur les nœuds. |
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 |
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.1SÉLECTION OU CRÉATION ?) 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.
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 |
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é.
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 |
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.
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 |
7 |
strResponse = MsgBox("Est-ce que la table '" & _ |
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 |
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).
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, _ |
Création du recordset. On utilise encore ici le catalogue. |
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 |
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. |
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. |
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 ( |
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é. |
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…
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. |
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). |
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é |
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 |
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.
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.BLA BASE DE DONNÉES ET LA TABLE DES NŒUDS et §II.CSAUVEGARDE DES NŒUDS DU TREEVIEW DANS LA TABLE). 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, _ |
|
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, _ |
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. |
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, _ |
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 |
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.
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. |
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. |
|
TreeView1.Drag vbBeginDrag |
L'opération de déplacement du nœud sélectionné démarre. |
|
6 |
End If |
|
7 |
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.
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. |
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 |
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.
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é. |
tblDrag(0, 0) = "Nœud déplacé" |
On indique les libellés dans la première colonne du tableau. |
|
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 & _ |
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 |
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).
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 |
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.