Programming => LCL => Topic started by: geraldholdsworth on June 11, 2021, 08:24:30 pm

Title: Crashing TTreeView when setting up a sub-form.
Post by: geraldholdsworth on June 11, 2021, 08:24:30 pm
So, latest problem, which I've been battering my head agains the wall now for 24 hours and can't see a solution.

I've got a TTreeView on a form, and a custom TTreeNode:
Code: Pascal  [Select][+][-]
  1. type
  2.  //We need a custom TTreeNode, as we want to tag on some extra information
  3.  TMyTreeNode = class(TTreeNode)
  4.   private
  5.    FParentDir : Integer;
  6.    FIsDir     : Boolean;
  7.   public
  8.    property ParentDir: Integer read FParentDir write FParentDir;//Parent directory reference
  9.    property IsDir    : Boolean read FIsDir write FIsDir;        //Is it a directory
  10.  end;
At first I couldn't get it to work in anything but the original TTreeView - I forgot to set the OnCreateNodeClass. OK, got that sorted. But I wanted to re-use the code to populate a TTreeView on another form. This just refuses to work.

Originally, I had the following procedures as part of MainForm, but separated them out (but still in the Unit). The following follows the above type declaration.
Code: Pascal  [Select][+][-]
  1. procedure AddImageToTree(Tree: TTreeView;ImageToUse: TDiscImage);
  2. procedure AddDirectoryToTree(CurrDir:TTreeNode;dir:Integer;Tree:TTreeView;
  3.                                   ImageToUse:TDiscImage;var highdir:Integer);
  4. function AddFileToTree(ParentNode: TTreeNode;importfilename: String;
  5.    index: Integer;dir: Boolean;Tree:TTreeView;ImageToUse:TDiscImage): TTreeNode;
And the procedures are thus:
Code: Pascal  [Select][+][-]
  1. {------------------------------------------------------------------------------}
  2. //Populate a directory tree with an image
  3. {------------------------------------------------------------------------------}
  4. procedure AddImageToTree(Tree: TTreeView;ImageToUse: TDiscImage);
  5. var
  6.  //Used as a marker to make sure all directories are displayed.
  7.  //Some double sided discs have each side as separate discs
  8.  highdir    : Integer;
  9. begin
  10.  //Clear the tree view, prior to populating it
  11.  Tree.Items.Clear;
  12.  //Set the highdir to zero - which will be root to start with
  13.  highdir:=0;
  14.  //Then add the directories, if there is at least one
  15.  if Length(ImageToUse.Disc)>0 then
  16.  begin
  17.   //Start by adding the root (could be more than one root, particularly on
  18.   //double sided discs)
  19.   repeat
  20.    //This will initiate the recursion through the directory structure, per side
  21.    AddDirectoryToTree(Tree.Items.Add(nil,ImageToUse.Disc[highdir].Directory),
  22.                       highdir,Tree,ImageToUse,highdir);
  23.    //Finished on this directory structure, so increase the highdir
  24.    inc(highdir);
  25.    //and continue until we have everything on the disc. This will, in effect,
  26.    //add the second root for double sided discs.
  27.   until highdir=Length(ImageToUse.Disc);
  28.   //Expand the top level of the tree (but not MMB)
  29.   if ImageToUse.FormatNumber>>4<>diMMFS then Tree.TopItem.Expand(False);
  30.   //And the root for the other side of the disc
  31.   if ImageToUse.DoubleSided then
  32.   begin
  33.    //First, we need to find it
  34.    repeat
  35.     inc(highdir);
  36.     //If there is one, of course - but it must be a directory
  37.    until(highdir>=Tree.Items.Count) or (TMyTreeNode(Tree.Items[highdir-1]).IsDir);
  38.    if highdir>Tree.Items.Count then
  39.     highdir:=Tree.Items.Count;
  40.    //Found? then expand it
  41.    if TMyTreeNode(Tree.Items[highdir-1]).IsDir then
  42.     Tree.Items[highdir-1].Expand(False);
  43.   end;
  44.  end;
  45. end;
  47. {------------------------------------------------------------------------------}
  48. //Adds a directory to the TreeView - is called recursively to drill down the tree
  49. {------------------------------------------------------------------------------}
  50. procedure AddDirectoryToTree(CurrDir:TTreeNode;dir:Integer;Tree:TTreeView;
  51.                                   ImageToUse:TDiscImage;var highdir:Integer);
  52. var
  53.  entry: Integer;
  54.  Node: TTreeNode;
  55. begin
  56.  //Make a note of the dir ref, it is the highest
  57.  if dir>highdir then highdir:=dir;
  58.  //Set the 'IsDir' flag to true, as this is a directory
  59.  TMyTreeNode(CurrDir).IsDir:=True;
  60.  //Iterate though all the entries
  61.  for entry:=0 to Length(ImageToUse.Disc[dir].Entries)-1 do
  62.  begin
  63.   //Adding new nodes for each one
  64.   Node:=AddFileToTree(CurrDir,ImageToUse.Disc[dir].Entries[entry].Filename,
  65.                       entry,false,Tree,ImageToUse);
  66.   //If it is, indeed, a direcotry, the dir ref will point to the sub-dir
  67.   if ImageToUse.Disc[dir].Entries[entry].DirRef>=0 then
  68.   //and we'll recursively call ourself to add these entries
  69.    AddDirectoryToTree(Node,ImageToUse.Disc[dir].Entries[entry].DirRef,Tree,
  70.                       ImageToUse,highdir);
  71.  end;
  72. end;
  74. {------------------------------------------------------------------------------}
  75. //Add a file or directory to the TTreeView, under ParentNode
  76. {------------------------------------------------------------------------------}
  77. function AddFileToTree(ParentNode: TTreeNode;importfilename: String;
  78.    index: Integer;dir: Boolean;Tree:TTreeView;ImageToUse:TDiscImage): TTreeNode;
  79. begin
  80.  Result:=nil;
  81.  if(ParentNode=nil)or(index<0)then exit;
  82.  RemoveTopBit(importfilename);
  83.  //Now add the entry to the Directory List
  84.  if ParentNode.HasChildren then
  85.   //Insert it before the one specified
  86.   if index<ParentNode.Count then
  87.    Result:=Tree.Items.Insert(ParentNode.Items[index],importfilename)
  88.   else //Unless this is the last one
  89.    Result:=Tree.Items.AddChild(ParentNode,importfilename)
  90.  else
  91.   //Is the first child, so just add it
  92.   Result:=Tree.Items.AddChildFirst(ParentNode,importfilename);
  93.  if Result<>nil then
  94.  begin
  95.   if ParentNode.Parent<>nil then //No parent, so will be the root
  96.    TMyTreeNode(Result).ParentDir:=ImageToUse.Disc[TMyTreeNode(ParentNode).ParentDir].
  97.                                       Entries[ParentNode.Index].DirRef
  98.   else
  99.    TMyTreeNode(Result).ParentDir:=ParentNode.Index; //But may not be the only root
  100.   TMyTreeNode(Result).IsDir:=dir;
  101.   Tree.Repaint;
  102.   //And update the free space display
  103.   if ImageToUse=MainForm.Image then MainForm.UpdateImageInfo;
  104.  end;
  105. end;
