0

Is there any way to add locally stored (in a file) TVirtualStringTree to another as a subtree under a specific node?

EXAMPLE

In the example, I used the TTreeView for the sake of speed, but I need to use the TVirtualStringTree. I tried to find something about this method.

{ TBaseVirtualTree.AddFromStream Method
  Adds the content from the given stream to the given node. }
procedure AddFromStream(Stream: TStream; TargetNode: PVirtualNode);
{ Description
  AddFromStream restores the subtree stored in Stream and adds it to 
  TargetNode. The content of the stream must have been saved previously with 
  SaveToStream. }

Which is perfect for the situation, because i need to add a previously saved tree to a specific node (the selected one in the example). But I can't find anything on the web about it or an example of it in action. So how can I restore a saved tree (all of its contents) as a subtree under the selected node?

Triber
  • 1,525
  • 2
  • 21
  • 38
  • I don't know this component at all (TVirtualStringTree component), but if you can't find anything about the serialization process I suppose that it uses a custom (do it yourself) protocol/format for the serialization. In this case you have to write a serializer class to load/store the data in the format that match your needs in the larges scale. – SOLID Developper Oct 02 '18 at 06:52

2 Answers2

1

For storing a particular node and its children of a virtual tree to file, you must use the method SaveToStream which, unlike SaveToFile accepts an optional parameter for the source node:

procedure SaveNodeToFile(ATree: TBaseVirtualTree; ANode: PVirtualNode; 
  AFileName: String);
var
  stream: TFileStream;
begin
  stream := TFileStream.Create(AFileName, fmCreate);
  try
    ATree.SaveToStream(stream, ANode);
  finally
    stream.Free;
  end;
end;

Since the virtual tree does not know anything about the data you must provide a method how to write the data assigned to each node. The tree offers the event OnSaveNode for this purpose. Here I assume that the node has data in a record containing a string "Caption" (or course, you must adapt this to your needs):

type
  PTreeData = ^TTreeData;
  TTreeData = record
    Caption: String;
  end;

procedure TForm1.VirtualStringTree1SaveNode(Sender: TBaseVirtualTree; 
  ANode: PVirtualNode; Stream: TStream);
var
  data: PTreeData;
  n: Integer;
begin
  // Get pointer to the node's data
  data := Sender.GetNodeData(ANode);
  // Write the length of the string (in characters)
  n := Length(data^.Caption);
  Stream.WriteDWord(n);
  if n > 0 then
    // write the string characters
    Stream.Write(data^.Caption[1], Length(data^.Caption) * SizeOf(char));
end;

In order to read the file back to a node of another tree, the method AddFromStream that you already mentioned is perfect:

procedure LoadNodeFromFile(ATree: TBaseVirtualTree; ANode: PVirtualNode; AFileName: String);
var
  stream: TFileStream;
begin
  stream := TFileStream.Create(AFileName, fmOpenRead);
  try
    ATree.AddFromStream(stream, ANode);
  finally
    stream.Free;
  end;
end;

Again, you need to tell the tree how to get the data assigned to each node - the handler for the event OnLoadNode must be the exact opposite of the OnSaveNode event:

procedure TForm1.VirtualStringTree2LoadNode(Sender: TBaseVirtualTree; Node: PVirtualNode; 
  Stream: TStream);
var
  data: PTreeData;
  n: DWord;
begin
  // Get pointer to the node's data
  data := Sender.GetNodeData(Node);
  // Read the string length (in characters)
  n := Stream.ReadDWord;
  if n > 0 then begin
    // Set the length of the string
    SetLength(data^.Caption, n);
    if n > 0 then
      // Read the string's characters
      Stream.Read(data^.Caption[1], n * SizeOf(char));
  end;
end;    
wp_1233996
  • 784
  • 1
  • 4
  • 12
  • Perfect, it works like a charm !, and also, i needed to store from the same virtualtree ( saving ) and load into another node in the same tree, with some little modifications in your code it worked, TY very much my friend you just saved lots and lots of my time :) – Leonardo Floriano Oct 04 '18 at 03:39
  • And also, i tried using the copyto method using a "buffer" tree, I was saving the main tree to a file ( simple saving it ), then loading it to a buffer tree component and then copying the whole buffer tree using CopyTo(Source: PVirtualNode; Target: PVirtualNode; Mode: TVTNodeAttachMode; ChildrenOnly: Boolean), on mode it was amAddChildFirst, so it was coping each node as a child to my target, it worked but not as clean as your code :) – Leonardo Floriano Oct 04 '18 at 03:46
  • You can also create a TMemoryStream to transfer the nodes - this avoids the intermediate file. – wp_1233996 Oct 04 '18 at 10:58
  • Or you can use CopyTo to avoid the stream altogether. But you must also provide OnLoadNode and OnSaveNode handlers because the tree does not know about the data. Note also that if different trees are involved in the CopyTo method you must assign the event handlers to the correct tree - this may be counter-intuitive: although you *read* the nodes from the source tree you must attach the *OnSaveNode* handler to it because data are written to the intermediate stream! Vice versa for the destination tree. – wp_1233996 Oct 04 '18 at 11:05
  • I am going to reopen this topic, with another problem that i am having, that is: – Leonardo Floriano Oct 25 '18 at 06:31
0

EDIT: solved, is needed to add the getnodedatasize event:

procedure TfrmBuilder.VST1GetNodeDataSize(Sender: TBaseVirtualTree;
  var NodeDataSize: Integer);
