Как заполнить Treeview через FTP

Сценарий

Я пытаюсь воспроизвести стандартный способ заполнения Treeview каталогами/папками из структуры папок, начиная с корня, но используя IdFTP для получения структуры с удаленного сервера вместо моего локального жесткого диска. Я бы хотел, чтобы результат был похож на такие клиенты, как Filezilla.

Я использовал этот достаточно стандартный код из Swiss Delphi Center (который работает для отображения структуры моего жесткого диска), а затем изменил его, чтобы использовать IdFTP.ChangeDir(Directory) и IdFTP.List вместо FindFirst() и FindNext().

Проблема

Кажется, я запутался, так как рекурсия неправильно «раскручивается», так что, как только она проходит вниз по каталогам /cpanel/cash/config на удаленном сервере, она не возвращается и не проходит по всем другим висящим каталогам. root, но выходит из процедуры, не отображая ничего другого. Также кажется, что не отображаются все папки верхнего уровня, но это может быть просто из-за порядка, в котором IdFTP.List возвращает их в

Может ли кто-нибудь сказать мне, что я сделал неправильно здесь?

Если вы также можете сказать мне, как мне показать корень (/), это было бы очень полезно

(Я закомментировал отображение не каталогов, так как на этом этапе мне нужны только папки)

Что я ожидал увидеть Скопировано с Filezilla

Что я увидел Использование Ttreeview в Delphi

Мой код

procedure TForm2.Button1Click(Sender: TObject);
var StartingDir : string;
begin
TreeView1.Items.BeginUpdate;
try
    StartingDir :=    '/';
    Screen.Cursor := crHourGlass;
    TreeView1.Items.Clear;
    FTPconnect;  //procedure to connect to remote server
    GetDirectories(TreeView1, StartingDir, nil, True);
    FTPDisconnect; //procedure to disconnect from remote server
finally
    TreeView1.Items.EndUpdate;
    Screen.Cursor := crDefault;
end;
end;

procedure TForm2.GetDirectories(Tree: TTreeView; Directory: string; Item: TTreeNode; IncludeFiles: Boolean);
var
  ItemTemp: TTreeNode;
  DirItemType : TIdDirItemType  ;
  Filename , NewStartingDirectory: string;
  i : Integer;
begin
  Tree.Items.BeginUpdate;
  IdFTP.ChangeDir(Directory);
  IdFTP.List;      //get directory of remote folder
  i:=0;
  repeat
     DirItemType := IdFTP.DirectoryListing[I].ItemType;
     Filename := IdFTP.DirectoryListing[I].FileName;
     If (DirItemType = ditDirectory) and (Filename <> '.') and (Filename <> '..')then
        begin
        if DirItemType = ditDirectory then
              Item := Tree.Items.AddChild(Item, Filename);
        ItemTemp := Item.Parent;
        if Directory = '/' then
            NewStartingDirectory := Directory  + Filename
        else
            NewStartingDirectory := Directory + '/' +Filename;
        GetDirectories(Tree, NewStartingDirectory, Item, IncludeFiles);
        Item := ItemTemp;
        end
     else 
        if IncludeFiles then
           begin  //this bit commented out as we only want to see directories
//         if (Filename <> '.') and (Filename <> '..') then
//         Tree.Items.AddChild(Item, Filename);
           end;
     inc(i);
  until i = IdFTP.DirectoryListing.Count;
  Tree.Items.EndUpdate;
end;

Код Swiss Delhpi Centre (для сравнения)

procedure TForm1.Button1Click(Sender: TObject);
var
  Node: TTreeNode;
  Path: string;
  Dir: string;
begin
  Dir := 'c:\temp';
  Screen.Cursor := crHourGlass;
  TreeView1.Items.BeginUpdate;
  try
    TreeView1.Items.Clear;
    GetDirectories(TreeView1, Dir, nil, True);
  finally
    Screen.Cursor := crDefault;
    TreeView1.Items.EndUpdate;
  end;
end;

