There is no need to call GetMem() or New() in the first concept in which
type
PTreeData = ^TTreeData;
TTreeData = record
Column0: String;
Column1: String;
Column2: String;
end;
This is because this record is maintained by the virtual tree itself since it is stored at the end of the basic node record (TVirtualNode). This is why VTV asks you for the size of the node data - it allocates the size needed by the TVirtualNode record plus the size that you specify as NodeDataSize in one single block. Therefore, it also automatically disposes the node data when it frees each node. However, it does not take care of what is stored inside the node data. Here you store three strings, they allocate memory on the heap, too, and you are responsible to dispose it. Therefore, you have to write a handler for the OnFreeNode event in which you erase the strings - just as you correctly do.
When you call VTV.GetNodeData you get the pointer to the TVirtualNode offset by the size of the basic TVirtualNode - it thus points to the beginning of your data record.
In the other concept with objects VTV adds the pointer to the object at the end of the TVirtualNode. Therefore you specify the size of a pointer as the NodeDataSize. Again, VTV does not take care of what the pointer points to, and you must create the object for it. I normally do this in the AddChild instruction because it's so easy, but I think the "official" idea to use the OnInitNode event because it is called only for those nodes needed for display etc.