Поиск и список всех файлов из каталога/подкаталогов в ListBox

Хорошо, я немного решил свою старую проблему, спасибо @sircodesalot

Public Sub AppendFilesFromDirectory(ByVal path As DirectoryInfo, ByVal files As List(Of FileInfo)) For Each file As FileInfo In CurrentFolder.GetFiles() files.Add(file) Next For Each subfolder As DirectoryInfo In CurrentFolder.GetDirectories() AppendFilesFromDirectory(subfolder, files) Next End Sub

Dim files As New List(Of FileInfo) AppendFilesFromDirectory(New DirectoryInfo(FolderBrowserDialog1.SelectedPath), files) For Each file As FileInfo In files ListBox1.Items.Add(file.FullName) Next


И это хорошо работает для папок, в которых есть подпапки, но если в папке есть только файлы, то это становится циклом, постоянно добавляющим файл в myList.

Любые предложения, как этого избежать? @sircodesalot любезно пытался объяснить мне, но я не могу сделать, что бы я ни пытался..

Помощь очень ценится!


person fulmix    schedule 08.09.2014    source источник
comment
Я не думаю, что вы понимаете, насколько огромным будет этот список.   -  person the_lotus    schedule 08.09.2014
comment
Это был пример с C:\, конечно у меня есть переменная с моим путем к списку.   -  person fulmix    schedule 08.09.2014
comment
Обратите внимание, что My.Computer.FileSystem.GetDirectories должно быть пустым, если вложенных папок нет, поэтому рекурсия должна остановиться.   -  person sircodesalot    schedule 08.09.2014
comment
И как вставить в него if и проверить? Пробовал несколько раз, не получается..   -  person fulmix    schedule 08.09.2014
comment
Это выглядит довольно близко к правильному. Что в нем не работает?   -  person sircodesalot    schedule 08.09.2014
comment
Я попытался добавить параметр if , если путь не содержит никакого каталога, используемого только сначала для и пропуска второго, но это не работает ... где я должен поместить оператор if и что я должен точно проверить? Заранее спасибо за ваше время.   -  person fulmix    schedule 08.09.2014
comment
Вам не нужно ничего добавлять, потому что (неявно) «если My.Computer.FileSystem.GetDirectories пусто», рекурсия все равно остановится.   -  person sircodesalot    schedule 08.09.2014
comment
Да, верно. Хотя я тестировал его с папкой, в которой был только один файл, и он постоянно добавлял его в список без остановки. Действительно странно. Использование папки хотя бы с одним подкаталогом работает как надо.   -  person fulmix    schedule 08.09.2014
comment
Обновлено с примером   -  person sircodesalot    schedule 08.09.2014


Ответы (1)


Сначала разберитесь с простой рекурсивной функцией, такой как factorial. Например:

int factorial(int number) {
    if (number < 1)
        return 1;
    else
        return factorial(number - 1) * number;
}

По сути, если мы хотим вычислить factorial(5), то это просто:

 factorial(5) * factorial(4) * factorial (3) * factorial(2) * factorial(1)

или более абстрактно:

 factorial(n) * factorial(n - 1) * factorial (n - 2) * ... * factorial (1)

Поэтому мы заставляем функцию вызывать саму себя с уменьшающимся значением для вычисления результата.

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

(1) List all the files in the current folder
(2) For the files that are directories, repeat step one.

Другими словами:

List<Folder> readAllFiles() {
    List<Folder> folders = new List<Folder>();

    readAllFilesRecursively("C:/", folders);
}

List<Folder> readAllFilesRecursively(String directory, List<Folder> folders) {
     Folder currentDirectory = castTheStringToADirectorySomehow(directory);

     // Add the current folder to the list.
     folders.add(currentDirectory);


     // ** HERE IS WHAT YOU'RE MISSING ** //
     // Re-call the same function on the sub folder, passing in our list
     // so that the subfolder items can be added to the list.
     foreach (Folder subfolder in currentDirectory.subFolders()) {
          readAllFilesRecursively(subFolder.pathString(), folders);
     }

     // Return the folder list.
     return folders;
}

