65.9K
CodeProject 正在变化。 阅读更多。
Home

CDirectoryChangeWatcher - 包装好的 ReadDirectoryChangesW

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.92/5 (112投票s)

2001年2月1日

viewsIcon

2285993

downloadIcon

39669

此类封装了 ReadDirectoryChangesW。

DirWatcher Sample App Screenshot

引言

此代码封装了 Win32 API 函数 ReadDirectoryChangesW,以便您的应用程序只需担心响应在文件或目录被添加、删除、修改或重命名时发生的事件。

此代码仅适用于 Windows NT、Windows 2000 或 Windows XP,并且您希望监视的目录也必须位于 Windows NT、Windows 2000 或 Windows XP 计算机上。

有两个类必须一起使用才能监视目录,它们是:CDirectoryChangeWatcherCDirectoryChangeHandler

关于 CDirectoryChangeWatcher:

CDirectoryChangeWatcher 类负责监视目录的所有繁重工作。它创建一个工作线程,该线程通过使用 ReadDirectoryChangesW Win32 API 函数来等待目录发生更改。可以使用单个 CDirectoryChangeWatcher 实例监视多个目录,并且可以随时添加或删除目录进行监视。

当收到文件更改通知时,CDirectoryChangeWatcher 会通过 CDirectoryChangeHandler 类(见下文)将这些通知“分派”给您的应用程序。

关于 CDirectoryChangeHandler:

CDirectoryChangeHandler 类接收来自 CDirectoryChangeHandler 的文件更改通知。您需要从 CDirectoryChangeHandler 派生一个类,以便您的应用程序能够处理文件更改通知。
可以使用单个 CDirectoryChangeHandler 派生类的实例来同时处理多个目录监视的通知,或者您可以为每个监视的目录指定不同的 CDirectoryChangeHandler 派生类。

接口

以下是 CDirectoryChangeWatcher 的公共接口

class CDirectoryChangeWatcher{
public: 
   enum	{  //options for determining the behavior of the filter tests.
           FILTERS_DONT_USE_FILTERS     = 1, 
	   FILTERS_CHECK_FULL_PATH	= 2, 
	   FILTERS_CHECK_PARTIAL_PATH	= 4, 
	   FILTERS_CHECK_FILE_NAME_ONLY	= 8, 
	   FILTERS_TEST_HANDLER_FIRST	= 16,
	   FILTERS_DONT_USE_HANDLER_FILTER = 32, 
	   FILTERS_DEFAULT_BEHAVIOR	= (FILTERS_CHECK_FILE_NAME_ONLY ),
	   FILTERS_DONT_USE_ANY_FILTER_TESTS = (FILTERS_DONT_USE_FILTERS | 
                                                 FILTERS_DONT_USE_HANDLER_FILTER)
	};
  CDirectoryChangeWatcher(bool bAppHasGUI = true, 
                          DWORD dwFilterFlags = FILTERS_DEFAULT_BEHAVIOR);
  virtual ~CDirectoryChangeWatcher();//dtor
 
  DWORD	WatchDirectory( const CString & strDirToWatch, 
			DWORD dwChangesToWatchFor, 
			CDirectoryChangeHandler * pChangeHandler, 
			BOOL bWatchSubDirs = FALSE,
			LPCTSTR szIncludeFilter = NULL,
			LPCTSTR szExcludeFilter = NULL);

  BOOL IsWatchingDirectory(const CString & strDirName)const;
  int  NumWatchedDirectories()const; 

  BOOL UnwatchDirectory(const CString & strDirToStopWatching);
  BOOL UnwatchAllDirectories();

  DWORD	SetFilterFlags(DWORD dwFilterFlags);
  DWORD GetFilterFlags() const;

...
};


CDirectoryChangeHandler 类具有以下接口

class CDirectoryChangeHandler{
   ...
   BOOL UnwatchDirectory(); 
   ...
 protected:
  //override these functions:	
   //Notification related:
   virtual void On_FileAdded(const CString & strFileName);
   virtual void On_FileRemoved(const CString & strFileName);
   virtual void On_FileModified(const CString & strFileName);
   virtual void On_FileNameChanged(const CString & strOldFileName, 
                                   const CString & strNewFileName);
   virtual void On_ReadDirectoryChangesError(DWORD dwError);
   virtual void On_WatchStarted(DWORD dwError, const CString & strDirectoryName);
   virtual void On_WatchStopped(const CString & strDirectoryName);
   //Filter related:
   virtual bool On_FilterNotification(DWORD dwNotifyAction,
                                      LPCTSTR szFileName, LPCTSTR szNewFileName);
   ...
  };

