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