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;