要处理在文件或目录添加、删除、修改或重命名时发生的事件,请创建一个从 CDirectoryChangeHandler 派生的类,该类执行您希望在这些事件发生时执行的所有操作。

通知

使用这些类时,您可以收到 7 种通知。

通知
CDirectoryChangeHandler 函数
通知描述
接收通知所需的标志——
(参数 dwChangesToWatchFor

CDirectoryChangeWatcher:
WatchDirectory()
)
监视已启动
On_WatchStarted
已启动目录监视,因为调用了 CDirectoryChangeWatcher::WatchDirectory
N/A
监视已停止
On_WatchStopped
已停止目录监视,因为调用了 CDirectoryChangeWatcher::
UnwatchDirectory

CDirectoryChangeWatcher::
UnwatchAllDirectories


CDirectoryChangeHandler 的析构函数被调用,并且当时有 1 个或多个目录正在被监视时,也可能调用此函数。
** 请参阅 DirectoryChanges.h 中 On_WatchStopped 函数附近的注释,以避免可能在某些情况下发生的“查阅手册”错误。**
N/A
目录监视错误
On_ReadDirectoryChangesError
监视目录时发生错误。在这种情况下,监视会自动停止。您将不会收到 On_WatchStopped 通知。
N/A
文件已添加
On_FileAdded
文件已添加到监视的目录中(新创建或复制到该目录中)。
FILE_NOTIFY_CHANGE_FILE_NAME 和/或 FILE_NOTIFY_CHANGE_DIR_NAME(针对目录)
文件已移除
On_FileRemoved
文件已从监视的目录中删除或移除(例如,移至回收站、移动到其他目录或删除)。
FILE_NOTIFY_CHANGE_FILE_NAME  FILE_NOTIFY_CHANGE_DIR_NAME
文件名已更改
On_FileNameChanged
监视目录中的文件名称已更改。此通知的参数是旧文件名和新文件名。
FILE_NOTIFY_CHANGE_FILE_NAME  FILE_NOTIFY_CHANGE_DIR_NAME
文件已修改
On_FileModified
监视目录中的文件已以某种方式修改。导致您收到此通知的原因可能包括文件最后访问、最后修改或创建时间戳的更改。其他更改,如文件属性、大小或安全描述符的更改,也可能触发此通知。
FILE_NOTIFY_CHANGE_ATTRIBUTES
FILE_NOTIFY_CHANGE_SIZE
FILE_NOTIFY_CHANGE_LAST_WRITE
FILE_NOTIFY_CHANGE_LAST_ACCESS
FILE_NOTIFY_CHANGE_CREATION
FILE_NOTIFY_CHANGE_SECURITY

过滤器

此类的新功能之一是能够过滤掉通知,以便您只接收您想要接收通知的文件。此功能使您可以根据更改文件的名称,或根据您实现的一个函数,仅接收您希望了解的文件通知。

没有过滤器时,您将收到所有文件更改的通知,具体取决于传递给 CDirectoryChangeWatcher::WatchDirectory 函数的标志组合(这是默认行为)。

有三种类型的过滤器:包含过滤器、排除过滤器以及由程序员实现的虚拟函数,该函数可以决定是否调用相应的 CDirectoryChangeHandler::On_Filexxxxx() 函数。

包含和排除过滤器

包含和排除过滤器是传递给 CDirectoryChangeWatcher::WatchDirectory 的字符串参数,用于启动目录监视。过滤器是一个可以包含 DOS 通配符 * 和 ? 的模式。可以通过用分号 (;) 分隔每个模式来指定多个模式。

以下是可用于过滤通知的有效模式字符串的示例

    "*.txt"   <-- 指定单个文件模式。
    "*.txt;*.tmp;myfile???.txt;MySubFolder???\*.doc"  <-- 指定多个文件模式。

