Windows Management Instrumentation - инфраструктура управления операционной системой, страница 10

  if (NodeExists (Получите из параметра файловый путь старой, и уже переименованной папки))

     searchedNode.Text = Path.GetFileName (e.FullPath);

  UpdateStatusBar (Color.LightSkyBlue, "File system changed (Directory renamed)");

}

Метод Invoke

Осталось реализовать реакцию на событие Changed, возбуждаемое наблюдателем, если тип изменения соответствует константе перечисления WatcherChangeTypes.Created. Кажется успех совсем близок, но здесь мне не повезло (пришлось провести часов 10 в режиме отладки). Первым вариантом (который кажется вполне логичным) был такой код:

case WatcherChangeTypes.Created:

if (NodeExists (файловый путь родительской папки))

     searchedNode.Nodes.Add (new TreeNode (Path.GetFileName(e.Name), 10, 11));

  break;

Он все делает правильно, но неожиданно обнаруживается необработанное исключение, которое сообщает, либо о том, что  поток остановился, либо о том, что вызов должен производиться из другого потока с помощью метода Invoke. Помилуйте, какие потоки? Мы не создавали дополнительных потоков. Теперь я жалею, что не сразу стал читать документацию по методу Invoke, которым обладают все классы, производные от Control. Я сэкономил бы часы, проведенные в отладке.

Будучи уверенным, что знаю, как работает метод Invoke интерфейса IDispatch (см. технологию COM), и будучи уверенным, что COM (слава Богу) где-то далеко от того места, где мы сейчас находимся (мы ведь работаем с классами .NET), я думал, что указание на Invoke — это бред компилятора, вызванный каким-то моим крупным промахом. Неужели FileSystemWatcher реализован в технологии COM? Это было бы печально, так как считаю старую COM (с интерфейсом IDispatch) тупиковой и самой унылой ветвью развития Microsoft-технологий. Справка по методу Control.Invoke необычайно богата и красноречива. Вот она вся:

              Executes the specified delegate on the thread that owns the control's underlying window handle.

Все же английский язык в изощренных умах бывает очень емким. Эти слова (все вместе) означают, что:

¨  Существует поток, который владеет ресурсом типа HWND, принадлежащим элементу управления,

¨  Метод Invoke запускает в этом потоке функцию, соответствующую указанному делегатному типу.

Попробуйте на русском выразить это так же коротко, как на английском. Если сможете, покажите мне ваш текст. Как бы там ни было, я не хочу признавать факт наличия еще одного потока, кроме потока, в котором выполняется наше приложение Client, и в адресное пространство которого загружена наша DLL Library. Но ведь меня не спрашивают. С другой стороны ситуация похожа на посылку сообщения самому себе, как в случае с функцией PostMessage).

PostMessage — традиционный трюк Win32-программиста. Он означает: выполни то, что хочешь, но позже, после окончания обработки текущего сообщения и сделай это в функции обратного вызова, то есть реакции на посланное себе же сообщение.

Что же делать? В качестве последнего шанса, я решил вызвать Invoke в своем же потоке, соблюдая описанную тактику. Но для этого надо сначала создать делегатный тип. В вашем приложении он уже имеется, просто я не объяснял для чего он нужен. Приведем его вновь:

public delegate void DirCreatedHandler (TreeNode node);

Хорошо, функция такого типа будет вызвана методом Invoke. Теперь надо создать саму функцию (задание делегату, которого еще нет). Функция должна иметь сигнатуру, соответствующую делегатному типу. В этом месте все без исключения говорят соответствующую делегату, а не делегатному типу. Это упрощение, так как делегат еще не создан. Вставьте функцию внутрь класса ExplorerControl (мы пока еще сидим в этом классе). Внутрь функции вставьте тот код, который не работает (в попытке прямого вызова).

private void OnDirCreated (TreeNode node)

{

  searchedNode.Nodes.Add (node);

}

Наконец, надо вызвать метод Invoke в том самом месте, где мы потерпели неудачу. При этом надо попутно создать делегата и зарядить его заданием — адресом функции OnDirCreated. Головоломно, не правда-ли? Но зато все работает. Чтобы избежать путаницы, приведем полную версию функции dirWatcher_Changed.

private void dirWatcher_Changed (object sender, FileSystemEventArgs e)

{

  string msg = "";

  switch (e.ChangeType)

  {

  case WatcherChangeTypes.Created:

     string parent = e.FullPath.ToLower();

     parent = parent.Substring(0, parent.LastIndexOf('\\')); // Вычленяем родительский узел

     if (NodeExists (parent))

       Invoke (new DirCreatedHandler (OnDirCreated),

         new object[] { new TreeNode (Path.GetFileName(e.Name), 10, 11) } );

     msg = e.Name + " created)";

     break;

  case WatcherChangeTypes.Deleted:

     if (NodeExists (e.FullPath.ToLower()))

       searchedNode.Remove();

     msg = e.Name + " deleted)";

     break;

  }

  UpdateStatusBar (Color.LightSkyBlue, "File system changed (Directory " + msg);

}

Имя родительского узла приходится выявлять без помощи класса Path, так как результат работы последнего, к сожалению, зависит от местоположения папки. Некоторые пояснения особенностей вызова Invoke можно найти в MSDN. Например, массив объектов, который создается по ходу дела, позволяет создать делегатный тип с произвольным числом параметров. Мы используем лишь один параметр: имя вновь созданной папки, а могли бы передать и два и три и т.д.

Запустите и проверьте. С помощью обычного Explorer'а создайте папку в какой-либо директории, и таскайте ее при нажатой клавише Ctrl. Затем удалите некоторые копии, или (нажав F2) переименуйте. Дерево должно послушно отображать все перепитии событий, происходящих в файловой системе. Недостаток — мы способны следить только за тем диском, в котором расположена папка, раскрытая в дереве последней. Если исправите этот недостаток (без помощи новых наблюдателей), то сообщите мне.

Список файлов

Прежде, чем задействовать второго наблюдателя fileWatcher, необходимо вычислить список файлов, расположенных в папке, выбранной (selected) в дереве. Это традиционно делается в обработчике события, возбуждаемого в момент выбора папки. В MFC это было уведомление типа =TVN_SELCHANGED, а в .NET — это событие AfterSelect (сравнение показывает насколько реалии .NET ближе к нормальному человеческому языку). Также поступим и мы. Заготовка функции уже есть, осталось вставить в нее код.