Редактировать:

Так что это работает для меня, но вам, очевидно, придется изменить его на VB. Кроме того, я на Mac, поэтому вы заметите, что формат пути немного отличается:

class Program {
    public static void Main() {

        List<FileInfo> files = new List<FileInfo> ();
        AppendFilesFromDirectory (new DirectoryInfo("/Users/sircodesalot/Desktop/Dev"), files);

        foreach (FileInfo file in files) {
            Console.WriteLine (file.FullName);
        }
    }

    public static void AppendFilesFromDirectory(DirectoryInfo currentFolder, List<FileInfo> files) {

        foreach (FileInfo file in currentFolder.GetFiles()) {
            files.Add(file);
        }

        // This if statement is unneccesary, but I'll add it for clarity 
        // to explain the concept of a base case. We don't really need it though
        // since the 'foreach' statement won't execute if there aren't any items.

        if (currentFolder.GetDirectories().Count() > 0) {

            // Process the subfolders
            foreach (DirectoryInfo subfolder in currentFolder.GetDirectories()) {
                AppendFilesFromDirectory (subfolder, files);
            }
        }
    }
}
person sircodesalot    schedule 08.09.2014
comment
Я забыл сказать, что я делаю это в vb.net, вы можете написать это в vb.net? Очень ценю! Первый пост тоже отредактировал. - person fulmix; 08.09.2014
comment
Да, я это заметил. К сожалению, я действительно не знаю vb.net, но решение в основном такое же. По сути, вам просто нужно написать функцию, которая захватывает все папки в текущем каталоге, а затем снова вызывать эту функцию (из этой функции) для каждой подпапки в этом каталоге. - person sircodesalot; 08.09.2014
comment
В порядке. Думаю, я понял. Но возникает другая проблема... если в каталоге есть только файлы и нет каталогов... это становится циклом при добавлении каждого найденного файла в список... - person fulmix; 08.09.2014
comment
Это часть дзен. Вы вызываете функцию снова, только если один из файлов является каталогом. Как и в приведенной выше факториальной функции, мы останавливаемся, когда достигаем 1, потому что в этот момент больше нечего делать. Если папок больше нет, то мы закончили и позволяем функции выйти (возвращая нас в родительский каталог). - person sircodesalot; 08.09.2014
comment
Другими словами, думайте о функции как о перечислении всех элементов в каталоге. Когда вы встречаетесь с подкаталогом, вы снова вызываете ту же самую функцию, но на этот раз для подкаталога. Если у подкаталога есть подкаталог, вы также делаете это. Когда мы закончим, мы позволим функции выйти, и она вернет нас обратно в родительский каталог, и так далее, пока мы не исчерпаем все файлы во всех каталогах. Поначалу рекурсивное программирование действительно странно, но на самом деле это решение вашей проблемы. - person sircodesalot; 08.09.2014
comment
Да, это действительно сбивает с толку... хотя ты первый, кто так хорошо мне объясняет. Ну, я сделал функцию с двумя: первая проверяет каждый найденный файл, добавляет его в список, вторая для каждого найденного каталога вызывает функцию с найденным подкаталогом. И до сих пор все хорошо, и я понял, почему функция снова вызывается. Но теперь я не знаю, где проверить, нет ли папок внутри for и exit, как вы сказали. Извините, если задаю такие глупые вопросы. - person fulmix; 08.09.2014
comment
Нет, все в порядке, рекурсия требует нескольких попыток, прежде чем вы с ней ознакомитесь. Тем не менее, если вложенных папок нет, ваш второй цикл не должен запускаться - и функция должна просто завершиться нормально. - person sircodesalot; 08.09.2014
comment
Я рекомендую потратить пару минут на реализацию факториала, чтобы вы могли понять основные понятия. В частности, концепция «базового случая», то есть точка, в которой вы прекращаете рекурсию. Для факториала это 1, потому что вы можете вычислить это напрямую. Для вашей ситуации базовым случаем является «нет папок». В основном вы продолжаете вызывать функцию снова и снова, пока не достигнете базового случая (без папок). В рекурсии вы рекурсивно повторяетесь до тех пор, пока не будет выполнен оператор if базового случая. Как только это выражение if выполнено, вы останавливаетесь (т.е. позволяете функции завершиться в обычном режиме). - person sircodesalot; 08.09.2014
comment
Обновленный код. Я получаю сообщение MissingMemberException не может найти GetFiles в типе Integer..? Странный.. - person fulmix; 09.09.2014
comment
stackoverflow.com/questions/17683271/ - person sircodesalot; 09.09.2014
comment
Я немного схитрил и использовал метод LINQ Count в необязательном операторе if. Linq делает много волшебства для компиляции, с которым я знаком в C#, но я не знаю, как это работает в VB.net, так что вы можете вообще игнорировать это. По сути, (опять же, совершенно необязательный) оператор if должен проверить, есть ли какая-либо причина для запуска цикла foreach, чтобы увидеть, есть ли какие-либо подпапки для обработки. - person sircodesalot; 09.09.2014
comment
Кроме того, это хороший опыт программирования для вас :), вы понимаете, что нужно сделать, вам просто нужно побороться с реализацией. - person sircodesalot; 09.09.2014
comment
Моя ошибка в коде.. :) теперь все работает как надо. Большое спасибо за это, действительно помогли мне лучше понять вещи! :) Очень признателен! - person fulmix; 09.09.2014
comment
Нп, сэр, рад слышать! - person sircodesalot; 09.09.2014
comment
Просто у меня возникли сомнения ... если я захочу перечислить все файлы C: \ в ListBox, программа почти развалится ... есть ли способ сделать это быстрее? Я имею в виду, что слышал о многопоточности, но не знаю, если это так. Быстрое теоретическое объяснение было бы здорово, заранее спасибо! - person fulmix; 10.09.2014
comment
Что делает большинство файловых браузеров, так это своевременная загрузка. Должно быть событие, к которому вы можете прикрепиться (особенно если вы используете древовидную структуру или что-то подобное), которое позволяет вам загружать следующий каталог, когда пользователь выбирает узел. В большинстве ситуаций есть лучшие варианты, чем предварительное полное сканирование системы. Тем не менее, вы никогда не должны использовать поток пользовательского интерфейса для длительной обработки. Вы должны использовать фоновый поток (например, BackgroundWorker или Task), а затем использовать Control.Invoke для сортировки (отправки) результатов обратно в основной поток. - person sircodesalot; 10.09.2014
comment
Если вы не знакомы с Invoke, сейчас самое время изучить его (stackoverflow.com/questions/1423446 /thread-control-invoke). В основном элементы управления пользовательского интерфейса поддерживают потоки, поскольку они регистрируют, какой поток их создал, и они проверяют, что только этот поток когда-либо обращался к ним. Чтобы обновить пользовательский интерфейс, вы должны использовать Invoke, чтобы запланировать функцию обратного вызова/события для выполнения фактического обновления экрана в потоке пользовательского интерфейса. Invoke немного сложно понять в первый раз, но это еще одна бесценная техника, которую нужно иметь под рукой. - person sircodesalot; 10.09.2014
comment
О, это действительно интересно, никогда не думал об использовании фонового потока и никогда не знал о том, что Invoke вызывает его в моем текущем потоке. Я изучу это, поскольку это кажется действительно интересным. Еще раз спасибо! Очень признателен :) - person fulmix; 10.09.2014
comment
Итак, с помощью ButtonClick я вызывал Timer1() для запуска AppendFilesFromDirectory().. теперь я создал BackgroundWorker, и на этот раз ButtonClick вызвал RunWorkerAsync(), а затем DoWork() использует Control.Invoke для вызова Timer1().. результат - то же самое.. он почти вылетает при сканировании C:\ - person fulmix; 15.09.2014
comment
Когда вы говорите «сбой», вы имеете в виду, что возникает исключение или оно зависает? - person sircodesalot; 15.09.2014
comment
Нет, я имею в виду, что он зависает из-за слишком большого количества файлов на всем жестком диске :) - person fulmix; 15.09.2014
comment
Ах да, для этого и нужен BackgroundWorker. Но вам не следует обновлять экран для каждого нового добавленного файла, иначе вы ничего не получите, поскольку поток пользовательского интерфейса должен будет обновляться для каждого добавленного нового элемента. (Я предполагаю, что проблема именно в этом). - person sircodesalot; 15.09.2014
comment
Да, но как мне быть дальше? Прости за мою тупость - person fulmix; 15.09.2014
comment
Просто обновляйте его периодически (раз в полсекунды или что-то в этом роде). Кроме того, если вы очищаете и повторно заполняете список или что-то в этом роде, знайте, что это повлечет за собой накладные расходы, особенно если список большой. Если вы заполняете весь жесткий диск пользователя, то для этого действительно нет хорошего решения, потому что в определенный момент список становится слишком длинным, чтобы его можно было эффективно поместить в поле. Поэтому вы можете подумать о том, чтобы просто переосмыслить свой подход. У вас есть умственные способности, необходимые для получения данных, теперь осталось только разработать удобный интерфейс. - person sircodesalot; 15.09.2014
comment
Я думаю, что он никогда не выполнит эту работу, поскольку я хочу отображать все файлы всего жесткого диска в списке как можно быстрее ... поэтому я думаю, что нет ничего, что могло бы его ускорить. - person fulmix; 15.09.2014
comment
Если вы идете по этому пути, вероятно, имеет смысл просто иметь индикатор выполнения, а затем позволить пользователю ждать, пока это будет сделано. Хотя я не могу придумать сценарий, в котором я хотел бы увидеть все файлы на моем жестком диске -- выполнить поиск, может быть, но не увидеть. - person sircodesalot; 15.09.2014
comment
Хе-хе, я понимаю твою мысль. Позвольте мне объяснить вам мою мотивацию: если я хочу сканировать все жесткие диски, чтобы найти, имеет ли файл тот же md5, что и постоянная строка md5, заданная мной, я делаю список всех файлов в ListBox, а затем, если ListBox.Contains() мой Строка md5 (после ее вычисления, конечно) отображает имя найденного файла. Так что это мое представление о том, как я мог бы это сделать ... может быть, есть еще способы сделать это, я не знаю. - person fulmix; 15.09.2014
comment
Если это так, вам следует обновлять список только в том случае, если он соответствует вашему хешу (что должно происходить очень редко). - person sircodesalot; 15.09.2014
comment
да, но сначала я вызываю рекурсивную функцию, чтобы добавить все файлы в список, а затем сравниваю хэш ListBox.SelectedItem() с хэшем, заданным мной. - person fulmix; 16.09.2014
comment
Верно, но подумайте о накладных расходах на отображение чего-либо, а затем на его скрытие. Обе эти вещи требуют дополнительного времени. Гораздо дешевле отфильтровать элемент до того, как он попадет в список. - person sircodesalot; 16.09.2014
comment
Вы имеете в виду фильтр по расширению? Если это так, мне нужно, чтобы все файлы были перечислены в списке, а не только некоторые расширения... - person fulmix; 16.09.2014
comment
Нет, я имею в виду, что если вы хотите показывать только те элементы, которые имеют определенный хэш, тогда вам следует выполнять эту фильтрацию отдельно, а затем отображать этот контент пользователю только тогда, когда вы уверены, что он должен быть представлен. Графика требует очень больших вычислительных ресурсов, поэтому вам следует показывать материал только в том случае, если вы абсолютно уверены, что хотите это показать. - person sircodesalot; 16.09.2014
comment
Я думаю, что я так понимаю, и это должно работать нормально. Хотя я тогда думал, как я мог узнать имя текущего сканируемого файла, с полным именем, добавленным в ListBox, я мог его увидеть... - person fulmix; 16.09.2014