if (NodeExists (Получите из параметра файловый путь старой, и уже переименованной папки))
searchedNode.Text = Path.GetFileName (e.FullPath);
UpdateStatusBar (Color.LightSkyBlue, "File system changed (Directory renamed)");
}
Осталось реализовать реакцию на событие 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 ближе к нормальному человеческому языку). Также поступим и мы. Заготовка функции уже есть, осталось вставить в нее код.
Уважаемый посетитель!
Чтобы распечатать файл, скачайте его (в формате Word).
Ссылка на скачивание - внизу страницы.