A little code repository by John Knipper

Virtual Tree View

Snow earth.bmp
A little code repository by John Knipper

This is just written by me John Knipper. Don't bother Mike if something is wrong here. I am not related to Mikes company in any way. I'm just doing that because I believe so much in his component, that I would not give you the possibility to miss the opportunity to use it. You won't regret it. I'm not going to enumerate all the nice advantage it has on it's competitors. Because it has so many. The biggest I see is the speed improvement, the multi columns, the automatic allocation of node data and so many more. 

 

You will see that the strong points of the Virtual tree view are not obvious. But you can believe me, this is the best Treeview ever. You will be kinda lost at the beginning, but it's only a matter of forgetting what you know about trees. This is the right way to do it. You will ask yourself why it has not be done like that at the beginning. 

 

 

Q: How to initially fill the tree? 

A: The only information VT needs at startup is the number of root nodes. All other information is queried from the application when they are needed (text, node height etc.). Hence all to do is to set property RootNodeCount to the number of nodes required. 

E:

VirtualStringTree1.RootNodeCount := 5; // is adding 5 nodes at the root of your tree

 

To initialize the nodes, use the OnInitNode event 

 

 

Q: How to add a node to the tree? 

A: The technique is very similar to the one you used with the standard tree view. The only difference is that you fill the node's data after the insertion of the node 

E:

var
  Node: PVirtualNode;
  Node := VirtualStringTree1.AddChild(nil); // Adds a node to the root of the Tree.
  Node := VirtualStringTree1.AddChild(ParentNode); // Adds a node as the last child of the given node.
  Node := VirtualStringTree1.InsertNode(Node, amInsertBefore); // Inserts a node as sibling just before the given node.

 

Alternatively you can use the OnInitChildren event. This event is used when a node is marked as having child nodes and these child nodes are somehow about to be accessed (like iteration, expanding, display etc.). 

 

 

Q: Where is gone all the information about my node, like text for example ? 

A: The text property is gone. You don't need it anymore. The basic idea behind Virtual Treeview is to leave all data management to the application which knows much better how to do this than the tree (see also Related Topics). Every node knows which is its parent and which are their children. Information like the text property, the new hint property, the ImageIndex property and everything else should be stored in the node's data. The tree will ask for it on demand, e.g. when it needs to show a certain node etc. 

E:

TTreeData = record
  Text: WideString;
  URL: String[255];
  CRC: LongInt;
  isOpened: Boolean;
  ImageIndex: Integer;
end;
PTreeData = ^TTreeData; // This is a node example.

 

 

Q: When should I allocate memory for the node data? 

A: Never, the VT does it for you. The only thing you have to do is to tell the VT how much memory you need for your node data. 

E:

VirtualStringTree1.NodeDataSize := SizeOf(TTreeData); // The nodes are reinitialized when you change that value.

 

If you know how much memory it will take, you can use the NodeDataSize property of the VT and initialize it directly at design time. 

 

 

Q: When should I fill my nodes data? 

A: The ideal place for this is the OnInitNode event. 

E:

procedure TMainForm.VTInitNode(Sender: TBaseVirtualTree; ParentNode, Node: PVirtualNode; var InitialStates: TVirtualNodeInitStates);

var
  Level: Integer;
  Data,
  ParentData: PMyNodeData;
  Count: Integer;

