3
votes

[This is an updated version of a question posted earlier, the previous title was Selecting node by index in Delphi’s Virtual Treeview.]

After the better part of a day, I believe I've got the Virtual Treeview component (powerful but complex) working in a simple two table data aware fashion.

Now, I'm trying to simply select the 1,512th (for instance) of the top-level nodes. I can't see any way to do this other than getting the first top-level node and then calling GetNextSibling 1,511 in a loop.

This seems needlessly involved. Is there a simpler way?

UPDATE

Because initializing the nodes in my tree requires database access, initializing all the nodes at startup is not feasible. When the user starts with form with no record already selected, that's fine. As the user scrolls around the tree, enough nodes are populated to display the current window into the tree and the performance is fine.

When the user starts the form in dialog mode with a database record already selected, I must advance the tree to that node before the user sees the form. This is a problem because, if the record is towards the end of the tree, it can take ten seconds as I walk the tree from the first node. Each time I can GetNextSibling(), a node is initialized, even though the vast majority of those nodes are not displayed to the user. I would prefer to defer the initialization of those nodes to the point at which they become visible to the user.

I know that there must be a better way, because if I open the tree without a record selected and use the vertical scroll bar to move, in a single operation, to the middle of the tree then the correct nodes are displayed without having to initialize the nodes I skipped over.

This is the effect I'd like to achieve when opening the tree with a record selected. I know the index of the node I want to go to, but if I can't get there by index I could do a binary search on the tree assuming I can jump some number of nodes backwards and forwards (similar to scrolling directly to the middle of the tree).

Alternatively, perhaps there is some State setting I can make to the tree view that will leave the intermediate nodes uninitialized as I traverse the grid. I've tried Begin/End Update and that doesn't seem to do the trick.

3

3 Answers

3
votes

To get a node's sibling without initializing it, just use the NextSibling pointer (see declaration of TVirtualNode).

3
votes

The tree control is structured just like classical trees you would learn about in a computer-science class. The only way to get from the root of the tree to the 1512th child is to walk the links one by one. Whether you do it yourself or you use a method of the tree control, it still has to be done that way. I don't see anything provided in the control itself, so you could use this function:

function GetNthNextSibling(Node: PVirtualNode; N: Cardinal;
  Tree: TBaseVirtualTree = nil): PVirtualNode;
begin
  if not Assigned(Tree) then
    Tree := TreeFromNode(Node);
  Result := Node;
  while Assigned(Result) and (N > 0) do begin
    Dec(N);
    Result := Tree.GetNextSibling(Result);
  end;
end;

If you find yourself doing that often, you might want to make yourself an index. It could be as simple as making an array of PVirtualNode pointers and storing all the top-level values in it, so you can just read the 1512th value out of it. The tree control has no need for such a data structure itself, so it doesn't maintain one.

You might also reconsider whether you need a data structure like that. Do you really need to access the nodes by index like that? Or could instead maintain a PVirtualNode pointer, so its position relative to the rest of the nodes in the tree no longer matters (meaning you can, for example, sort them without losing reference to the node you wanted)?

0
votes

You write in your update:

I know that there must be a better way, because if I open the tree without a record selected and use the vertical scroll bar to move, in a single operation, to the middle of the tree then the correct nodes are displayed without having to initialize the nodes I skipped over.

There is a difference here, because vertical scrolling changes the logical Y coordinate that is displayed at client position 0. The control calculates the offset from scrollbar position and scroll range, and then calculates which node is visible at the top of the control. Nodes are only initialized again when the area that has been scrolled into view needs to be painted.

If you have the Y coordinate of a node you can get the node pointer by calling

function TBaseVirtualTree.GetNodeAt(X, Y: Integer; Relative: Boolean;
  var NodeTop: Integer): PVirtualNode;

The Y coordinate of a node is the sum of heights of all previous visible nodes. Assuming you don't have collapsed nodes (so either it is a flat list of records, or all nodes with child nodes are expanded) and they all have the default height this is easy. This code should be a good starting point:

procedure TForm1.SelectTreeNode(AIndex: integer; ACenterNodeInTree: boolean);
var
  Y, Dummy: integer;
  Node: PVirtualNode;
begin
  Y := Round((AIndex + 0.5) * VirtualStringTree1.DefaultNodeHeight);
  Node := VirtualStringTree1.GetNodeAt(0, Y, False, Dummy);
  if Node <> nil then begin
    Assert(Node.Index = AIndex);
    VirtualStringTree1.ScrollIntoView(Node, ACenterNodeInTree);
    VirtualStringTree1.Selected[Node] := True;
    VirtualStringTree1.FocusedNode := Node;
  end;
end;