Remembering about the OnCreateNodeClass event handler, I copied it to the secondary form:
Code: Pascal  [Select][+][-]
  1. {------------------------------------------------------------------------------}
  2. //This just creates our custom TTreeNode
  3. {------------------------------------------------------------------------------}
  4. procedure TMainForm.DirListCreateNodeClass(Sender: TCustomTreeView;
  5.   var NodeClass: TTreeNodeClass);
  6. begin
  7.   NodeClass:=TMyTreeNode;
  8. end;
Code: Pascal  [Select][+][-]
  1. {------------------------------------------------------------------------------}
  2. //This just creates our custom TTreeNode
  3. {------------------------------------------------------------------------------}
  4. procedure TImportSelectorForm.ImportDirListCreateNodeClass(
  5.  Sender: TCustomTreeView; var NodeClass: TTreeNodeClass);
  6. begin
  7.  NodeClass:=TMyTreeNode;
  8. end;
OK, but it still crashes.

I call this from a procedure in MainForm:
Code: Pascal  [Select][+][-]
  1. AddImageToTree(DirList,Image); // Works OK
But, in another procedure, I call this to populate the TTreeView on the secondary form (having read in another Image):
Code: Pascal  [Select][+][-]
  1. AddImageToTree(ImportSelectorForm.ImportDirList,NewImage); //Crashes
As a workaround, I created a temporary TreeView in MainForm and did this:
Code: Pascal  [Select][+][-]
  1. AddImageToTree(TempTreeView,NewImage);
  2. ImportSelectorForm.ImportDirList.Items:=TempTreeView.Items;
Which works. I'm just confused as to why the original crashes. Of course, if I have no option but to use the second option, I'll just re-integrate those three procedures into the MainForm definition.

Oh - Lazarus 2.12 on macOS Catalina. Screen shot of error is in attachments.
Title: Re: Crashing TTreeView when setting up a sub-form.
Post by: geraldholdsworth on June 11, 2021, 08:36:59 pm
And then I go and answer my own question.

It is because ImportSelectorForm is not yet visible, hence the TTreeView is also not visible.

Found this because I hid the TempTreeView and that then also crashed!
TinyPortal © 2005-2018