Recent

Author Topic: [Resolved] VirtualTreeView and a dynamic Array: what's wrong with this code?  (Read 496 times)

Nimral

  • Full Member
  • ***
  • Posts: 157
  • Keep it simple.
Hi,

I have have some troubles with the VirtualTreeView component, and need advise from more experienced users.

Using the following definitions.

Code: Pascal  [Select][+][-]
  1. type
  2.   PPayload = ^TPayload;
  3.   TPayload = record
  4.     ID:Integer;
  5.     Name: string[50];
  6.   end;
  7.   TArrPayload = array of TPayload;

the attached test project is supposed to do the following:

- Create a dynamic array of records and populate it with some test data
- Store a pointer to each rec in a VirtualStringTree

Code: Pascal  [Select][+][-]
  1. procedure TForm1.FormCreate(Sender: TObject);
  2.  
  3. const
  4.   NumItems = 10;
  5.  
  6. var
  7.   i: integer;
  8.   rec: TPayload;
  9.  
  10. begin
  11.   VirtualStringTree1.NodeDataSize := SizeOf(PPayload);
  12.   // populate the array
  13.   ArrPayload := TArrPayload.Create;
  14.   for i := 1 to NumItems do
  15.   begin
  16.     rec.ID := i;
  17.     rec.Name := Format('Node %d', [i]);
  18.     Insert(rec,ArrPayload,MaxInt);  // append tail
  19.   end;
  20.   // initalize the nodes
  21.   for rec in ArrPayload do
  22.      VirtualStringTree1.AddChild(nil,@rec);
  23. end;

- In VirtualStringTree.GetText: retrieve the record, and display the test data

Code: Pascal  [Select][+][-]
  1. procedure TForm1.VirtualStringTree1GetText(Sender: TBaseVirtualTree;
  2.   Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType;
  3.   var CellText: String);
  4.  
  5. var
  6.   p:PPayload;
  7.  
  8. begin
  9.   assert(assigned(Node),'VirtualStringTree1GetText: Node not assigned');
  10.   p := Sender.GetNodeData(Node);
  11.   CellText := p^.Name;
  12. end;

Problems with the enclosed demo code:

The number of nodes displayed is OK, but the node captions are all garbage.
The project crashes on exit, obviously memory corruption. Seems to be caused by FreeAndNil of the dynamic array.

This seems to work, but why? Coincidence?

Code: Pascal  [Select][+][-]
  1. procedure TForm1.FormDestroy(Sender: TObject);
  2.  
  3. begin
  4.   // FreeAndNil(ArrPayload);
  5.   ArrPayload := Nil;
  6. end;
  7.  

I can't find any obvious error in hours, who can help me out?

Thnx, Armin

P.S. I think I solved the memory corruption problem: though the syntax of dynamic arrays does somewhat mimic object syntax, dynamic arrays are not descendants of TObject, and so using FreeAndNil with a dynamic arry is not valid. Correct? But why, then, doesn't FreeAndNil complain that it hasn't been passed an object reference?

In fact, FreeAndNil seems not to care about anything, because this code compiles, and does also crash on exit:

Code: Pascal  [Select][+][-]
  1. procedure TForm1.FormDestroy(Sender: TObject);
  2.  
  3. var
  4.   x:integer;
  5.  
  6. begin
  7.   ArrPayload := nil;
  8.   FreeAndNil(x);
  9. end;  
« Last Edit: May 06, 2021, 08:43:22 pm by Nimral »
Lazarus 2.0.10 on Windows 10, Raspberry Pi OS, VMWare Workstation

Nimral

  • Full Member
  • ***
  • Posts: 157
  • Keep it simple.
Finally I found it.

GetNodeData returns a pointer to the data, not the data itself. To get at the actual data, which in this case happens to be another pointer, I need to dereference what comes back from GetNodeData and cast the result to a pointer to the actual data.

This code sequence works:

Code: Pascal  [Select][+][-]
  1. procedure TForm1.VirtualStringTree1GetText(Sender: TBaseVirtualTree;
  2.   Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType;
  3.   var CellText: string);
  4.  
  5. var
  6.   p: PPayload;
  7.  
  8. begin
  9.   assert(assigned(Node), 'VirtualStringTree1GetText: Node not assigned');
  10.   p := PPayload(Sender.GetNodeData(Node)^);
  11.   CellText := p^.Name;
  12. end;

Furthermore, I analysed the pointers passed, and, to my great astonishment, I found that there is a difference between "for variable in array" and "for i := low(array) to high(array)" ... array[ i ]:

Code: Pascal  [Select][+][-]
  1.   // initalize the nodes
  2.   // for rec in ArrPayload do
  3.   //   VirtualStringTree1.AddChild(nil, @rec);
  4.  
  5.   for i := low(ArrPayload) to high(ArrPayload) do
  6.   begin
  7.     p := @ArrPayload[i];
  8.     VirtualStringTree1.AddChild(nil, p);
  9.   end;

The upper code (commented out) will always pass the same address (like if rec was a separate variable and not an address following the Array records). Huh?

I'll open a separate question about this strange behaviour of for ...

Thanks to all who already looked into my post and tried to help,

Armin.
« Last Edit: May 06, 2021, 09:04:03 pm by Nimral »
Lazarus 2.0.10 on Windows 10, Raspberry Pi OS, VMWare Workstation

 

TinyPortal © 2005-2018