begin
  with Sender do
  begin
    Data := GetNodeData(Node);
    ParentData := GetNodeData(ParentNode);
    if Assigned(ParentData) then Level := ParentData.Level + 1
                            else Level := 0;

    case FFillMode of
      0: // fill tree with a specific amount of nodes and levels
        begin
          // determine new node level
          if Level < (LevelsUpDown.Position - 1) then Include(InitialStates, ivsHasChildren);
        end;
      1: // fill tree with one million root nodes (nothing special to do here)
        ;
      2: // fill tree with a certain amount of root nodes (they always get fixed text to test sorting)
        begin
          Data.FixedText := True;
          Data.NewText := Format('Node: %d', [Node.Index]);
        end;
      3: // fill tree with a certain amount of root nodes and a random amount of child nodes
         // up to an absolute amount of ~1 million nodes and with at most 10 levels
        begin
          if Assigned(ParentNode) then Count := ParentNode.ChildCount
                                  else Count := TVirtualStringTree(Sender).RootNodeCount;
          if (Level < 15) and
             (Random(Count) < (Count div 2)) and
             (FCurrentCount < 1000000) then Include(InitialStates, ivsHasChildren);
        end;
    end;

    Data.Level := Level;
    Node.CheckType := ctTriStateCheckBox;
    case Level of
      1:
        if Random(5) < 2 then Include(InitialStates, ivsDisabled);
    end;
  end;
end;

 

 

Q: How do I access a node's data? 

A: Use GetNodeData(Node) to get a pointer on your nodes data 

E: Either use

with PTreeData(VirtualStringTree1.GetNodeData(Node))^ do
begin
  Text:= ChangeFileExt(ExtractFileName(FileName), '');
  ImageIndex:= 1; //it's an example ;)
end;

Or in that case you can use

var
  NodeData: PTreeData;

begin
  NodeData := VirtualStringTree1.GetNodeData(Node);
  NodeData.Text := 'a test';
  NodeData.ImageIndex := 1;
  ...

 

 

Q: What else can I do with that nodes data pointer? 

A: Usually you already have all data in your own structure (database, file etc.) so you need only to supply an identifier or a pointer into your own structure. This prevents your application from doubling the data just for display which in turn saves a remarkable amount of memory. 

E: You could connect a TBookmark to the data. To display the name of your customer in a VT :

procedure TFRM_WWW_main.vFavTreeGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: Integer; TextType: TVSTTextType; var Text: WideString);

begin
  // Column is -1 if the header is hidden or no columns are defined
  if Column < 0 then Exit;
  if TVirtualStringTree(Sender).Header.Columns[Column].Text = 'Customer Name' then
  begin
    Table.GotoBookmark(TBookmark(Sender.GetNodeData(Node)));
    Text := Table.FieldByName('Name').asString;
  end;
end;

 

Q: A move of a scrollbar's thumb doesn't directly scroll the tree. What to do? 

A:

VirtualStringTree1.VertScrollBar.Track := True;

 

 

Q: How can I display text for other columns? 

A: In the OnGetText event, check the column index. 

E:

procedure TFRM_WWW_main.vFavTreeGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: Integer; TextType: TVSTTextType; var Text: WideString);

begin
  case Column of
    -1, // main column, -1 if columns are hidden, 0 if they are shown
    0:
      Text := 'Text of column 1';
    1:
      Text := 'Text of column 2';
    2:
      Text := 'Text of column 3';
  end;
end;

 

Q: When do I tell which icon to use? 

A: It's the same principle as for the OnGetText event. With the exception that you must tell which icon to use in 3 cases: the normal icon, the selected icon and the state icon. 

E:

procedure TFRM_WWW_main.vFavTreeGetImageIndex(Sender: TBaseVirtualTree; Node: PVirtualNode; Kind: TVTImageKind; Column: Integer; var Index: Integer);

begin
  if Kind = ikState then
  begin
    Index := 2;
  end
  else
    if (Kind = ikNormal) or (Kind = ikSelected) then
    begin
      Index := 1;
    end;
end;

or just use

procedure TFRM_WWW_main.vFavTreeGetImageIndex(Sender: TBaseVirtualTree; Node: PVirtualNode; Kind: TVTImageKind; Column: Integer; var Index: Integer);

begin
  case Kind of
    ikState:
      Index := 2;
    ikNormal,
    ikSelected:
      Index := 1;
  end;
end;

 

 

Ok, here we are. This is only a small introduction to help you begin with Virtual Treeview. There are many more useful functions. Nearly everything was done for you. Thank you very much for your work Mike.

What do you think about this topic? Send feedback!