Parent de RootNode de TVirtualStringTree
Selon la documentation, la propriété RootNode de TVirtualStringTree est un nœud caché, parent de tous les nœuds créés par l'utilisateur. Mais le Parent du RootNode est défini. Je suis confronté au problème suivant:
Parfois, lors de la fermeture d'un formulaire contenant un TVirtualStringTree, j'obtiens une erreur de violation d'accès. Débogage, l'erreur se produit lorsque l'on essaie d'accéder à la propriété Name du composant (virtualstringtree). Si j'essaie d'évaluer la propriété Name, j'obtiens "Valeur inaccessible". En creusant le problème, j'ai découvert que la propriété Name d'un composant ne peut pas être modifiée au moment de l'exécution :
Avertissement: la modification du nom lors de l'exécution entraîne la non-définition des références à l'ancien nom. Tout code ultérieur utilisant l'ancien nom provoquera une exception.
Il est arrivé que je ne change pas cela dans mon code. En utilisant un point d'arrêt de données dans la propriété Name, j'ai vu qu'il changeait dans une boucle virtualstringtree, quelque chose comme ceci (exemple de code que j'utilise dans un exemple d'application pour tester le problème, mais le code de production est similaire):
var
N: PNode;
P: PVirtualNode;
begin
P := tree.FocusedNode;
while Assigned(P) do
begin
N := tree.GetNodeData(P);
P := P.Parent;
end;
Le GetNodeData est le suivant:
if (FNodeDataSize <= 0) or (Node = nil) or (Node = FRoot) then
Result := nil
else begin
Result := PByte(@Node.Data) + FTotalInternalDataSize;
Include(Node.States, vsOnFreeNodeCallRequired); // We now need to call OnFreeNode, see bug #323
end;
Donnant cela, j'ai l'arbre suivant:
Node 1
Node 2
Node 3
Lorsque le nœud 3 est sélectionné, la boucle est exécutée 5 fois. 3 dans mes nœuds créés, puis le RootNode (caché), puis le parent de la racine. Seul le parent du nœud racine parent est nul. Dans la méthode GetNodeData est vérifié s'il s'agit du nœud racine, renvoie nil. Mais pour ce parent de la racine, puisque ce n'est pas la racine, entre dans le code else. Voici maintenant le problème:
L'adresse de la propriété Name du virtualstrintree est 141B9768 $
L'adresse de la propriété States du parent du nœud racine est 141B976A $
Lorsque vous placez le point d'arrêt des données dans la propriété Name, il est modifié à ce stade:
Include(Node.States, vsOnFreeNodeCallRequired);
et générer l'AV.
Je sais que je peux changer la boucle, pour vérifier correctement le nœud racine , mais je veux comprendre cela, donc je corrige peut-être le composant (s'il s'agit d'un bogue de composant), au lieu de mon code.
Code:
type
TNode = record
Text: string;
end;
PNode = ^TNode;
...
procedure TForm1.FormCreate(Sender: TObject);
var
N1, N2, N3: TNode;
TreeNode: PVirtualNode;
begin
N1.Text := 'Node 1';
TreeNode := tree.AddChild(nil, PNode(N1));
N2.Text := 'Node 2';
TreeNode := tree.AddChild(TreeNode, PNode(N2));
N3.Text := 'Node 3';
TreeNode := tree.AddChild(TreeNode, PNode(N3));
end;
Il suffit de mettre un arbre et un bouton dans le formulaire, je mets la boucle en cliquant sur le bouton.
Réponses
Tout d'abord, la documentation: (par exemple) https://documentation.help/VirtualTreeview/TBaseVirtualTree_RootNode.html
dit (c'est moi qui souligne)
propriété RootNode: PVirtualNode;
La description
Pour ancrer la hiérarchie de l'arborescence, un nœud d'arbre interne est maintenu, ce qui est essentiellement comme n'importe quel autre nœud d'arbre mais qui a parfois été géré différemment. Le nœud racine est toujours développé et initialisé. Son membre parent pointe vers l'arborescence à laquelle appartient le nœud et ses membres PreviousSibling et NextSibling pointent vers le nœud racine lui-même pour permettre de reconnaître réellement ce nœud .
Remarques
Vous ne devez pas utiliser le nœud racine pour parcourir l'arborescence. Il n'est accessible que publiquement car il est le parent de tous les nœuds de niveau supérieur et peut être utilisé pour tester un nœud, qu'il s'agisse d'un nœud de niveau supérieur ou non.
en deuxième
Dans votre code
var
N: PNode;
P: PVirtualNode;
begin
P := tree.FocusedNode;
while Assigned(P) do
begin
N := tree.GetNodeData(P);
P := P.Parent;
end;
vous traversez inconditionnellement vers le haut le nœud racine interne parce que vous ne vérifiez pas s'il Ps'agit de la racine interne, en comparant avec tree.RootNode(ou en comparant avec P.NextSibling).
Peut-être changer le
while Assigned(P) do
condition à
while P <> tree.RootNode do
vous conviendrait
Donc, grâce à la réponse de Tom et à la documentation du composant, le parent de RootNode est en fait l'arbre lui-même. Ainsi, lors de la réalisation de la boucle avec Parent = nilcomme point d'arrêt, en atteignant la racine du parent (arbre), le code Includein GetNodeDataest exécuté, et, dans mon cas, lors de l'accès à la Statepropriété, son adresse était très proche de Nom adresse de l'arbre , parce que l'adresse du "nœud" était l'arbre.
Donc, je fixe la boucle pour vérifier Node <> Tree.RootNodeet corriger aussi GetNodeDatapour vérifier aussi si le nœud est auto: or (Node = Pointer(Self), juste au cas où un autre code dans mon application (héritage de 2 millions de code +) utilise la même boucle mal :
Avant que:
function TBaseVirtualTree.GetNodeData(Node: PVirtualNode): Pointer;
begin
Assert(FNodeDataSize > 0, 'NodeDataSize not initialized.');
if (FNodeDataSize <= 0) or (Node = nil) or (Node = FRoot) then
Result := nil
else begin
Result := PByte(@Node.Data) + FTotalInternalDataSize;
Include(Node.States, vsOnFreeNodeCallRequired); // We now need to call OnFreeNode, see bug #323
end;
end;
Après:
function TBaseVirtualTree.GetNodeData(Node: PVirtualNode): Pointer;
begin
Assert(FNodeDataSize > 0, 'NodeDataSize not initialized.');
if (FNodeDataSize <= 0) or (Node = nil) or (Node = FRoot) or (Node = Pointer(Self)) then
Result := nil
else begin
Result := PByte(@Node.Data) + FTotalInternalDataSize;
Include(Node.States, vsOnFreeNodeCallRequired); // We now need to call OnFreeNode, see bug #323
end;
end;