Index de l'article

Quelques conseils pour bien utiliser le composant Virtual TreeView

Les records doivent être initialisés

Note: Ceci n'est plus nécessaire depuis la révision c62ba8c6ece69078f472054bac770158388c0c19, 01/07/2009 21:24:17

Pour le virtual treeview, les données qu'il manipulent ne sont jamais qu'un tableau d'octets, sans signification particulière pour lui. Les exemples fournis avec lui montrent l'utilisation de records comme une des pistes de développement et c'est donc ce qui a est généralement utilisé. Cependant, une particularité des records est fréquement oubliée : l'initialisation.
Avec une classe, l'initialisation est faite automatiquement lors de sa construction avec l'appel à Create. Pour un record, il n'y a pas de constructeur, mais les champs présents dans le record doivent être initialisés, en particulier les chaînes et les tableaux. Dans un cadre classique, cette initialisation est faite automatiquement par le compilateur dès le début de la procédure où une variable de type record a été déclarée.
Dans le cadre du virtual treeview, seuls des pointeurs vers record sont manipulés, jamais des variables de type record puisque c'est le virtual treeview qui alloue les octets nécessaires au stockage du record demandé. La mémoire est donc bien allouée mais aucune initialisation n'est faite si ce n'est la mise à zéro des octets (selon les options de compilation).
Ainsi quand le pointeur est déréférencé pour accéder aux membres du record, on accède bien à une zone mémoire valide mais les routines de gestion implicite de la mémoire pour les strings et les tableaux risquent de ne pas fonctionner.
C'est pourquoi il est indispensable de fournir un gestionnaire pour OnInitNode dans lequel on fera un appel spécifique pour initialiser le record :

NodeData := Sender.GetNodeData(Node);
Initialize(NodeData^);

Avec NodeData qui est une variable de type pointeur sur le record. L'utilisation de l'indirection (le chapeau ^) est obligatoire pour passer le record à Initialize et pas un pointeur sur le record. Ainsi, on est sûr que ce dernier est initialisé correctement. On pourra placer ce code dans le gestionnaire pour OnInitNode ou, plus judicieusement, dans OnStructureChange comme on le voit à la section suivante.


OnFreeNode doit être implémenté pour libérer la mémoire

Quand on utilise un virtual treeview on devrait avoir un gestionnaire d'événement pour OnFreeNode chargé de libérer les ressources associées au record de chacun des noeuds. Le code le plus simple est le suivant :

procedure TMyForm.vstMyTreeFreeNode(Sender: TBaseVirtualTree; Node: PVirtualNode);
var
  Data : PMyRecord;
begin
  Data := Sender.GetNodeData(Node);
  Finalize(Data^);
end;

C'est la meilleure façon de procéder puisqu'on laisse faire la méthode prévue par Delphi pour libérer les ressources utilisées par un record.


Le treeview n'appelle pas OnFreeNode par défaut

Note: Cette section ne s'applique plus depuis la révision f22306586809d97181e77989d03c8680f2471ab0, 14/05/2013 21:48:58

Comme vu à la section précédente, OnFreeNode doit être implémenté. Cependant, il est certain qu'aucun de ces gestionnaires n'est appelé par défaut. En effet, le virtual treeview n'appelle OnFreeNode que s'il considère que c'est nécessaire. Et cette décision n'est prise que s'il a été informé que le noeud a besoin d'une libération via le flag vsInitialUserData dans l'état du noeud (Node.States)

Cette valeur est ajoutée par le virtual treeview que lorsqu'un appel à AddChild ou InsertNode est effectué avec une valeur non nulle pour le paramètre UserData.
Dans un cas classique, ça n'arrive jamais et c'est normal, le treeview étant virtuel, il n'est pas logique de donner les données dès la création des noeuds. Il faut donc "ruser" en ajoutant à la main la valeur vsInitialUserData dans les états du noeud nouvellement créé. On le fait de cette façon:

Include(Node.States, vsInitialUserData);

Ainsi, on est sûr que OnFreeNode sera toujours appelé.

Il faut par contre ajouter cette ligne de code de façon à ce qu'elle soit appelée automatiquement par le virtual treeview à chaque fois qu'il crée un nouveau noeud. Le plus sûr est d'utiliser l'événement OnStructureChange du VirtualTreeView qui nous indique un changement de structure (ajout, déplacement, suppression...) et dans les cas d'ajout de node ou d'enfant de node, on fera les appels nécessaires, à savoir initialisation du record et indication de l'existence de cette initialisation au treeview. Voici le code à placer dans un gestionnaire d'événement pour OnStructureChange :

case Reason of
  crChildAdded:
  begin
    Node := Sender.GetLastChildNoInit(Node);
    if Assigned(Node) then
    begin
      Include(Node.States, vsInitialUserData);
      Initialize(PMyRecord(Sender.GetNodeData(Node))^);
    end;
  end;
  crNodeAdded:
  begin
    Include(Node.States, vsInitialUserData);
    Initialize(PMyRecord(Sender.GetNodeData(Node))^);
  end;
end;

Vous aurez sans doute remarqué l'appel à GetLastChildNoInit dans le cas de crChildAdded. Cet appel est nécessaire car dans ce cas là, Node contient le noeud parent, pas le noeud enfant qui vient d'être ajouté et on ne recevra jamais de notification avec crNodeAdded pour l'enfant.

Sans ces ajustements, il est quasiment assuré que l'utilisation des virtual treeview entraîne des fuites mémoires liées aux chaînes qui ne sont pas correctement libérées.