请注意,这些字符串过滤器的支持代码使用 PathMatchSpec Win32 API 函数来确定模式匹配。 PathMatchSpec 实现于 shlwapi.dll 版本 4.71 或更高版本。如果您运行的是 NT4.0 且未安装 Internet Explorer 4.0,则使用修改后的 wildcmp 版本。

包含过滤器是什么意思?

包含过滤器用于告知 CDirectoryChangeWatcher 您希望仅接收与特定模式匹配的文件的通知。所有其他文件通知都将被隐式排除,因为文件名不匹配“包含过滤器”模式。即:如果您传递一个“包含过滤器”为“*.txt”,您将只收到文件名以“.txt”结尾的文件的通知(文件添加、文件删除等)。您不会收到其他文件更改的通知。

注意:指定 NULL 或空字符串比指定包含过滤器“*.*”更好。

排除过滤器是什么意思?

使用排除过滤器,您可以选择告知 CDirectoryChangeWatcher 根据更改文件的名称,明确忽略对文件更改的通知。例如,您可以选择忽略监视目录中的 .log 文件更改。为此,您可以指定一个排除过滤器“*.log”。

包含和排除过滤器是自定义您希望接收的通知的强大方法。要测试您的模式字符串,示例应用程序中提供了一个对话框。在示例应用程序上按“测试过滤器模式...”按钮。

程序员定义的过滤器

您也可以重写函数:virtual bool CDirectoryChangeHandler::On_FilterNotification()。如果 On_FilterNotification 返回 true(默认值),您将收到通知。如果 On_FilterNotification 返回 false,则文件通知将被忽略。有关此函数的更多信息,请参阅源代码中的注释。

过滤器选项

有几个与 CDirectoryChangeWatcher 如何处理过滤器相关的选项。

过滤器标志
标志描述
FILTERS_DONT_USE_FILTERS
指定不检查包含和排除过滤器的字符串过滤器。除非 CDirectoryChangeHandler::On_FilterNotification() 返回 false,否则所有通知都会被发送。
FILTERS_DONT_USE_HANDLER_FILTER
指定不测试 CDirectoryChangeHandler::On_FilterNotification()。如果文件名通过包含和排除过滤器的测试,则继续传递通知。
FILTERS_DONT_USE_ANY_FILTER_TESTS
指定不执行任何过滤器测试。不测试包含和排除过滤器,也不调用 CDirectoryChangeHandler::On_FilterNotification()。这是 FILTERS_DONT_USE_FILTERS 和 FILTERS_DONT_USE_HANDLER_FILTER 标志的组合。
FILTERS_NO_WATCHSTART_NOTIFICATION 不会调用 CDirectoryChangeHandler::On_WatchStarted
FILTERS_NO_WATCHSTOP_NOTIFICATION 不会调用 CDirectoryChangeHandler::On_WatchStopped
FILTERS_NO_STARTSTOP_NOTIFICATION 不会调用 CDirectoryChangeHandler::On_WatchStartedCDirectoryChangeHandler::On_WatchStopped。是 FILTERS_NO_WATCHSTART_NOTIFICATION 和 FILTERS_NO_WATCHSTOP_NOTIFICATION 的组合。
FILTERS_TEST_HANDLER_FIRST
指定在文件名与包含和排除过滤器模式进行测试之前,调用 CDirectoryChangeHandler::On_FilterNotification()。默认情况下,只有当文件名与可能指定的包含或排除过滤器模式匹配后,才会测试 CDirectoryChangeHandler::On_FilterNotification()
FILTERS_CHECK_FULL_PATH
指定要将整个文件名和路径与包含和排除过滤器模式进行测试。
FILTERS_CHECK_PARTIAL_PATH
指定仅测试文件路径和名称的一部分是否与包含和排除过滤器进行匹配。检查的路径部分是监视文件夹名称之后的路径部分。

例如,如果您正在监视“C:\Temp”(并且也监视子文件夹),并且名为“C:\Temp\SomeFolder\SomeFileName.txt”的文件被更改,则与包含和排除过滤器进行检查的文件名部分是“SomeFolder\SomeFileName.txt”。
FILTERS_CHECK_FILE_NAME_ONLY
指定仅将文件路径的文件名部分与包含和排除过滤器进行测试。
FILTERS_DEFAULT_BEHAVIOR
指定“默认”过滤器处理行为。
目前,它仅设置为 FILTERS_CHECK_FILE_NAME_ONLY。

