According to documentation, property RootNode of TVirtualStringTree is a hidden node, parent of all user created nodes. But the Parent of the RootNode is set. I am facing the following problem:
Sometimes, when closing a form that have a TVirtualStringTree in it, I get an access violation error. Debugging, the error occurs when is tryng to access the Name property of the componente (virtualstringtree). If I try to evaluate Name property, I get "Inaccessible value". Digging in the problem, I found out that the Name property of a component cannot be changed in runtime:
Warning: Changing Name at runtime causes any references to the old name to become undefined. Any subsequent code that uses the old name will cause an exception.
It happend that I am not changing this in my code. Using a data breakpoint in the Name property, I saw that it was changing in a virtualstringtree loop, something like this (example code that I am using in a sample app to test the problem, but production code is similar):
var
N: PNode;
P: PVirtualNode;
begin
P := tree.FocusedNode;
while Assigned(P) do
begin
N := tree.GetNodeData(P);
P := P.Parent;
end;
The GetNodeData is the following:
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;
Giving that I have the following tree:
Node 1
Node 2
Node 3
When the Node 3 is selected, the loop is executed 5 times. 3 in my created nodes, then the RootNode (hidden), and then the Parent of the root. Only the Parent of the parent root node is nil. In the GetNodeData method is verified if it is the root node, returns nil. But for this parent of the root, since it is not the root, enters in else code. Now here is the problem:
Address of the Name property of the virtualstrintree is $141B9768
Address of the States property of the parent of the root node is $141B976A
When putting the data breakpoint in Name property, it gets changed at this point:
Include(Node.States, vsOnFreeNodeCallRequired);
and generating the AV.
I know I can change the loop, to check the root node correctly, but I want to understand this, so I maybe fix the component (if it is a component bug), instead of my 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;
Just put a tree and a button in the form, I put the loop in click of the button.
First, the documentation: (e.g.) https://documentation.help/VirtualTreeview/TBaseVirtualTree_RootNode.html
says (emphasis mine)
property RootNode: PVirtualNode;
Description
For anchoring the tree hierarchy an internal tree node is maintained which is mostly just like any other tree node but has sometimes differently handled. The root node is always expanded and initialized. Its parent member points to the treeview to which the node belongs to and its PreviousSibling and NextSibling members point to the root node itself to make it possible to actually recognize this node.
Notes
You should not use the root node to iterate through the tree. It is only publicly accessible because it is the parent of all top level nodes and can be used to test a node whether it is a top level node or not.
Secondly
In your code
var
N: PNode;
P: PVirtualNode;
begin
P := tree.FocusedNode;
while Assigned(P) do
begin
N := tree.GetNodeData(P);
P := P.Parent;
end;
you are unconditionally traversing upwards past the internal root node because you are not checking for P
being the internal root, by comparing with tree.RootNode
(or comparing with P.NextSibling
).
Perhaps changing the
while Assigned(P) do
condition to
while P <> tree.RootNode do
would suit you