Recent

Author Topic: VirtualStringTree - class instead of record  (Read 3506 times)

egsuh

  • Hero Member
  • *****
  • Posts: 1738
VirtualStringTree - class instead of record
« on: April 16, 2022, 12:21:52 pm »
Basically I'm referring to following page.

https://wiki.freepascal.org/VirtualTreeview_Example_for_Lazarus

Following is the basic example. Reading this, I think TVirtualStringTree seems to get memory for new memory within AddChild function, with New or GetMem (Please tell me if I'm wrong).

Code: Pascal  [Select][+][-]
  1. type
  2.   PTreeData = ^TTreeData;
  3.   TTreeData = record
  4.     Column0: String;
  5.     Column1: String;
  6.     Column2: String;
  7.   end;
  8.  
  9. procedure TForm1.VSTGetNodeDataSize(Sender: TBaseVirtualTree; var NodeDataSize: Integer);
  10. begin
  11.   NodeDataSize := SizeOf(TTreeData);
  12. end;
  13.  
  14.  
  15. procedure TForm1.VSTFreeNode(Sender: TBaseVirtualTree; Node: PVirtualNode);
  16. var
  17.   Data: PTreeData;
  18. begin
  19.   Data := VST.GetNodeData(Node);
  20.   if Assigned(Data) then begin
  21.     Data^.Column0 := '';
  22.     Data^.Column1 := '';
  23.     Data^.Column2 := '';
  24.   end;
  25. end;
  26.  
  27.  
  28. procedure TForm1.VSTGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
  29.  Column: TColumnIndex; TextType: TVSTTextType; var CellText: WideString);
  30. var
  31.   Data: PTreeData;
  32. begin
  33.   Data := VST.GetNodeData(Node);
  34.   case Column of
  35.     0: CellText := Data^.Column0;
  36.     1: CellText := Data^.Column1;
  37.     2: CellText := Data^.Column2;
  38.   end;
  39. end;
  40.  

I'm wondering whether I can change "record" class. Related changes would be as following.

Code: Pascal  [Select][+][-]
  1. type
  2.   TTreeData = class
  3.     Column0: String;
  4.     Column1: String;
  5.     Column2: String;
  6.   end;
  7.  
  8. procedure TForm1.VSTGetNodeDataSize(Sender: TBaseVirtualTree; var NodeDataSize: Integer);
  9. begin
  10.   NodeDataSize := SizeOf(pointer);
  11. end;
  12.  
  13.  
  14. procedure TForm1.VSTFreeNode(Sender: TBaseVirtualTree; Node: PVirtualNode);
  15. var
  16.   Data: TTreeData;
  17. begin
  18.   Data := VST.GetNodeData(Node);
  19.   if Assigned(Data) then Data.Free;
  20. end;
  21.  
  22.  
  23. procedure TForm1.VSTGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
  24.  Column: TColumnIndex; TextType: TVSTTextType; var CellText: WideString);
  25. var
  26.   Data: TTreeData;
  27. begin
  28.   Data := VST.GetNodeData(Node);
  29.   case Column of
  30.     0: CellText := Data.Column0;
  31.     1: CellText := Data.Column1;
  32.     2: CellText := Data.Column2;
  33.   end;
  34. end;
  35.  

This may not be perfect and may need another step (i.e. GetNodeData return pointer to address field of target class). But anyway I think I need to replace New with TTreeData.Create within AddChild method of TVirtualStringTree.

Is this feasible, do you think?

jamie

  • Hero Member
  • *****
  • Posts: 7515
Re: VirtualStringTree - class instead of record
« Reply #1 on: April 16, 2022, 02:54:27 pm »
Couple of things I see.

 It looks like you have the correct concept on how to use it, however, in your first sample where it free's the node data, you should be freeing the DATA there since you created initially.

 On the second example you are freeing it so it looks ok from here.

 
 Also, the node already has the pointer to the node in question so why are you casting it via the class instance ?


 Node^.Data....... is all you need here.

 But of course, first test to ensure its not NIL on both the parameter Node and then Node^data.

 The Data field is an untyped and sizeless field which is why a size of the field is required. You can in theory simply indicate the size of the field and then just add to it. Maybe that is how your first example is working? That being the case then the size of data needs to be checked first if you are simply appending the data to the Node field.

 Did you understand that ?


 
