delphitvirtualstringtree

Parent of RootNode of TVirtualStringTree


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.


Solution

  • 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