PLEASE: This is such a short program, it is much more helpful for us if you post a complete, compilable project. Put .lfm, .pas, .lpi, and lpr files into a common zip and upload it via "Attachment and other options".
The issue is that you write/read the TData record directly (FS.Write(Data^, Sizeof(TData)). This is wrong here because the TData record contains dynamic strings which are pointers. So, you are writing only the address of the string, but not the string content itself, and after reading the address points to anywhere...
There are two ways to solve this:
/1/ Declare the strings in TData as ShortStrings, i.e. with their max length in square brackets. This way the string are stored inside the TData record, and your code works:
type
TData = packed record
Imie: String[32];
Nazwisko: String[16];
end;
/2/ If you have strings longer than 255 characters (this is the max length of ShortStrings) or want smaller files you still can use your original TData record, but you must read/write the strings with Stream.ReadAnsiString/Stream.WriteAnsiString. Be careful with the layout of the record elements if you have non-string data as well: put the strings at the end of the record declaration and write/read the non-string data first en bloc and the strings separately. Here's an example:
type
TData = record
IntNumber: integer;
FloatValue: Double;
Imie: String; // declare the strings at the end of the record
Nazwisko: String;
end;
procedure TForm1.SaveData;
begin
...
FS.Write(Data^, SizeOf(TData - 2*SizeOf(string)); // Write everything en bloc, but skip the 2 strings at the end
FS.WriteAnsiString(Data^.Imie);
FS.WriteAnsiString(Data^.Nazwisko);
...
end;
procedure TForm1.LoadData;
begin
...
FS.Read(Data^, SizeOf(TData - 2*SizeOf(String)); // Read everything except for the strings
Data^.Imie := FS.ReadAnsiString;
Data^.Nazwisko := FS.ReadAnsiString;
...
I found some other issues in your code:
/1/ "Abuse" of global variables with should be local variables instead. Because your Data variable is global it exists only once, but Data is used all over again and there is a high risk that one operation may overwrite the Data set up by another operation. Much better if you declare Node, Data, Cnt, I, and FS only locally within the procedures that need them. Here is the OnGetText handler as an example:
procedure TForm1.VSTGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
Column: TColumnIndex; TextType: TVSTTextType; var CellText: String);
var
Data: PData; // declare locally!
begin
Data:= VST.GetNodeData(Node);
case Column of
0: CellText:= Data^.Imie;
1: CellText:= Data^.Nazwisko;
end;
end;
/2/ There is no OnFreeNode handler to release the memory occupied by the strings in the TData record. Otherwise you have a huge memory leak:
procedure TForm1.VSTFreeNode(Sender: TBaseVirtualTree; Node: PVirtualNode);
var
Data: PData;
begin
Data := VST.GetNodeData(Node);
Data^.Imie := '';
Data^.Nazwisko := '';
end;
/3/ I would not set the node's NodeDatasize multiple times at different locations. If you happen to assign different values you will destroy the tree which requires a constant NodeDataSize for all nodes. Better to do this in the OnGetNodeDataSize event, or to set it in the form's OnCreate.
procedure TForm1.VSTGetNodeDataSize(Sender: TBaseVirtualTree;
var NodeDataSize: Integer);
begin
NodeDataSize := SizeOf(TData);
end;