The only true wisdom is knowing you know nothing

wp

  • Hero Member
  • *****
  • Posts: 13349
Re: VirtualStringTree - class instead of record
« Reply #2 on: April 16, 2022, 04:49:46 pm »
There is no need to call GetMem() or New() in the first concept in which
Code: Pascal  [Select][+][-]
  1. type
  2.   PTreeData = ^TTreeData;
  3.   TTreeData = record
  4.     Column0: String;
  5.     Column1: String;
  6.     Column2: String;
  7.   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.
« Last Edit: April 16, 2022, 04:51:41 pm by wp »

egsuh

  • Hero Member
  • *****
  • Posts: 1738
Re: VirtualStringTree - class instead of record
« Reply #3 on: April 19, 2022, 08:16:54 am »
Quote
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).

Ah I see.  For now, I may define a record type which has only one object field, and methods within it. I'll try. Thank you for the explanation. 

egsuh

  • Hero Member
  • *****
  • Posts: 1738
Re: VirtualStringTree - class instead of record
« Reply #4 on: April 26, 2022, 04:30:37 am »
With some trial and errors, I could make following codes run (run means adding nodes and freeing form without errors).  VirtualStringTree's GetNodeData seems to return the starting address of record TTreeData, if I had defined it as record. So it is pointing the starting address of an address field in my case. So typecasting pointer(Sender.GetNodeData(Node)^) returns the address of object I have created.   

I'm not sure how much helpful this would be, but I hope that this will save my cognitive efforts.

Code: Pascal  [Select][+][-]
  1. type
  2.   TTreeData = class
  3.     Column0: String;
  4.     Column1: String;
  5.     Column2: String;
  6.   end;
  7.  
  8.  
  9. procedure TForm1.vstChange(Sender: TBaseVirtualTree; Node: PVirtualNode);
  10. begin
  11.     Sender.Refresh;
  12. end;
  13.  
  14. procedure TForm1.vstFocusChanged(Sender: TBaseVirtualTree; Node: PVirtualNode;
  15.   Column: TColumnIndex);
  16. begin
  17.     Sender.Refresh;
  18. end;
  19.  
  20. procedure TForm1.vstFreeNode(Sender: TBaseVirtualTree; Node: PVirtualNode);
  21. var
  22.    Data: TTreeData;
  23. begin
  24.    Data := pointer(Sender.GetNodeData(Node)^);
  25.    Data.Free;
  26. end;
  27.  
  28. procedure TForm1.vstGetNodeDataSize(Sender: TBaseVirtualTree;
  29.   var NodeDataSize: Integer);
  30. begin
  31.    NodeDataSize:= SizeOf(pointer);
  32. end;
  33.  
  34. procedure TForm1.vstGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
  35.   Column: TColumnIndex; TextType: TVSTTextType; var CellText: String);
  36. var
  37.   Data: TTreeData;
  38. begin
  39.   Data := Pointer(Sender.GetNodeData(Node)^);
  40.   case Column of
  41.     0: CellText := Data.Column0;
  42.     1: CellText := Data.Column1;
  43.     2: CellText := Data.Column2;
  44.   end;
  45. end;
  46.  
  47. procedure TForm1.Button3Click(Sender: TObject);
  48. var
  49.   Data: TTreeData;
  50.   XNode: PVirtualNode;
  51.   Rand: Integer;
  52. begin
  53.   Randomize;
  54.   Rand := Random(99);
  55.  
  56.   XNode := VST.AddChild(nil);
  57.  
  58.   if VST.AbsoluteIndex(XNode) > -1 then
  59.   begin
  60.      Data := TTreeData.Create;
  61.      Data.Column0 := IntToStr(Rand);
  62.      Data.Column1 := 'Two ' + IntToStr(Rand + 10);
  63.      Data.Column2 := 'Three ' + IntToStr(Rand - 10);
  64.  
  65.      pointer(VST.GetNodeData(Xnode)^) := Data;
  66.   end;
  67. end;


