Project: Monitoring Directory Changes
Demo and Sample Project (147 KB)
How do I monitor
the contents of a directory? This is certainly a commonly asked question
with a usually simple answer: Use the FindFirstChangeNotification() API
function. While this is indeed the correct approach to take, the
methods to actually accomplish this are not so straightforward.
In fact, FindFirstChangeNotification() is just a small part of making this
work. As such, the purpose of this article is to provide an more
in-depth look at exactly what's involved in monitoring the contents of
Why would one want to monitor directories
anyway? Consider the case of using the TFileListBox component to
allow the end user to select files which will be transferred via FTP, for
example. Imagine that the user copies a file into the folder referred
to by the FileListBox's Directory property, yet fails to see it appear
in the list. That is, the FileListBox does not update upon a change
of the folder's contents. As you can imagine, this could cause frustration
and a premature judgment of your application as being less than robust.
As consumers ourselves, we would expect such an application to respond
to directory updates. Consider the hassle of needing to refresh Explorer's
display once you drag a file into that folder. Obviously, there has
to be a standard solution to this problem.
As mentioned earlier, the standard
solution is to use the FindFirstChangeNotification() API function.
This function will return a handle to a change notification object.
The syntax is as follows:
to a buffer that holds the
absolute path to monitor
flag that indicates whether or
not to watch subdirectories of lpPathName
to specify filter conditions that satisfy
a change notification wait.
In the call to this function, dwNotifyFilter
specifies what changes of the directory you want notification of.
The following list outlines the available options (use the logical OR operator,
to combine these values:
||Gives notification of any changes to the filename of
a file within the folder (and subfolders, if specified) being monitored.
||Gives notification of any changes to the directory name
of a folder within the folder (and subfolders, if specified) being monitored.
||Gives notification of any changes to the attribute of
a file or folder within the folder (and subfolders, if specified) being
monitored. For example, changing a file within the watched folder
||Gives notification of any changes to the size of a file
or folder within the folder (and subfolders, if specified) being monitored.
||Gives notification of a write-time change of a file within
the folder (and subfolders, if specified) being monitored. Influenced
||Gives notification of a security-descriptor change within
the folder (and subfolders, if specified) being monitored.
So once you receive a change notification
handle from the call to FindFirstChangeNotification(), you pass this handle
into the WaitForMultipleObjects() function which waits for a change to
occur. The WaitForMultipleObjects() function takes the following
of handles included in the in the object
HANDLE *lpHandles, <--- pointer
to an array of object handles
(handles returned by
indicating whether to return when one (false),
or all of the folders in the object handle array,
(true) indicates a change.
interval (in milliseconds)
You may be asking youself, won't
waiting for a change notification cause my program to suspend? In
a sense, yes, execution of your program will not continue until the WaitForMultipleObjects()
function returns. This means that important messages such as WM_PAINT
will not be processed, causing what may seem as application lock. This
is where multitasking, and in particular, the TThread class comes in handy.
So now you know that the reason you're advised to use a separate thread
is due to the use of the WaitForMultipleObjects() function. As mentioned
earlier, this function will wait until it receives notification from the
system. By "waiting" in a separate thread, your main application
is free to process vital messages. Is a thread absolutely necessary?
Well, technically, no, you could actually use the MsgWaitForMultipleObjects()
function inside your window procedure, but unless your application is extremely
simple, this will yield sluggish performance.
Once the WaitForMultipleObjets()
function returns, the return value will indicate whether a change actually
occured or the function timed-out. After the function returns, simply
call the FindNextChangeNotification() function to continue monitoring,
or the FindCloseChangeNotification() function to close the change notification
handle. Here's an overview of the entire process necessary to make
Derive a new thread class from
TThread -- the constructor will accept a TControl* called FTargetControl
which will be notified of changes via a user defined message. In
this way, we won't limit the scope of our thread class to only work with
a particular control.
Within this class, add a public
member function to set the directory to monitor and call the FindFirstChangeNotification()
function -- we'll call this method SetWatchPath().
Within the Execute() method
of the thread class, call the WaitForMultipleObjects() function with the
handle returned by the FindFirstChangeNotification() function.
Once the WaitForMultipleObjects()
returns (i.e., a change has taken place in the directory being monitored),
call a member function that will notify the main process that a change
has occurred -- we'll call this member function UpdateDirectory() and it
will send a user defined message to the control specified by the FTargetControl
WaitForMultipleObjects(), FindNextChangeNotification(), FindCloseChangeNotification().
in thread class header...
CM_DIRCONTENTSCHANGED WM_APP + 101 //
TDirMonitorThread : public TThread
TControl *FTargetControl; // control that
will be notified of changes
HANDLE FChangeHandle; //
handle to a find change notification object
AnsiString FWatchPath; //
directory to monitor
void __fastcall Execute();
__fastcall TDirMonitorThread(bool CreateSuspended,
void __fastcall DirMonitorTerminate(TObject *Sender);
void __fastcall UpdateDirectory();
void __fastcall SetWatchPath(AnsiString Value);