Forum > LCL

[SOLVED] Virtual String Tree

(1/1)

Pe3s:
Hello, when reading a VST, Acces Violation pops up?
What can it be caused by ?

--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---unit Unit1; {$mode objfpc}{$H+} interface uses  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, ExtCtrls,  VirtualTrees; type  PData = ^TData;  TData = packed record    Imie: String;    Nazwisko: String;  end;   { TForm1 }   TForm1 = class(TForm)    Button1: TButton;    Button2: TButton;    Button3: TButton;    Button4: TButton;    Edit1: TEdit;    Edit2: TEdit;    Edit3: TEdit;    Label1: TLabel;    Label2: TLabel;    Label3: TLabel;    Panel1: TPanel;    VST: TVirtualStringTree;    procedure Button1Click(Sender: TObject);    procedure Button2Click(Sender: TObject);    procedure Button3Click(Sender: TObject);    procedure Button4Click(Sender: TObject);    procedure FormCreate(Sender: TObject);    procedure VSTGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;      Column: TColumnIndex; TextType: TVSTTextType; var CellText: String);  private    procedure SaveData;    procedure LoadData;   public   end; var  Form1: TForm1;  Data: PData;  Node: PVirtualNode;  Cnt, I: Integer;  FS: TFileStream; implementation {$R *.lfm} { TForm1 } procedure TForm1.Button1Click(Sender: TObject);begin  VST.NodeDataSize := SizeOf(TData);   Node:= VST.AddChild(nil);  Data:= VST.GetNodeData(Node);   Data^.Imie:= Edit1.Text;  Data^.Nazwisko:= Edit2.Text;end; procedure TForm1.Button2Click(Sender: TObject);begin  if not Assigned(VST.FocusedNode) then Exit;    VST.DeleteNode(VST.FocusedNode);end; procedure TForm1.Button3Click(Sender: TObject);begin  LoadData;end; procedure TForm1.Button4Click(Sender: TObject);begin  SaveData;end; procedure TForm1.FormCreate(Sender: TObject);begin  //LoadData;end; procedure TForm1.VSTGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;  Column: TColumnIndex; TextType: TVSTTextType; var CellText: String);begin  Data:= VST.GetNodeData(Node);   case Column of  0: CellText:= Data^.Imie;  1: CellText:= Data^.Nazwisko;  end;end; procedure TForm1.SaveData;begin  FS:= TFileStream.Create(ExtractFilePath(Application.ExeName)+'baza.vst', fmCreate or fmShareDenyWrite);  try    FS.WriteWord(VST.RootNodeCount);    Node:= VST.GetFirst;    while Assigned(Node) do    begin      Data := VST.GetNodeData(Node);      FS.Write(Data^, SizeOf(TData));      Node:= VST.GetNext(Node);    end;  finally    FS.Free;  end;end; procedure TForm1.LoadData;begin   VST.Clear;  VST.NodeDataSize:= SizeOf(TData);   FS:= TFileStream.Create(ExtractFilePath(Application.ExeName)+'baza.vst', fmOpenRead or fmShareDenyNone);  try    Cnt:= FS.ReadWord;    if Cnt > 0 then    begin      I:= 1;      while I <= Cnt do      begin        Node:= VST.AddChild(nil);        Data:= VST.GetNodeData(Node);        FS.Read(Data^, SizeOf(TData));        Inc(I);      end;    end;  finally    FS.Free;  end;end;

jamie:
Strings are managed and nothing more than a pointer.

you are expecting to read pointers from file to be exactly correct for the current instance of your code, it will not be if the file was created with a different time of your app.

instead, you should be using something different to write strings out to file and reading them back.

 Like a ShortString type instead.

 Short strings you can specify the max length and your data record will have space for it and it will not be managed related.

 Don't use managed types in a record that is going to be stored later to be used in a different session of your code.

etc, etc and etc.

wp:
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:

--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---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:

--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---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:

--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---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:

--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---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.

--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---procedure TForm1.VSTGetNodeDataSize(Sender: TBaseVirtualTree;   var NodeDataSize: Integer);begin  NodeDataSize := SizeOf(TData);end;

Pe3s:
Thank you for your very valuable advice   :)

Navigation

[0] Message Index

Go to full version