procedure TForm1.GetDirectories(Tree: TTreeView; Directory: string; Item: TTreeNode; IncludeFiles: Boolean);
var
  SearchRec: TSearchRec;
  ItemTemp: TTreeNode;
begin
  Tree.Items.BeginUpdate;
  if Directory[Length(Directory)] <> '\' then Directory := Directory + '\';
  if FindFirst(Directory + '*.*', faDirectory, SearchRec) = 0 then
  begin
    repeat
      if (SearchRec.Attr and faDirectory = faDirectory) and (SearchRec.Name[1] <> '.') then
      begin
        if (SearchRec.Attr and faDirectory > 0) then
          Item := Tree.Items.AddChild(Item, SearchRec.Name);
        ItemTemp := Item.Parent;
        GetDirectories(Tree, Directory + SearchRec.Name, Item, IncludeFiles);
        Item := ItemTemp;
      end
      else if IncludeFiles then
        if SearchRec.Name[1] <> '.' then
          Tree.Items.AddChild(Item, SearchRec.Name);
    until FindNext(SearchRec) <> 0;
    FindClose(SearchRec);
  end;
  Tree.Items.EndUpdate;
end;

Я посмотрел ТАК здесь - слишком сложный и неправильный язык и здесь - похоже на швейцарский Delphi Center и здесь - неправильный язык и не уверен, что он делает.

если лучше использовать TlistView, не могли бы вы показать мне эквивалентный код для его использования?

Вы должны сохранить содержимое IdFTP.DirectoryListing в своей собственной переменной, иначе следующий вызов IdFTP.List() изменит его для каждой итерации рекурсии, независимо от того, где вы сейчас находитесь. Если у /.cpanel/caches/config нет или есть одна запись, тогда IdFTP.DirectoryListing.Count будет 0 или 1 для всего и выйдите из цикла именно с этой логикой.

AmigoJack 17.10.2022 11:17

@AmigoJack Спасибо. Я добавил переменную TheDirectoryListing : TIdFTPListItems;, добавил directoryListing := IdFTP.DirectoryListing; сразу после IdFTP.List и изменил ссылки на IdFTP.DirectoryListing на TheDirectoryListing;, но это не имело значения, я получил тот же результат. Как я и ожидал, я все еще звоню IdFTP.list на каждой итерации. Я, должно быть, неправильно понял, пожалуйста, уточните.

user2834566 17.10.2022 11:32

Вы просто скопировали ссылку, а не содержание. В качестве альтернативы можно выполнить итерацию IdFTP.DirectoryListing без рекурсии и сохранить только папки в TStringList — после этого вы можете выполнить рекурсию по своему собственному списку.

AmigoJack 17.10.2022 11:37

Это имеет смысл. Подскажите, как скопировать содержимое IdFTP.DirectoryListing?

user2834566 17.10.2022 11:44

Точно так же, как вы это делаете сейчас, но вместо того, чтобы добавлять новый узел дерева и повторно вызывать свой собственный метод, вы добавляете папку в свой список как текст.

AmigoJack 17.10.2022 11:49

Извините, я имел в виду, как мне правильно сделать directoryListing := IdFTP.DirectoryListing;, но скопировать содержимое IdFTP.DirectoryListing; вместо ссылки на него, как вы предложили (я хотел бы сначала попробовать это, прежде чем переписывать все это, чтобы покончить с рекурсией, сохраняя папки и т. д. )

user2834566 17.10.2022 11:57

Точно так же, как вы копируете содержимое объекта (например, TStringList) — либо ищите его, либо выделяйте его в отдельный вопрос.