I tried to do replace GetNodeData(Node) with property NodeData within helper type, but this failed.

Code: Pascal  [Select][+][-]
  1. interface
  2.   HLazVirtualStringTree = class helper for TBaseVirtualTree
  3.   private
  4.      function getNodeData(Node: PVirtualNode): pointer;
  5.      procedure setNodeData(Node:PVirtualNode; AValue:pointer);
  6.   public
  7.      property NodeData[const Data:PVirtualNode]: pointer read getNodeData write setNodeData;
  8.  
  9.   end;
  10.  
  11.  
  12. implementation
  13.  
  14. function HLazVirtualStringTree.getNodeData(Node: PVirtualNode): pointer;
  15. begin
  16.   Result := Pointer(Self.GetNodeData(Node)^);
  17. end;
  18.  
  19. procedure HLazVirtualStringTree.setNodeData(Node: PVirtualNode; AValue: pointer
  20.   );
  21. begin
  22.   Pointer(Self.GetNodeData(Node)^) := AValue;
  23. end;
  24.  


Interestingly, the exception occurs at the "begin" line of HLazVirtualStringTree.getNodeData function. Any idea?

Wallaby

  • Full Member
  • ***
  • Posts: 130
Re: VirtualStringTree - class instead of record
« Reply #5 on: April 30, 2022, 12:40:23 pm »
I am using classes without any problems. Looks like this:

1) Upon initialisation set Tree.NodeDataSize := SizeOf(TObject);
2) To get node's object: whatever := TObject(GetNodeData(Node)^)
3) To set node's object: TObject(GetNodeData(Node)^) := whatever

You can also associated the object when adding a node:
whatever := TObject.Create;
AddChild(nil, whatever);

This will need to be freed in OnFreeNode.

egsuh

  • Hero Member
  • *****
  • Posts: 1738
Re: VirtualStringTree - class instead of record
« Reply #6 on: May 02, 2022, 08:10:26 am »
Quote
1) Upon initialisation set Tree.NodeDataSize := SizeOf(TObject);
2) To get node's object: whatever := TObject(GetNodeData(Node)^)
3) To set node's object: TObject(GetNodeData(Node)^) := whatever

You can also associated the object when adding a node:
whatever := TObject.Create;
AddChild(nil, whatever);

One problem with your approach is in the second one, whatever :=TObject(GetNodeData(Node)^).
Here, if whatever is defined as a class, this will cause compiler error. It's child:=Parent relationship.

But, I could change some part of my code  as following based on your advice. Thank you very much.

Code: Pascal  [Select][+][-]
  1.   Data := TTreeData.Create;
  2.   XNode := VST.AddChild(nil, Data);
  3.  
  4.   if VST.AbsoluteIndex(XNode) > -1 then
  5.   begin
  6.         Data.Column0 := IntToStr(Rand);
  7.         Data.Column1 := 'Two ' + IntToStr(Rand + 10);
  8.         Data.Column2 := 'Three ' + IntToStr(Rand - 10);
  9.   end;

marcov

  • Administrator
  • Hero Member
  • *
  • Posts: 12634
  • FPC developer.
Re: VirtualStringTree - class instead of record
« Reply #7 on: May 02, 2022, 10:24:42 am »
Afaik VST can mass allocate nodes, so classes might not work. Simply have a record with one member that is a class.

wp

  • Hero Member
  • *****
  • Posts: 13349
Re: VirtualStringTree - class instead of record
« Reply #8 on: May 02, 2022, 10:53:21 am »
In the attachment I am posting a tested example of using VirtualStringTree with a class as data (rather than a record). If there is a pointer to the class (TTreeData = class(...), PTreeClass = ^TTreeClass), basically the same syntax can be used as with records, no ugly type-casts needed. Except for OnFreeNode which always must call .Free. The example works in both Delphi and ObjFPC compiler modes.


carl_caulkett

  • Hero Member
  • *****
  • Posts: 654
Re: VirtualStringTree - class instead of record
« Reply #9 on: September 28, 2024, 03:23:50 pm »
That's a big help! Thanks very much 🙏🏽
"It builds... ship it!"

 

TinyPortal © 2005-2018