views:

998

answers:

3

[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  A: 

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)?

Rob Kennedy
I do need to access the tree by index. This is a dialog to select a record from a pair of related datasets. If the dialog opens in a case where a value is already selected I want the dialog to display the currently selected node. I find that record by doing a Locate on the dataset, the record number gives me the index of the node I'm after. I could build my own index as you suggest but I kind of expected to find it built into the component (I'm suffering from old-thinking, coming directly from Delphis TTreeView.Nodes paradigm).
Larry Lustig
A: 

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;
mghie
Thank you. I don't think it will be quite this easy since I can't be sure of the expanded state of the intermediate nodes, but I'm sure I can use this technique as the basis of an incremental or binary search through the tree. The missing element was the idea of scrolling forward by pixels instead of by nodes.
Larry Lustig
+1  A: 

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

TOndrej
Works perfectly, with zero apparent time to iterate over 9,000 or so nodes.
Larry Lustig