AmigoJack 17.10.2022 12:13
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
7
126
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Непроверенный:

  • Переменную TIdFTP я сделал параметром, так как TTreeView тоже был один и надо делать последовательно, а не архаично.
  • Использование петель for вместо repeat until.
  • Устранение IncludeFiles, когда оно все равно не использовалось.
  • Устранение странной логики, чтобы всегда получать новый родитель TreeNode.
  • Больше не блокировать TreeView - сделайте это один раз перед вызовом этого метода и разблокируйте его после вызова - иначе вы делаете это десятки раз напрасно.
  • Основная логика такова, как я писал в комментариях:
    1. Сохраните все строки папок в свой собственный список и избегайте рекурсии на этом этапе.
    2. Зафиксируйте путь для объединения один раз, а не для каждой итерации цикла.
    3. Пройдитесь по этому списку, чтобы выполнить рекурсию — на данный момент состояние FTP не имеет значения, и вы не перепутаете списки на разных уровнях.
    4. Конечно, освободите созданный экземпляр StringList.
procedure TForm2.GetFolders
( Ftp: TIdFTP  // The source, from which we read the content
; Tree: TTreeView  // The destination, which we want to fill
; ParentNode: TTreeNode  // Node under which all new child nodes should be created
; Path: String  // Starting directory
);
var
    NewNode: TTreeNode;  // New child in the tree
    Filename: String;  // Check against unwanted folder entries
    i: Integer;  // Looping over both lists
    sl: TStringList;  // Collect folders only
begin
    FTP.ChangeDir( Path );
    FTP.List;  // Entire remote listing

    sl:= TStringList.Create;  // Collect all entries we're interested in
    try
        for i:= 0 to FTP.DirectoryListing.Count- 1 do begin  // For each entry
            Filename:= FTP.DirectoryListing[i].FileName;
            if  (FTP.DirectoryListing[i].ItemType= ditDirectory)  // Only folders
            and (Filename<> '.')
            and (Filename<> '..') then begin
                sl.Add( Filename );  // Only the name, not the full path
            end;
        end;

        // Do this only once
        if Path<> '/' then Path:= '/'+ Path+ '/';

        for i:= 0 to sl.Count- 1 do begin  // All collected folders
            NewNode:= Tree.Items.AddChild( ParentNode, sl[i] );  // Populate tree
            GetFolders( Ftp, Tree, NewNode, Path+ sl[i] );  // Recursion of folder name + current path
        end;
    finally
        sl.Free;
    end;
end;

Не тестировалось, но должно компилироваться.

Спасибо, что взяли на себя труд закодировать это. Понятно, что мне пришлось изменить прототип процедуры с Procedure GetFolders; на Procedure GetFolders ( FTP : TIdFTP; Tree : TTreeView; ParentNode :TTreeNode; Path: String );, но тогда этот альтернативный метод дал желаемый результат. Я принял ваш ответ, поскольку он отвечает на вопрос в заголовке, вводя дополнительный шаг, и я вижу логику этого. Мне все же было бы интересно посмотреть, можно ли это сделать, используя достаточно стандартный алгоритм в моем посте, но вместо этого используя методы FTP, если findfirst/findnext. - Может и не может.

user2834566 18.10.2022 10:47

Я нигде не определял procedure GetFolders; без параметров (но да: вы также должны объявить его в своем классе Form). Нет, нет "первого/следующего", потому что вы вытягиваете весь список сразу - так работает FTP в отличие от файловых систем. Это также не решит ваше непонимание того, что вы глобально работаете с одним и тем же экземпляром списка вместо того, чтобы иметь отдельные экземпляры списка для каждой рекурсии. Вся ваша проблема заключается в том, что вы не можете отличить экземпляры от ссылок. Задайте/ищите вопрос об этой концепции, чтобы получить ответ.

AmigoJack 18.10.2022 11:13

Я имел в виду, что ваш код имеет «процедуру TForm2.GetFolders;» вверху, без параметров, но рекурсивный вызов GetFolders( Ftp, Tree, NewNode, Path+ sl[i] ); с четырьмя параметрами.

user2834566 18.10.2022 11:39

Нет, это не так. Присмотритесь: точки с запятой нет — определение продолжается в следующих строках. Вы также ставите then begin в несколько строк, а я нет. Нет необходимости запихивать все в одну строку. В качестве альтернативы объясните мне, почему строки 2-6 в моем коде не вызывают вопросов у вас.

AmigoJack 18.10.2022 13:16

Другие вопросы по теме