begin
   NodeDataSize := SizeOf(TTreeData);
end;

Without that the tree just doesn't load.

Thanks,


I am going to reopen this topic, with another problem that I am having, that is:

I can save and load flawlessly with two columns that contains strings:

type
  PTreeData = ^TTreeData;
  TTreeData = record
  Fname: String;
  quant: String;
End;

procedure TfrmBuilder.VST1LoadNode(Sender: TBaseVirtualTree; Node: PVirtualNode;
  Stream: TStream);
var
  data: PTreeData;
  n: DWord;
begin
  // Get pointer to the node's data
  data := Sender.GetNodeData(Node);

  // Read the string length (in characters)
  n := Stream.ReadDWord;
  if n > 0 then begin
    // Set the length of the string
    SetLength(data^.Fname, n);
    if n > 0 then
      // Read the string's characters
      Stream.Read(data^.Fname[1], n * SizeOf(char));
  end;

  n := Stream.ReadDWord;
  if n > 0 then begin
    // Set the length of the second string
    SetLength(data^.quant, n);
    if n > 0 then
      // Read the second string's characters
      Stream.Read(data^.quant[1], n * SizeOf(char));
  end;

end;

procedure TfrmBuilder.VST1SaveNode(Sender: TBaseVirtualTree; Node: PVirtualNode;
  Stream: TStream);
var
  data: PTreeData;
  n: Integer;
begin
  // Get pointer to the node's data
  data := Sender.GetNodeData(Node);

  // Write the length of the string (in characters)
  n := Length(data^.Fname);
  Stream.WriteDWord(n);
  if n > 0 then
    begin
    // write the string characters
    Stream.Write(data^.Fname[1], Length(data^.Fname) * SizeOf(char));
    end;

  // Write the length of the second string (in characters)
  n := Length(data^.quant);
  Stream.WriteDWord(n);

  if n > 0 then
    begin
    // write the second string chars
    Stream.Write(data^.quant[1], Length(data^.quant) * SizeOf(char));
    end;

end;

BUT, when i try to save three columns, by just using the same method ( writing the string down in the stream), it loads at the first run of the executable, but when i close the exe, reopen and try to reload it gives an error, sisegv, in another words memory access violation

type
  PTreeData = ^TTreeData;
  TTreeData = record
  Fname: String;
  quant: String;
  OBS: string;

End;

procedure TfrmBuilder.VST1LoadNode(Sender: TBaseVirtualTree; Node: PVirtualNode;
  Stream: TStream);
var
  data: PTreeData;
  n: DWord;
begin
  // Get pointer to the node's data
  data := Sender.GetNodeData(Node);

  // Read the string length (in characters)
  n := Stream.ReadDWord;
  if n > 0 then begin
    // Set the length of the string
    SetLength(data^.Fname, n);
    if n > 0 then
      // Read the string's characters
      Stream.Read(data^.Fname[1], n * SizeOf(char));
  end;

  n := Stream.ReadDWord;
  if n > 0 then begin
    // Set the length of the second string
    SetLength(data^.quant, n);
    if n > 0 then
      // Read the second string's characters
      Stream.Read(data^.quant[1], n * SizeOf(char));
  end;

  { THE CODE FOR THE THIRD STRING: 
  n := Stream.ReadDWord;
  if n > 0 then begin
    // Set the length of the THIRD string
    SetLength(data^.OBS, n);
    if n > 0 then
      // Read the THIRD string's characters
      Stream.Read(data^.OBS[1], n * SizeOf(char));
  end;
  }
end;

procedure TfrmBuilder.VST1SaveNode(Sender: TBaseVirtualTree; Node: PVirtualNode;
  Stream: TStream);
var
  data: PTreeData;
  n: Integer;
begin
  // Get pointer to the node's data
  data := Sender.GetNodeData(Node);

  // Write the length of the string (in characters)
  n := Length(data^.Fname);
  Stream.WriteDWord(n);
  if n > 0 then
    begin
    // write the string characters
    Stream.Write(data^.Fname[1], Length(data^.Fname) * SizeOf(char));
    end;

  // Write the length of the second string (in characters)
  n := Length(data^.quant);
  Stream.WriteDWord(n);

  if n > 0 then
    begin
    // write the second string chars
    Stream.Write(data^.quant[1], Length(data^.quant) * SizeOf(char));
    end;

  { 
  // Write the length of the THIRD string (in characters)
  n := Length(data^.OBS);
  Stream.WriteDWord(n);

  if n > 0 then
    begin
    // write the THIRD string chars
    Stream.Write(data^.OBS[1], Length(data^.OBS) * SizeOf(char));
    end;
   }
end;

What could it be? for 2 strings (2 columns) it works like a charm, but for three I got access violation?

Thanks in advance, Leonardo

PS: a pic from the grid that i am trying to save / load

grid

As seen it has two columns, first is from data^.fname, second from data^.quant, and third should have been from data^.OBS, but it just doesn't load (sigsegv), so I took it out.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
  • I assume that you added the third string to the writing code, too. -- Your reading code (as well as mine above) has an initialization issue when the data string is empty because it does not set data^.FName etc to an empty string when n is read to be zero. The correct code would be: n := StreamReadDWord; SetLength(data^.FName, n); if n > 0 then Stream.Read(data^.FName[1], n*SizeOf(char)); (in other words: drop the first "if n > 0"). – wp_1233996 Nov 02 '18 at 08:16