I am converting a Delphi app from using a TTreeView to using a TVirtualStringTree; The node data is held in TItemData records in a TList.
type
TItemData = record
idName: string;
end;
PItemData = ^TItemData
TMyForm = class(TForm)
...
private
itemData: TList<TItemData>;
..
end;
I wanted to get the displayed tree up and running in the most straightforward way possible, and then gradually convert the app little by little as I got to understand how to use the VirtualStringTree. So, I have a buildTreeFromItemData() method, which iterates through the TList elements and creates child nodes in the VirtualStringTree. I [tried to] pass a pointer to each TItemData record in each call to VST.addChild() which would then be dereferenced in vstGetText(), something like this:
procedure buildTreeFromItemData;
var
i: integer;
idA: TItemData;
begin
for i := 0 to itemData.count - 1 do begin
idA := itemData[i];
vst.addChild(NIL, @idA);
end;
end;
Dereference the pointer:
procedure TMyForm.vstGetData(...);
var
idB: TItemData;
begin
idB := node.getData^;
CellText := idB.idName;
end;
It quickly became apparent that no matter how many different ways I tried to code this, e.g. @itemData[i], the only times my code compiled every vst node was actually getting the address of the idA local variable, and every tree node was pointing to the most recent TItemData record pointed to by idA. I was then getting access violations in vstGetText() once buildTreeFromItemData() had completed and the local idA variable went out of scope, i.e. every node's data pointer in vst was now invalid.
Most of my attempts to somehow deference idA and get at the address location of the TItemData stored in idA generated an "invalid typecast" from the Delphi syntax checker, let alone the compiler.
At one point I tried something like this:
ptr1^ := @idA;
I have no idea what that actually means to the Delphi compiler. I know what I wanted it to mean: I wanted it to mean "set ptr1 to the [dereferened] address stored at the address of the idA local variable". To my surprise, it compiled but went bang as soon as the debugger hit that statement. (What does the compiler think "ptr1^ := " means?)
Eventually I hit upon the idea of typecasting idA to a TObject; at least then, my thinking went, the compiler would know we were at least in the realms of dereferencing and might actually let me, eventually, get to the pointer I really needed to pass to vst.addChild().
After much experimentation, and many more "invalid typecast"s, unbelievably [at least to me] the following code works!.....
procedure buildTreeFromItemData;
var
i: integer;
idA: TItemData;
myObj: TObject;
ptr1: pointer;
ptr2: PItemData;
begin
for i := 0 to itemData.count - 1 do begin
idA := itemData[i];
myObj := TObject(@idA);
ptr1 := pointer(myObj)
ptr2 := PItemData(ptr1^);
vst.addChild(NIL, ptr2);
end;
end;
ptr2 is now so far removed, syntactically and semantically, from idA, that the compiler finally allows the dereference in PItemData(ptr1^), although it only allowed it after I added the PItemData(...) typecast.
I don't even have to dereference this pointer in vstGetText!...
procedure TMyForm.vstGetText(...);
var
idB: PItemData;
begin
idB := PItemData(node.getData);
CellText := idB.idName;
end;
The tree displays perfectly and the access violations are gone. (NB. The actual code in buildTreeFromItemData() is a lot more involved and creates child nodes of child nodes to create a complex tree structure several levels deep.)
Although I eventually found a solution at gone 1am this morning after a lot of trial and error, I find it difficult to believe that my jiggerypokery with the local variable is really necessary for something so simple. So my question is this: what is the correct Delphi syntax for getting the address of my TItemData record stored in a plain "idA: TItemData;" local variable?
(I think this is my first ever question to stackoverflow; I hope I have formulated it well enough. I've kept the code to the absolute bare bones necessary to illustrate the issue and I wasn't able to completely reproduce the exact experimentation code I went through. The solution in the final two code blocks, though, is my working code. If I can improve how I've formulated the question and the explanation to meet stackoverflow's stringent standards, please let me know.)
The idea of TVirtualTree is to store your data inside the nodes. TVirtualTree will reserve memory for your data and that's why you need to tell the tree how big is your data.
So, when your form is created (inside OnCreate handler) you should set the
NodeDataSizeproperty of your VirtualTree:You should also handle the OnFreeNode event from your VirtualTree:
When you want to create a new node in the tree:
You will also need to handle other events from TVirtualTree like OnGetText, onPaintText, onCompareNodes, onIncrementalSearch, onDblClick, onGetHint, etc.