Приложение Монитор каталогов
|
Сидит мужик за компом. Вдруг разбивает окно камень обернутый бумажкой, пролетает по комнате, попадает в монитор, монитор вдребезги. Мужик берет камень разворачивает бумажку, видит объявление:
"Продам новый монитор!"
|
Часть 1. (создаем класс – потомок TThread)
1. Введение.
Уважаемые программисты! Для начала хочу выразить искреннюю благодарность
создателю замечательного сайта в интернете “Мастера DELPHI” (http://delphi.mastak.ru/), конференции для
программистов - Алексею (Merlin). Я думаю, что со мной согласятся многие,
если скажу, что конференции помогли и помогают многим программистам в их
нелегком труде. До сих пор не встречал такого живого форума и такой
атмосферы взаимопомощи на просторах интернета. Конференции стали неплохим
подспорьем программистам в их деятельности. Постоянно посещая форум, я
заметил, что некоторые вопросы довольно часто повторяются. Желание помочь
коллегам и позволило появиться в свет этой статье. Надеюсь, что некоторые
приемы, описанные ниже, будут полезны и профессионалам. Версии Windows ниже
98 безвозвратно уходят в прошлое, поэтому особенности программирования для них
мы рассматривать не будем. Так как некоторые приемы, описанные в статье, в
разных версиях Windows могут существенно отличаться, в некоторых случаях мы
рассмотрим альтернативные решения. Здесь я не касаюсь особенностей
программирования в Delphi6, так как до сих пор у меня не было возможности
опробовать эту систему.В первой статье цикла хочу на примере программы
«Монитор каталогов» показать приемы работы с файлами (поиск/чтение/запись) и
потоками (TThread). Эта статья ни в коем случае не претендует на
исключительность и «истину в последней инстанции». В статье я не буду
придерживаться предельной точности в определении терминов, а сделаю упор на
«понятность» изложения материала. Как мне кажется, для начинающих программистов,
более важно ухватить сначала суть, и лишь затем углубляться во внутренности
процессов, проходящих внутри Windows. Я буду благодарен всем, кто пришлет
отзывы, замечания и пожелания по материалам статьи, сообщения о найденных в ней
ошибках, а также алгоритмы и другие реализации решений, отличные от тех, что
описаны на этих страницах по адресу panov@hotbox.ru. Благодарю за ценные замечания
и предложения при подготовке статьи:
При подготовке к статье использовались следующие источники:
А теперь приступим к практическим занятиям.
Приложение «Монитор каталогов»
2. Создаем основную форму и наводим на
ней красоту.
Давайте вместе создадим программу,
которая будет непрерывно проверять каталог
на предмет изменения содержимого (появление
нового файла, удаление файла,
переименование и изменение файла).
Добавим в нашу программу возможность
ведения протокола изменений в проверяемом
каталоге и функции динамической настройки
этого списка.
После написания программы добавим еще одно
“ удобное” свойство – спрячем
программу в SysTray (для тех, кто не знает – это
место на панели задач справа рядом с часами).
Введем также еще условие: программа не
должна блокировать работу с основной
формой. Для этого будем использовать
класс TThread.
Здесь я сделаю небольшое отступление, и
кратко расскажу, что такое «Процесс», «поток»,
и зачем они нужны.
Рекомендую также почитать статью Подмогова
Михаила, расположенную по адресу (http://delphi.mastak.ru/articles/winexec/index.html)
и статью Николая
Кариха (http://delphi.mastak.ru/articles/thread/index.html)
Процессом в Windows называется любое
выполняющееся приложение. Каждый процесс
имеет свой уникальный идентификатор –
целое число, назначаемое операционной
системой при создании процесса.
Поток – это последовательность машинных
команд, которые выполняются процессором (часто
потоки называют «нитями»).
При запуске программы в Windows создается
процесс, в котором описано, чем владеет этот
процесс, и первичный или основной (главный)
поток, в котором начинается выполнение
программы.
Важное свойство процесса – для него может
быть создано несколько потоков,
причем выполнение каждого потока может, как
зависеть, так и не зависеть от выполнения
других потоков.
Для однопроцессорных систем справедливо
также следующее:
- в каждый момент времени выполняется,
только один поток, поскольку для выполнения
любой команды требуется занять время
процессора. Но операционная система
выделяет каждому потоку по очереди
промежуток времени (называется он «квант
времени») для выполнения, причем этот квант
времени очень мал. Поэтому создается
впечатление, что все задачи в системе
выполняются одновременно.
Учитывая это, будет естественно применить
возможности потоков в нашей программе.
В Delphi работы с потоками предусмотрен
специальный класс – TThread, о методах и
свойствах которого я расскажу немного
позже.
Теперь приступим к созданию приложения.
2.1. Начало.
Откроем новый проект, поменяем имя формы c
Form1 на fMonDirMain, заголовок формы на «Сканер
каталогов».
Сохраним проект в отдельном каталоге, при
сохранении назовем Unit с формой ufMain, а проект
– MonDir.
Устанавливаем свойства для формы fMonDirMain:
Width – 600
Height – 400
Position - poScreenCenter
Далее добавим на форму следующие объекты:
TMainMenu (имя mm),
TStatusBar (имя sbMain),
TTimer (имя tmDate),
TListBox (имя lbLog).
Выбрав sbMain, в инспекторе объектов
отредактируем список панелей (Panels).
Добавим в список 3 панели.
для первой панели выбираем ширину (поле Width)
110,
для второй панели выбираем ширину 150.
Панели будем использовать для
отображения текущего времени и состояния
сканера («Активен»/«Отключен»).
Для lbLog устанавливаем свойство Align в alClient.
Для tmTimer определяем процедуру обработки
события OnTimer
procedure TfMonDirMain.tmDateTimer(Sender: TObject);
begin
// На первой панели отображаем текущие дату и время
sbMain.Panels[0].Text := FormatDateTime('dd.mm.yyyy hh.nn.ss',now);
end;
|
и устанавливаем свойство Interval в 1000 мс (1 секунда), а Enabled в True.
Для формы создадим процедуру обработки события OnCreate:
procedure TfMonDirMain.FormCreate(Sender: TObject);
begin
// Обновляем панели статуса.
tmDateTimer(Self);
sbMain.Panels[1].Text := ‘Отключен’;
// Здесь добавим заголовок для нашего приложения.
Application.Title := 'Монитор каталогов';
// Такой заголовок будет у свернутого на панель задач (TaskBar) нашего приложения.
// На самом деле заголовок приложения можно указать сразу при разработке приложения.
// Для этого откройте в меню DelphiaProjectaOptions и на закладке Application
// исправьте поле Title
end;
|
Обратите внимание на строку tmDateTimer(Self).
Процедуру обработки события OnTimer при
создании формы мы вызываем для того,
чтобы на панели статуса сразу же
отобразились бы текущие дата и время. Если
этот код не выполнить при создании формы, то
при ее появлении на экране дата и время
начнут отображаться только через одну
секунду.
Дважды кликнув на значок TMainMenu,
отредактируем наше меню.
Добавим 2 пункта: “Монитор” и “Выход”:
“Выход” – для освобождения ресурсов и
выхода из программы.
В пункт меню «Монитор» добавим:
“Старт” – запуск потока для сканирования.
“Стоп” – остановка потока.
Назовем созданные пункты меню:
для “Старт” – mmStart,
для “Стоп” – mmStop,
для “Выход” – mmExit;
Свойство пункта Enabled для меню “Стоп”
установим в False.
Создадим для меню “Выход” процедуру –
обработчик, в коде напишем:
procedure TfMonDirMain.mmExitClick(Sender: TObject);
begin
Close;
end;
|
“Каркас” нашей программы готов.
2.2. Создание класса для обработки изменений каталога.
В Delphi работа с потоками инкапсулирована в класс TThread, с помощью которого и ведется вся работа в потоках.
Предлагаю Вашему вниманию краткий обзор свойств и методов класса TThread.
Свойство/ Метод/ Событие |
Тип |
Описание |
Значения |
FreeOnTerminate |
Boolean |
Свойство, определяющее, вызывается ли процедура уничтожения потока
(destructor) автоматически по окончании выполнения процедуры
Execute, или нет. |
True: деструктор потока вызывается автоматически.
False: для вызова деструктора и уничтожения потока необходимо
воспользоваться методом Terminate. |
Handle, ThreadId |
THandle |
Идентификатор потока, используется различными функциями Windows
API. |
Целое число. Назначается операционной системой. |
Priority
|
TThreadPriority |
Приоритет потока. Используется для указания, насколько выше/ниже
приоритет потока относительно основного (главного) потока. |
tpIdle – поток работает в то время, когда процессор не занят
выполнением других задач. (Наинизший приоритет из возможных)
tpLowest
-
поток имеет приоритет на 2 пункта ниже нормального tpNormal. (Очень
низкий приоритет) tpLower - поток имеет приоритет на 1 пункт
ниже нормального tpNormal. (Низкий приоритет) tpNormal
– приоритет по умолчанию. Используемый большинством потоков
операционной системы. (Нормальный приоритет) tpHigher -
поток имеет приоритет на 1 пункт выше нормального tpNormal.
(Высокий приоритет) tpHighest - поток имеет приоритет на 2
пункта выше нормального tpNormal. (Очень высокий приоритет)
tpTimeCritical – поток работает в режиме реального времени.
Необходимо пользоваться с большой осторожностью, так как поток займет
все время процессора. (Наивысший приоритет из возможных) |
ReturnValue |
Integer |
Значение, которое возвращает поток при своем завершении. |
Значение ReturnValue можно получить при использованиии метода
WaitFor. |
Suspended |
Boolean |
Свойство, показывающее, в каком состоянии находится поток. Поток
может находиться в двух состояниях: - активный -
приостановлен В приостановленном состоянии поток не выполняет
никакой работы. Для завершения потока, который находится в состоянии
«приостановлен» необходимо сначала выдать для него команду
Resume |
True - поток находится в приостановленном состоянии
False – поток находится в активном состоянии |
Terminated |
Boolean |
Свойство показывает, получена ли потоком команда на завершение
Terminate. |
True – получена команда Terminate завершить работу.
False – команда Terminate не получена. |
OnTerminate |
событие |
Событие, возникающее после окончания работы процедуры
Execute, но до вызова деструктора потока. |
|
Create |
Метод |
Конструктор потока. Для создания потока и передачи ему
параметров нужно переопределить конструктор. Пример см. дальше в
статье. |
|
Destroy |
Метод |
Деструктор потока. Вызывается при завершении выполнения потока
автоматически. Примечание: Destroy будет вызван
автоматически если FreeOnTerminate = True и
Suspended = False |
|
DoTerminate |
Метод |
Генерирует событие OnTerminate. Выполняется код из
процедуры-обработчика события OnTerminate, но не вызывает
завершения потока. |
|
Execute |
Метод |
Выполняется после создания потока конструктором Create.
Начинает работу немедленно после Create, если поток создан с
опцией CreateSuspended = False |
|
Resume |
Метод |
Заставляет приостановленный поток продолжить работу. |
|
Suspend |
Метод |
Приостанавливает работу потока. |
|
Synchronize |
Метод |
Выполняет процедуру, определенную пользователем как метод потока.
Используется для выполнения кода процедуры, определенной пользователем,
в основном потоке. |
|
Terminate |
Метод |
Вызывается для завершения потока. |
|
WaitFor |
Метод |
Используется для получения значения, которое возвращает поток после
своего завершения. Вызывается в основном потоке. |
|
|
|
|
|
Поясню некоторые методы и свойства подробнее.
- Create
- Как правило, поток создается для выполнения каких-либо однотипных операций, но с разными начальными условиями (параметрами), поэтому конструктор потока необходимо переопределить для своих нужд.
Например:
type
MyThread := class(TThread)
private
FFirstPar: String;
FNextPar: String;
FCounter: Integer;
...
public
constructor Create(const FirstParameter, NextParameter: String);
end;
|
А при реализации конструктора можно использовать эти параметры:
constructor Create(const FirstParameter, NextParameter: String);
begin
inherited Create(True); // Вызов конструктора родительского класса
FFirstPar := FirstParameter; // Инициализация параметров
FNextPar := NextParameter;
FCounter := 0;
Resume; // Переводим поток в состояние «Активный»
// Далее в процедуре Execute используем эти полученные параметры.
end;
|
Обратите внимание на выделенные строки:
В этой строке вызывается конструктор родительского класса TThread.
Внимание! После выполнения этой строки немедленно начнет выполняться метод Execute, если поток создать с флагом CreateSuspended = False. Поэтому, если Вы хотите инициализировать здесь переменные, которые будут использоваться в Execute, используйте конструкцию, приведенную выше.
- Execute.
- В Execute должен выполняться весь код, который должен выполняться «параллельно» основному потоку. Как правило, если поток предназначен для выполнения многократно повторяющегося кода (цикла), то в Execute используется конструкция следующего вида:
while not Terminated do
begin
// В этом месте выполняется весь необходимый код.
...
// Если здесь необходимо обновлять некоторые данные в основном потоке,
// то здесь используйте метод Synchronize.
Synchronize(MyUpdateProcedure);
// Процедуру MyUpdateProcedure определяем в описании нашего класса.
end;
|
- Synchronize
- Так как процедура, вызываемая в методе Synchronize выполняется в основном потоке, то на время выполнения Synchronize главная форма будет блокирована для ввода, обновления информации, как это происходит при длительных операциях в основном потоке.
Поэтому этот метод надо вызывать по возможности реже.
Простейший пример таймера:
procedure MyThread.MyUpdateProcedure;
begin
FormMain.LabelCounter.Caption := IntToStr(FCounter);
end;
procedure MyThread.Execute;
begin
while not Terminated do
begin
Inc(FCounter);
Synchronize(MyUpdateProcedure);
Sleep(1000);
end;
end;
|
Если Вам необходимо из потока обновлять переменные и объекты, которые используются вне этого потока, всегда пользуйтесь методом Synchronize. Результат обновления прямо из Execute или других процедур, определенных в потоке, может быть непредсказуемым, вплоть до возникновения исключительной ситуации(Exception)
Теперь вернемся к разработке нашего приложения.
В меню Delphi File выберем NewàThread Object и введем имя класса TMonDirThread.
Сохраним новый модуль с именем uMonThread.pas.
Исходный вид модуля uMonThread.pas приведен ниже:
unit uMonThread;
interface
uses
Classes;
type
TMonDirThread = class(TThread)
private
{ Private declarations }
protected
procedure Execute; override;
end;
implementation
{ Important: Methods and properties of objects in VCL can only be used in a
method called using Synchronize, for example,
Synchronize(UpdateCaption);
and UpdateCaption could look like,
procedure TMonDirThread.UpdateCaption;
begin
Form1.Caption := 'Updated in a thread';
end; }
{ TMonDirThread }
procedure TMonDirThread.Execute;
begin
{ Place thread code here }
end;
end.
|
При написании кода будем исходить из того,
что наш поток должен:
- Начинать работать сразу после создания.
- Извещать основную форму об изменениях в
сканируемом каталоге.
- Прекращать работу по «инициативе»
основного потока.
2.3. Создание конструктора потока.
Добавим в определение нашего класса
секцию public и напишем код:
public
constructor TMonDirThread.Create(aPath: String);
destructor Destroy; override;
|
В секцию implementation добавим код нашего конструктора:
constructor TMonDirThread.Create(aPath: String);
begin
inherited Create(True); // Поток создаем в состоянии «Приостановлен»
FreeOnTerminate := True; // Поток освободит ресурсы при окончании работы
FPath := aPath; // Проверяемый каталог
Self.Priority := tpHighest; // Стартуем с высоким приоритетом
Resume; // Переводим поток в состояние «Активен»
end;
|
Подробнее о параметре Self.Priority := tpHighest;
Для нашего потока устанавливаем один из
самых высоких приоритетов в системе для
того, чтобы поток успевал обрабатывать
изменения в каталоге.
Т.к. в потоке не используется ресурсоемких
конструкций, то для каждого цикла обработки
в процедуре Execute (напишем этот код ниже)
будет затрачиваться минимальное
процессорное время.
Переходим к кодированию основной
процедуры нашего потока – Execute.
2.4. Создание процедуры Execute.
Для проверки изменений в Windows есть
специальные функции:
- FindFirstChangeNotification/FindNextChangeNotification/FindCloseChangeNotification;
- ReadDirectoryChangesW (только в Windows NT и более
поздних версиях)
- FindFirst/FindNext/FindClose;
Самой удобной для наших целей является
функция ReadDirectoryChangesW, так как, используя
ее, можно сразу определить, какой тип
изменений произошел в каталоге (создание/удаление/модификация
файла) и узнать имя файла, который вызвал
изменение.
Но, к великому моему сожалению, эта функция
не работает в версиях Windows9x.
При использовании этих функций (Find___ChangeNotification)
мы не сможем определять имя файла,
вызвавшего изменения, но узнаем, какое
изменение произошло в каталоге, и, что не
менее важно, они работают во всех версиях
Windows.
Мы воспользуемся для сканирования
функциями Find___ChangeNotification.
К сожалению, мы не сможем использовать оба
этих метода для сканирования каталогов,
находящихся на сетевых дисках, так как
отслеживание изменений производится на
уровне операционной системы, и эти функции
лишь используют заложенные в Windows
возможности.
Метод с использованием функций FindFirst/FindNext/FindClose
будет работать во всех версиях Windows и может
обеспечить сканирование сетевых каталогов,
но у него есть один существенный недостаток
– он довольно медлителен в сравнении с
первым и вторым методами, а также требует
для работы значительно больше ресурсов.
Третий метод я опишу во второй части
статьи.
Для понимания следующего материала
необходимо пояснить еще, что такое «объекты
синхронизации» Windows.
Работа Windows как операционной системы была
бы невозможна без механизма синхронизации
потоков в системе. Для решения этих задач в
Windows существуют так называемые «объекты синхронизации».
«Объектом синхронизации» может
выступать любой объект ядра Windows.
Перечислю некоторые из них: Event, Mutex, Semaphore,
Timer.
В их число также входит и объект, который
будем использовать мы - ”find change notification object".
Далее я буду называть его “Объект”.
Все такие объекты имеют Handle – дескриптор (тип
Integer), Handle назначается объекту при создании
операционной системой и используется для
идентификации в различных процедурах и
функциях Windows.
В приложениях эти объекты используется
для извещения потоков о каком-либо
происшедшем событии в системе.
Все подобные объекты имеют свойство «состояние»
- «включен/выключен».
В состоянии «Включен» объект сигнализирует
о том, что некоторое событие произошло.
«Выключен» означает, что объект ожидает,
пока произойдет некоторое событие.
Для проверки состояния объектов
используется несколько похожих функций. В
нашем приложении мы будем использовать
функцию WaitForSingleObject.
Рассмотрим подробнее функции
FindFirstChangeNotification
FindNextChangeNotification
FindCloseChangeNotification
WaitForSingleObject
HANDLE FindFirstChangeNotification
Создает
сигнализирующий объект find change notification object
для сканирования каталога.
Параметры: LPCTSTR lpPathName,
- Указатель на строку, которая
содержит путь к проверяемому каталогу.
BOOL bWatchSubtree, -
Флаг, указывающий, нужно ли
проверять подкаталоги.
DWORD dwNotifyFilter
- О каких изменениях в каталоге
сигнализировать.
dwNotifyFilter
Определяет, о
каких типах изменений будет
сигнализировать объект
Возможные значения:
FILE_NOTIFY_CHANGE_FILE_NAME
Сигнализировать
о создании, удалении и переименовании файла.
FILE_NOTIFY_CHANGE_DIR_NAME
Сигнализировать о создании, удалении,
переименовании подкаталогов.
FILE_NOTIFY_CHANGE_ATTRIBUTES
Сигнализировать об изменении атрибутов
файлов или подкаталогов.
FILE_NOTIFY_CHANGE_SIZE
Сигнализировать об
изменении размеров файлов. Операционная
система определяет, что размер файла
изменился, только если он закрыт.
FILE_NOTIFY_CHANGE_LAST_WRITE
Сигнализировать о
смене даты последнего изменения файла.
Операционная система определяет, что дата
изменения файла сменилась, только если он
закрыт.
FILE_NOTIFY_CHANGE_SECURITY
Сигнализировать об
изменениях дескриптора защиты.
В случае успешного выполнения функция
возвращает Handle созданного
сигнализирующего объекта.
BOOL FindNextChangeNotification
Функция запрашивает операционную систему,
изменилось ли состояние сигнализирующего
объекта.
Параметры:
HANDLE hChangeHandle // Handle сигнализирующего
объекта
BOOL FindCloseChangeNotification
Удаляет
сигнализирующий объект.
DWORD WaitForSingleObject
Функция
завершается в том случае, если при проверке
состояния сигнализирующего объекта
происходит одно из событий:
· Состояние
объекта изменилось на «Включен»
· Время ожидания,
определенного при вызове функции, истекло.
Параметры:
HANDLE hHandle,
// Handle сигнализирующего объекта
DWORD dwMilliseconds
// Время ожидания в миллисекундах
dwMilliseconds
Может принимать следующие значения:
- время ожидания в миллисекундах
- INFINITE, в этом случае время ожидания не
ограничено.
Функция WaitForSingleObject возвращает
следующие значения:
WAIT_OBJECT_0 –
Состояние объекта изменилось на «Включен»
WAIT_TIMEOUT -
Истекло время ожидания.
Если при выполнении функции возникли
ошибки, WaitForSingleObject возвращает код WAIT_FAILED.
Если состояние объекта изменяется до
истечения времени dwMilliseconds, то функция
завершает работу немедленно с кодом WAIT_OBJECT_0.
-------------
Примечание: При возникновении ошибки во
время выполнения этих функций
код ошибки можно получить, вызвав функцию GetLastError.
-------------
За более подробной информацией по этим и
другим функциям обращайтесь к
Windows
SDK.
-------------
Для синхронизации (обновления) данных
формы будем использовать три процедуры – UpdateLog,
ThreadStart и ThreadStop которые будут
использоваться в методе Synchronize.
procedure TMonDirThread.UpdateLog;
begin
fMonDirMain.lbLog.Items.Add(TimeToStr(time)+': изменение!');
if fMonDirMain.lbLog.Items.Count > 200 then // Показываем только 200 записей
fMonDirMain.lbLog.Items.Delete(0);
fMonDirMain.lbLog.ItemIndex := fMonDirMain.lbLog.Items.Count-1;
end;
procedure TMonDirThread.ThreadStart;
begin
fMonDirMain.sbMain.Panels[1].Text := 'Активен';
fMonDirMain.lbLog.Items.Add(TimeToStr(time)+': монитор запущен');
end;
procedure TMonDirThread.ThreadStop;
begin
fMonDirMain.sbMain.Panels[1].Text := 'Отключен';
fMonDirMain.lbLog.Items.Add(TimeToStr(time)+': монитор остановлен');
fMonDirMain.mmStart.Enabled := True; // Отключаем кнопку mmStart
fMonDirMain.mmStop.Enabled := False; // Включаем кнопку mmStop
end;
|
Далее опишу алгоритм работы процедуры Execute
Для начала сканирования каталога вызовем функцию
FindFirstChangeNotification, которая создаст сигнализирующий объект:
var
HandleChange: THandle; // Handle создаваемого объекта
begin
HandleChange :=
FindFirstChangeNotification(
PChar(FPath), // Проверяемый каталог
False, // Подкаталоги не проверяются
FILE_NOTIFY_CHANGE_FILE_NAME + // флаг изменений
FILE_NOTIFY_CHANGE_ATTRIBUTES );
|
В случае ошибки создания объекта, выдадим сообщение об ошибке и законцим
процедуру Execute:
Win32Check(HandleChange <> INVALID_HANDLE_VALUE);
|
При успешном создании синхронизирующего объекта вызовем процедуру ThreadStart
для обновления главной формы:
Synchronize(ThreadStart); // Сообщение о старте потока
|
Далее, в цикле продолжаем проверку изменений содержимого каталога. В
секцию реализации(implementation) добавляем процедуру
procedure TMonDirThread.Execute;
var
HandleChange: THandle; // Handle создаваемого объекта для ожидания события
begin
// -- Создаем объект для ожидания события
HandleChange :=
FindFirstChangeNotification(
PChar(FPath), // Проверяемый каталог
False, // Подкаталоги не проверяются
FILE_NOTIFY_CHANGE_FILE_NAME + // Проверка создания/удаления/
FILE_NOTIFY_CHANGE_ATTRIBUTES); // переименования/изменения файлов
// -- При ошибке Win32Check выводит сообщение и прерывает Execute.
Win32Check(HandleChange <> INVALID_HANDLE_VALUE);
Synchronize(ThreadStart); // Сообщение о старте потока
try
// -- Цикл, пока для потока не будет выдана команда Terminate
while not Terminated do
begin
case WaitForSingleObject(HandleChange,1000) of
WAIT_FAILED: Terminate; // Ошибка, завершаем поток
WAIT_OBJECT_0: Synchronize(UpdateLog); // Сообщаем об изменении
end;
FindNextChangeNotification(HandleChange);
end;
finally
FindCloseChangeNotification(HandleChange);
end;
Synchronize(ThreadStop); // Сообщаем о завершении потока
end;
|
Не забудьте добавить в секцию interface ссылку на следующие модули:
uses
Classes, Windows, SysUtils;
|
2.5. Последний штрих.
В обработчики пунктов меню «Старт» и «Стоп» добавим следующие строки:
procedure TfMonDirMain.mmStartClick(Sender: TObject);
begin
fMonDirMain.Tag := Integer(TMonDirThread.Create('c:\temp'));
mmStart.Enabled := False; // Отключаем кнопку mmStart
mmStop.Enabled := True; // Включаем кнопку mmStop
end;
procedure TfMonDirMain.mmStopClick(Sender: TObject);
begin
if Assigned(TMonDirThread(fMonDirMain.Tag)) then
TMonDirThread(fMonDirMain.Tag).Terminate;
fMonDirMain.Tag := 0;
end;
|
Обратите внимание на строки, выделенные
жирным шрифтом.
Вместо того, чтобы создавать дополнительно
переменную типа TMonDirThread, мы присвоили
свойству формы Tag ссылку на созданный
объект-поток, воспользовавшись явным
приведением типа Integer(), так как любой
указатель можно преобразовать к типу Integer.
При создании потока (TMonDirThread.Create('c:\temp')) мы
передаем в качестве параметра имя
проверяемого каталога.
Теперь у нас все готово для первого старта
нашего приложения.
Подведем итоги.
Мы написали простейшее приложение,
имеющие следующие возможности:
- Проверять каталог на предмет добавления,
удаления, переименования и изменения
файлов в каталоге, используя для этого
отдельный поток.
- Уведомлять основной поток(главную форму)
об изменениях.
- Вести простейший протокол своей работы.
Для сканирования нескольких каталогов
параллельно в нашем приложении нужно
просто стартовать несколько потоков с
необходимыми параметрами.
Для практики предлагаю самостоятельно
реализовать возможность сканирования
нескольких каталогов, а также изменить
реализацию потока так, чтобы он извещал
основной поток о каждом типе изменений
в отдельности.
Каких возможностей не имеет приложение?
- Сканировать сетевые диски.
- Определять имя изменившегося файла.
- Вести протокол работы в некотором файле.
- Сворачиваться в виде иконки в SysTray.
Все эти возможности мы реализуем во
второй части статьи.
Хочу также привести реализацию потока,
предложенную Юрием Зотовым:
unit ThreadUnit;
interface
uses
Windows, Classes;
type
TDirMonThread = class(TThread)
private
FNotifier: THandle;
procedure UpdateLog;
protected
procedure Execute; override;
public
// Поле FNotifier должно быть недоступно для изиенения извне.
property Notifier: THandle read FNotifier;
end;
implementation
uses
SysUtils, MainUnit;
{ TDirMonThread }
procedure TDirMonThread.Execute;
begin
FNotifier := FindFirstChangeNotification('C:\', False,
FILE_NOTIFY_CHANGE_FILE_NAME or FILE_NOTIFY_CHANGE_ATTRIBUTES or
FILE_NOTIFY_CHANGE_LAST_WRITE);
// При ошибке Win32Check выводит сообщение и прерывает Execute.
Win32Check(FNotifier <> INVALID_HANDLE_VALUE);
while WaitForSingleObject(FNotifier, INFINITE) = WAIT_OBJECT_0 do
begin
// Поскольку стоит INFINITE, поток берет минимум процессорного времени,
// несмотря на высокий приоритет
Synchronize(UpdateLog);
FindNextChangeNotification(FNotifier)
end // FindCloseChangeNotification будет вызвана в обработчике OnTerminate.
end;
procedure TDirMonThread.UpdateLog;
begin
frmMain.lbLog.Items.Add('Changed at ' + TimeToStr(Time))
end;
end.
|
Пример можно взять здесь
На этом хочу закончить первую часть статьи, и пожелать всем научиться писать
такой же компактный, красивый и емкий код, приведенный выше!
|