FMX.Treeview 函数 TTreeViewContent.GetLastVisibleObjectIndex 出错
Error in FMX.Treeview function TTreeViewContent.GetLastVisibleObjectIndex
我正在将 VCL 应用程序迁移到 Firemonkey。它具有几个显示文件夹树的 TTreeview 控件。在主窗体的 OnCreate 事件处理程序中,从 .Ini 文件中读取文件夹的路径并设置树视图。在主窗体出现在屏幕上之前的某个时刻,FMX.TreeView TTreeViewContent.GetLastVisibleObjectIndex 函数出现异常,即:
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;
我检查了变量的值,发现 FTreeView.FGlobalList.Count =1 和 FTreeView.FLastVisibleItem = -1。错误发生在语句
Item := FTreeView.FGlobalList[FTreeView.FLastVisibleItem];
因为数组索引无效。
此代码似乎与确定哪些树视图项在树视图控件的滚动 window 中可见有关。由于错误发生在表单显示之前,我尝试在更新树视图时使树视图不可见,如下代码:
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;
如果程序 运行 在显示表单之前没有设置树视图控件,即没有从 .ini 文件读取文件夹路径并且没有更新树视图控件,则错误不会发生。
关于如何避免函数中看似编码错误的任何建议 TTreeViewContent.GetLastVisibleObjectIndex?
回答Tom,FolderToTreeview的代码是:
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 是单独的 class 层次结构的元素,用于在内存树结构中存储根文件夹下所有文件和文件夹的名称和元数据。 class TFolder 的根对象有一个 Read(Path: string) 方法,它通过使用 FindFirst 和 FindNext 过程访问根目录下的文件和目录来生成它的所有节点。 Folder.Read 在调用 FolderToTreeview 之前被调用。由于这种额外的复杂性,我认为您无法 运行 FolderToTreeview 方法。
我找到了我发布的问题的解决方案。 FolderToTreeview方法修改如下:
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;
最重要的变化是将 Treeview 节点更新代码括在 BeginUpdate ... EndUpdate 括号中。
现在在 TTreeviewItemFolderCpt 构造函数中分配链接到 TreeviewItem 的 FolderCpt:
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;
我正在将 VCL 应用程序迁移到 Firemonkey。它具有几个显示文件夹树的 TTreeview 控件。在主窗体的 OnCreate 事件处理程序中,从 .Ini 文件中读取文件夹的路径并设置树视图。在主窗体出现在屏幕上之前的某个时刻,FMX.TreeView TTreeViewContent.GetLastVisibleObjectIndex 函数出现异常,即:
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;
我检查了变量的值,发现 FTreeView.FGlobalList.Count =1 和 FTreeView.FLastVisibleItem = -1。错误发生在语句
Item := FTreeView.FGlobalList[FTreeView.FLastVisibleItem];
因为数组索引无效。
此代码似乎与确定哪些树视图项在树视图控件的滚动 window 中可见有关。由于错误发生在表单显示之前,我尝试在更新树视图时使树视图不可见,如下代码:
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;
如果程序 运行 在显示表单之前没有设置树视图控件,即没有从 .ini 文件读取文件夹路径并且没有更新树视图控件,则错误不会发生。
关于如何避免函数中看似编码错误的任何建议 TTreeViewContent.GetLastVisibleObjectIndex?
回答Tom,FolderToTreeview的代码是:
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 是单独的 class 层次结构的元素,用于在内存树结构中存储根文件夹下所有文件和文件夹的名称和元数据。 class TFolder 的根对象有一个 Read(Path: string) 方法,它通过使用 FindFirst 和 FindNext 过程访问根目录下的文件和目录来生成它的所有节点。 Folder.Read 在调用 FolderToTreeview 之前被调用。由于这种额外的复杂性,我认为您无法 运行 FolderToTreeview 方法。
我找到了我发布的问题的解决方案。 FolderToTreeview方法修改如下:
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;
最重要的变化是将 Treeview 节点更新代码括在 BeginUpdate ... EndUpdate 括号中。
现在在 TTreeviewItemFolderCpt 构造函数中分配链接到 TreeviewItem 的 FolderCpt:
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;