I am migrating a VCL application to Firemonkey. It features a couple of TTreeview controls which display folder trees. In the main form's OnCreate event handler the paths of the folders are read from a .Ini file and the treeviews are set up. At some point before the main form appears on screen, an exception occurs in the FMX.TreeView TTreeViewContent.GetLastVisibleObjectIndex function, which is:
function TTreeViewContent.GetLastVisibleObjectIndex: Integer;
var
Item: TTreeViewItem;
begin
if (FTreeView.FGlobalList.Count > 0) and (FTreeView.FLastVisibleItem < FTreeView.FGlobalList.Count) then
begin
Item := FTreeView.FGlobalList[FTreeView.FLastVisibleItem];
{etc.}
end
else
Result := ControlsCount;
end;
I have inspected the values of the variables and found that FTreeView.FGlobalList.Count =1 and FTreeView.FLastVisibleItem = -1. The error occurs in the statement
Item := FTreeView.FGlobalList[FTreeView.FLastVisibleItem];
since the array index is invalid.
This code seems to be related to determining which treeview items are visible within the scrolling window of the treeview control. Since the error occurs before the form has been displayed, I tried making the treeview invisible during the update of the treview, as in the following code:
procedure TFormMain.UpdateTreeview(var Folder: TFolder; Treeview: TTreeview);
begin
if Folder= FInputFolder then
begin
if not FTreeviewInputFolderValid then
begin
Treeview.Visible:= False;
Treeview.BeginUpdate;
FolderToTreeView(Folder, Treeview);
//Treeview.InvalidateRect(Treeview.ContentRect);
Treeview.ExpandAll;
Treeview.EndUpdate;
Treeview.Visible:= True;
FTreeviewInputFolderValid:= True;
end;
end;
{Ditto for FOutputFolder}
end;
If the program is run without setting up the treeview controls before the form is shown, i.e. by not reading the folder paths from a .ini file and not updating the treeview controls, then the error does not occur.
Any suggestions about how to avoid what seems to be a coding error in function TTreeViewContent.GetLastVisibleObjectIndex?
In answer to Tom, the code of FolderToTreeview is:
procedure TFormMain.FolderToTreeview(Folder: TFolder; Treeview: TTreeview);
var
TreeviewOwner: TComponent;
RootNode: TTreeViewItemFolderCpt;
procedure AddFolderChildCpts(ParentTreeNode: TTreeViewItemFolderCpt; Folder: TFolder);
var
i: integer;
FolderCpt: TFolderCpt;
FileCpt: TFileCpt;
SubFolder: TFolder;
ChildTreeNode: TTreeViewItemFolderCpt;
Found: Boolean;
begin
{Add all cpts of folder to child nodes of ParentTreeNode}
for i:= 0 to Folder.CptCount-1 do
begin
FolderCpt:= Folder.Cpts[i];
ChildTreeNode:= TTreeViewItemFolderCpt.Create(ParentTreeNode);
ParentTreeNode.AddObject(ChildTreeNode);
ChildTreeNode.Parent:= ParentTreeNode;
ChildTreeNode.FolderCpt:= FolderCpt;
ChildTreeNode.OnPaint:= TreeViewItemPaint;
if FolderCpt is TFileCpt then
begin
FileCpt:= FolderCpt as TFileCpt;
ChildTreeNode.ImageIndex:= 1;
end
else if FolderCpt is TFolder then
begin
SubFolder:= FolderCpt as TFolder;
ChildTreeNode.ImageIndex:= 0;
{Recursively add subfolder:}
AddFolderChildCpts(ChildTreeNode, SubFolder);
end;
end;
end;
begin
if not Folder.IsSorted then
Folder.Sort(True);
{Delete all existing nodes in tree:}
Treeview.Clear;
{Create a new root node and add to tree:}
RootNode:= TTreeviewItemFolderCpt.Create(Treeview);
Treeview.AddObject(RootNode);
RootNode.Parent:= Treeview;
{Link folder object to root tree node:}
RootNode.FolderCpt:= Folder;
RootNode.ImageIndex:= 0;
RootNode.OnPaint:= TreeViewItemPaint;
{Now install child folder cpts:}
AddFolderChildCpts(RootNode, Folder);
end;
TFolder, TFolderCpt, TFileCpt are elements of a separate class hierarchy to store in a memory tree structure the names and metadata of all files and folders under the root folder. The root object of class TFolder has a method Read(Path: string) which generates all its nodes by visiting the files and directories under the root directory using FindFirst and FindNext procedures. Folder.Read is called before FolderToTreeview is called. Because of this extra complication I don't think that you be able to run the FolderToTreeview method.
I have found a solution to the problem I posted. The method FolderToTreeview has been amended as follows:
procedure TFormMain.FolderToTreeview(Folder: TFolder; Treeview: TTreeview;
PaintEventHandler: TOnPaintEvent);
const
COptionA= True; {True if TreeviewItem.Clear used to destroy existing tree nodes}
COptionB= False; {True if PaintEventHandler assigned after tree nodes have been created}
var
RootNode: TTreeViewItemFolderCpt;
i: integer;
procedure AddFolderChildCpts(ParentTreeNode: TTreeViewItemFolderCpt;
Folder: TFolder; PaintEventHandler: TOnPaintEvent);
var
i: integer;
FolderCpt: TFolderCpt;
FileCpt: TFileCpt;
SubFolder: TFolder;
ChildTreeNode: TTreeViewItemFolderCpt;
begin
{Add all cpts of folder to child nodes of ParentTreeNode}
for i:= 0 to Folder.CptCount-1 do
begin
FolderCpt:= Folder.Cpts[i];
ChildTreeNode:= TTreeViewItemFolderCpt.Create(ParentTreeNode, FolderCpt);
ParentTreeNode.AddObject(ChildTreeNode);
ChildTreeNode.Parent:= ParentTreeNode;
ChildTreeNode.OnPaint:= PaintEventHandler;
if FolderCpt is TFileCpt then
begin
FileCpt:= FolderCpt as TFileCpt;
ChildTreeNode.ImageIndex:= 1;
end
else if FolderCpt is TFolder then
begin
SubFolder:= FolderCpt as TFolder;
ChildTreeNode.ImageIndex:= 0;
{Recursively add subfolder:}
AddFolderChildCpts(ChildTreeNode, SubFolder, PaintEventHandler);
end;
end;
end;
procedure SetSubNodesPaintEventHandler(ParentTreeNode: TTreeViewItem;
PaintEventHandler: TOnPaintEvent);
var
i, j: integer;
ChildTreeNodeI: TTreeViewItem;
begin
ParentTreeNode.OnPaint:= PaintEventHandler;
{Assign PaintEventHandler to all child nodes of TreeNode}
for i:= 0 to ParentTreeNode.Count-1 do
begin
ChildTreeNodeI:= ParentTreeNode.Items[i];
SetSubNodesPaintEventHandler(ChildTreeNodeI, PaintEventHandler);
end;
end;
begin
if not Folder.IsSorted then
Folder.Sort(True);
Treeview.BeginUpdate;
try
{Delete all existing nodes in tree:}
if COptionA then
Treeview.Clear
else
for i:= Treeview.Count-1 downto 0 do
Treeview.Items[i].Release;
{Create a new root node and add to tree:}
RootNode:= TTreeviewItemFolderCpt.Create(Treeview, Folder);
Treeview.AddObject(RootNode);
RootNode.Parent:= Treeview;
{Assign properties to root tree node:}
RootNode.ImageIndex:= 0;
{Now install child folder cpts:}
if COptionB then
{For testing purposes}
AddFolderChildCpts(RootNode, Folder, nil)
else
AddFolderChildCpts(RootNode, Folder, PaintEventHandler);
{Assign OnPaint event handler:}
RootNode.OnPaint:= PaintEventHandler;
if COptionB then
{For testing purposes}
SetSubNodesPaintEventHandler(RootNode, PaintEventHandler);
finally
Treeview.EndUpdate;
end;
end;
The most important change was to enclose the Treeview nodes update code in BeginUpdate ... EndUpdate brackets.
The FolderCpt linked to the TreeviewItem is now assigned in the TTreeviewItemFolderCpt constructor:
TTreeViewItemFolderCpt = class(TTreeViewItem)
protected
FFolderCpt: TFolderCpt;
procedure SetFolderCpt(const Value: TFolderCpt);
function GetName: string;
procedure SetName(Value: string);
public
constructor Create(Owner: TComponent; FolderCpt: TFolderCpt);
property FolderCpt: TFolderCpt read FFolderCpt write SetFolderCpt;
property Name: string read GetName write SetName;
end;
constructor TTreeViewItemFolderCpt.Create(Owner: TComponent;
FolderCpt: TFolderCpt);
begin
inherited Create(Owner);
FFolderCpt:= FolderCpt;
end;