Сначала я написал рекурсивную функцию анализа и заполнения всего файлового дерева при начальном запуске приложения. Оказалось, что эта процедура занимает 5-7 минут, в течение которых приложение выглядит мертвым. Правда, после этого дерево раскрывает свои ветви мгновенно, потому что оно имеет информацию о всех своих ветвях. Если логических дисков и папок много (а в сетевой системе их очень много), то программа будет молчать очень долго. Кроме того, дерево потребует недопустимо много памяти.
Обычно используют другие варианты работы с большими деревьями. Один из них состоит в том, что при проверке объекта файловой системы (диска или папки) он не сканируется далеко вглубь. Вместо этого заполняется лишь один уровень вложенности узлов (тот, который еще не виден пользователю). Если этого не сделать, то закрытый узел не будет иметь маркера раскрытия. Затем, когда пользователь действительно раскроет узел (это событие надо обработать), следует продвинуться вглубь дерева еще на один уровень вложенности. Раскрываемая же на данном шаге ветвь, уже наполнена реальными сущностями, так как это было выполнено на предыдущем шаге движения вглубь дерева.
Такой прием обеспечивает постепенное наполнение дерева по сценарию, определяемому пользователем. Вновь раскрываемые ветви вносят некоторую задержку, но после схлопывания (collapse) какой-либо ветви ее повторное раскрытие происходит быстро, так как информация уже записана в дереве (объекте tree класса TreeView). Альтернативным вариантом решения проблемы считается параллельное сканирование файлового дерева в отдельном потоке и постепенное заполнение его найденными узлами.
Кроме демонстрации дерева и списка файлов, мы собираемся наблюдать за файловой системой, реагировать на изменения в ней и эффективно отображать их в дереве. Кроме того, мы собираемся оповещать приложение Client об изменениях в состоянии дерева (а позже и других компонентов) с тем, чтобы оно смогло отобразить сообщения в строке состояния (info).
Учитывая сказанное, введем в класс ExplorerControl два новых компонента типа FileSystemWatcher. Один (с именем fileWatcher) будет следить за файлами, другой (с именем dirWatcher) — за папками. Видимо, можно обойтись и одним компонентом, но мне показалось это неудобным. Вы можете поступить по-другому.
Здесь следует сделать замечание. Компоненты можно вводить с помощью дизайнера (тогда код их инициализации спрятан внутрь InitializeComponents), а можно и вручную (тогда мы пишем код сами). Довольно часто я предпочитаю второй способ, так как дизайнер своими установками по умолчанию и длинными, нечитаемыми выражениями постоянно меня подводит. Вы можете выбрать другой путь, но мой долг предупредить, что дважды пытаясь добится результата с помощью дизайнера, оба раза я терпел неудачу. Дело в том, что FileSystemWatcher требует тонкой настройки свойства NotifyFilter. Дизайнер же создает очень неуклюжее логическое выражение, которое не работает. Я предпочитаю видеть все выражения.
Несмотря на замечание, приведу перечень действий, которые надо выполнить, если вы решите воспользоваться услугами дизайнера.
¨ Перейдите в режим дизайна и добавьте два новых компонента типа FileSystemWatcher с именами: fileWatcher и dirWatcher. Установите им следующие свойства:
¨ EnableRaisingEvents в положение False (для обоих компенентов), // События пока выключены
¨ Filter = "" — пустая строка (для обоих компенентов), // Означает, что мы следим за всеми типами папок
¨ NotifyFilter = DirectoryName (только для компенента dirWatcher), // Следим только за папками
¨ IncludeSubdirectories в True (только для компенента dirWatcher), // Следим за вложенными папками тоже
¨ NotifyFilter = FileName, Size, LastWrite, CreationTime (только для компенента fileWatcher),
Далее, вручную введите в класс ExplorerControl множество новых переменных, смысл которых пояснен комментариями.
private FileSystemWatcher fileWatcher, dirWatcher; // Вставьте эту строку, если идете моим путем
private TreeNode root, searchedNode; // Корневой и искомый узлы
private SortedList expandedNodes; // Коллекция раскрытых узлов
private string searchPath, rootName; // Искомый файловый путь и имя корневого узла
private int rootLength; // Длина имени корневого узла
public event StatusHandler UpdateStatus; // Возбуждаемое нами событие "Изменения состояния",
public delegate void StatusHandler (Color clr, string msg); // Делегат события UpdateStatus
public delegate void DirCreatedHandler (TreeNode node); // Делегат события "Создания папки"
private ListViewItem curItem; // Текущий узел
private MyListViewComparer listComparer; // Объект вспомогательного класса
public static string dateSep = " "; // Разделитель даты и времени
public bool bHide; // Флаг состояния "Показывать скрытые папки"
Добавьте объявление API-функции, которую мы используем для определения типов логических дисков, присутствующих в системе. Здесь используется атрибут, который работает, благодаря InteropServices.
[DllImport("Kernel32")]
public static extern int GetDriveType (string lpRootPathName);
Теперь войдите внутрь конструктора и вставьте коды инициализации используемых объектов.
bHide = true; // Скрытые папки пока не показываем
list.Columns.Add ("Name", 150, HorizontalAlignment.Left); // Создаем колонки в списке файлов
list.Columns.Add ("Size", 75, HorizontalAlignment.Right);
list.Columns.Add ("Created", 140, HorizontalAlignment.Center);
list.Columns.Add ("Modified", 140, HorizontalAlignment.Center);
list.Sorting = SortOrder.Ascending; // Настраиваем способ сортировки списка файлов
list.ListViewItemSorter = listComparer = new MyListViewComparer();
list.ListViewItemSorter = listComparer;
expandedNodes = new SortedList();
rootName = "My Computer";
rootLength = rootName.Length + 1;
searchPath = "";
Уважаемый посетитель!
Чтобы распечатать файл, скачайте его (в формате Word).
Ссылка на скачивание - внизу страницы.