这意味着包含/排除过滤器在 On_FilterNotification 之前进行测试,并且会调用 On_WatchStartedOn_WatchStopped

要指定这些选项,请参阅 CDirectoryChangeWatcher 的构造函数,或使用函数 CDirectoryChangeWatcher::SetFilterFlags()

请注意,过滤器标志用于下一次调用 CDirectoryChangeWatcher::WatchDirectory,并且调用 CDirectoryChangeWatcher::SetFilterFlags() 不会对任何当前正在监视的目录产生影响。

示例应用程序包含一个允许您进行测试的对话框。

线程安全性和消息泵。

虽然 CDirectoryChangeWatcher 使用工作线程来完成工作,但所有通知都在主线程的上下文中调用。“主”线程是第一个调用 CDirectoryChangeWatcher::WatchDirectory 的线程。CDirectoryChangeWatcher 使用一个隐藏的通知窗口将通知从工作线程分派到主线程。因为它使用窗口,所以调用应用程序必须在程序的“主”线程的某个地方实现消息泵。

控制台应用程序

对于控制台应用程序,或没有消息泵的应用程序/线程,仍然可以使用 CDirectoryChangeWatcher。只需将 bAppHasGUI 参数的值传递为 false 到 CDirectoryChangeWatcher 的构造函数。而不是使用隐藏的通知窗口,CDirectoryChangeWatcher 使用一个额外的工作线程。请注意,当您将 bAppHasGUI 参数的值传递为 false 到 CDirectoryChangeWatcher 的构造函数时,所有 CDirectoryChangeHandler 函数都在工作线程的上下文中调用,而不是主线程。

CDirectoryChangeWatcher watcher(false); //safe to use in a console app.
				//
				//Note: CDirectoryChangeHandler functions are called 
				// 	in a worker thread.

示例

class CMyDirectoryChangeHandler : public CDirectoryChangeHandler
{
public:
      	CMyDirectoryChangeHandler(){} 
      	virtual ~CMyDirectoryChangeHandler(){}

protected:
       	void On_FileNameChanged(const CString & strOldFileName, const CString & strNewFileName)
       	{
             MessageBox(NULL, _T("The file ") + strOldFileName + _T(" was renamed to: ") + 
                              strNewFileName,_T("Test"),MB_OK);
       	}
       	bool On_FilterNotification(DWORD dwNotifyAction, LPCTSTR szFileName, LPCTSTR szNewFileName)
       	{ 
	   //
            // This programmer defined filter will only cause notifications
            // that a file name was changed to be sent.
            //
		if( dwNotifyAction == FILE_ACTION_RENAMED_OLD_NAME ) 
                   return true;
              	return false;
	}
};

....
        CDirectoryChangeWatcher watcher;
        CMyDirectoryChangeHandler MyChangeHandler;
        watcher.WatchDirectory(_T("C:\\Temp"), 
                                 FILE_CHANGE_NOTIFY_FILE_NAME,
                                 &MyChangeHandler,
                                 FALSE, //<-- watch sub directories? 
                                 NULL, //<-- Include Filter
                    		 NULL);//<-- Exclude Filter

CDirectoryChangeWatcher 基于 SDK 中的 FWatch 示例程序,并使用 I/O 完成端口,因此对于任何数量的监视目录,将只有一个工作线程(每个 CDirectoryChangeWatcher 实例)。

在您的应用程序中使用此源代码时,您只需要使用 CDirectoryChangeWatcher 和从 CDirectoryChangeHandler 派生的类。这些源文件中的任何其他类都用于实现 CDirectoryChangeWatcher

下载示例或源代码以获取更多详细信息。

致谢

  • CDirectoryChangeWatcher 基于 SDK 中的 FWatch 示例。
  • 示例应用程序使用了 Armen Hakobyan 的 CFolderDialog
  • CDirectoryChangeWatcher 使用了 Jack Handy 的 wildcmp 的修改版本。

欢迎通过电子邮件向我发送 bug、bug 修复、技巧、评论、赞扬或批评:wesj@hotmail.com

© . All rights reserved.