Êíèãà: Windows API Tutorials

When Folders Change

When Folders Change

Have you ever wondered how the Explorer knows that it should update its display because a file has been added or removed from a folder by some external application? Wonder no more because, with the use of our Active Object, we can do the same and more. There are a few simple API calls that you can make to ask the file system to selectively notify you about changes to files and folders. Once you set up such a watch, your thread can go to sleep waiting on an event. The file system will set the event as soon as it detects the kind of change that you're watching for.

Without further ado, let's derive a FolderWatcher from ActiveObject. We'll give it a notification source — an event, and a notification sink — a handle to a window that will respond to the notification. The source event is set up in the constructor of FolderWatcher. It is also important to start the captive thread at the end of the constructor.

class FolderWatcher : public ActiveObject {
public:
 FolderWatcher (char const * folder, HWND hwnd) : _notifySource (folder), _hwndNotifySink (hwnd) {
  strcpy (_folder, folder);
  _thread.Resume ();
 }
 ~FolderWatcher () {
  Kill ();
 }
private:
 void InitThread () {}
 void Loop ();
 void FlushThread () {}
 FolderChangeEvent _notifySource;
 HWND _hwndNotifySink;
 char _folder [MAX_PATH];
};

The action in the ActiveObject happens inside the Loop method. Here we set up an "infinite" loop in which we let the thread wait for the event. When the event happens, we check for the _isDying flag (as usual) and post a special message WM_FOLDER_CHANGE to the window that deals with notifications. This is not one of the predefined Windows messages, it's a message defined by us for the special purpose of passing folder notification information from one thread to another.

Two thing happen now: the captive thread makes another API call to let the file system know that it wants more notifications. It then goes back to watchful sleep. Concurrently, Windows retrieves our WM_FOLDER_CHANGE message from the message queue and sends it to the Windows Procedure of the sink window. More about that in a moment.

UINT const WM_FOLDER_CHANGE = WM_USER;
void FolderWatcher::Loop () {
 for (;;) {
  // Wait for change notification
  DWORD waitStatus = WaitForSingleObject (_notifySource, INFINITE);
  if (WAIT_OBJECT_0 == waitStatus) {
   // If folder changed
   if (_isDying) return;
   PostMessage (_hwndNotifySink, WM_FOLDER_CHANGE, 0, (LPARAM) _folder);
   // Continue change notification
   if (!_notifySource.ContinueNotification ()) {
    // Error: Continuation failed
    return;
   }
  } else {
   // Error: Wait failed
   return;
  }
 }
}

So here what happens in the Windows Procedure in response to our special message: we call the Controller's method OnFolderChange. This method can do anything we want. In the Explorer it refreshes the display of the folder we are watching. In our example it just pops up a simple message box. Notice how we are passing the name of the changed folder as LPARAM. It is totally up to us to define what goes into WPARAM and LPARAM of a user-defined message.

By the way, the Folder Watcher is just a part of the Controller.

case WM_FOLDER_CHANGE:
 pCtrl->OnFolderChange (hwnd, (char const *) lParam);
 return 0;
void Controller::OnFolderChange (HWND hwnd, char const * folder) {
  MessageBox (hwnd, "Change Detected", "Folder Watcher", MB_SETFOREGROUND | MB_ICONEXCLAMATION | MB_OK);
}
class Controller {
public:
 Controller(HWND hwnd, CREATESTRUCT * pCreate);
 ~Controller ();
 void OnFolderChange (HWND hwnd, char const *folder);
private:
 FolderWatcher _folderWatcher;
};

Now that we know how to deal with notification, let's have a look at their sources, the File Change Events. An event object is created by the file system in response to FindFirstChangeNotification. A handle to that event is returned from the call. We remember this handle and use it later to either renew or abandon our interest in further notifications. Notice that we can set the watch to be recursive, i.e., watching a given folder and all its subfolders and sub-subfolders. We can also express interest in particular types of changes by passing a bitwise OR of any combination of the following flags:

• FILE_NOTIFY_CHANGE_FILE_NAME (renaming, creating, or deleting a file)

• FILE_NOTIFY_CHANGE_DIR_NAME (creating or deleting a directory)

• FILE_NOTIFY_CHANGE_ATTRIBUTES

• FILE_NOTIFY_CHANGE_SIZE

• FILE_NOTIFY_CHANGE_LAST_WRITE (saving a file)

• FILE_NOTIFY_CHANGE_SECURITY

For convenience we have defined a few subclasses of FileChangeEvent that correspond to some useful combinations of these flags. One of them is the FolderChangeEvent that we used in our FolderWatcher.

class FileChangeEvent {
public:
  FileChangeEvent (char const * folder, BOOL recursive, DWORD notifyFlags) {
   _handle = FindFirstChangeNotification (folder, recursive, notifyFlags);
   if (INVALID_HANDLE_VALUE == _handle) throw WinException ("Cannot create change notification handle");
  }
  ~FileChangeEvent () {
   if (INVALID_HANDLE_VALUE != _handle) FindCloseChangeNotification (_handle);
  }
  operator HANDLE () const { return _handle; }
  BOOL ContinueNotification () {
   return FindNextChangeNotification (_handle);
  }
private:
 HANDLE _handle;
};
class FolderChangeEvent : public FileChangeEvent {
public:
 FolderChangeEvent (char const * folder) : FileChangeEvent (folder, FALSE, FILE_NOTIFY_CHANGE_FILE_NAME) {}
};
class TreeChangeEvent : public FileChangeEvent {
public:
  TreeChangeEvent (char const * root) : FileChangeEvent (root, TRUE, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME) {}
};

It should be easy now to generalize this example to do some really useful work in your programs. Remember to look up the API's that we use in these tutorials in the online help that comes with your compiler.

How about some OLE or COM programming without MFC? Go to the next page.

Îãëàâëåíèå êíèãè


Ãåíåðàöèÿ: 1.215. Çàïðîñîâ Ê ÁÄ/Cache: 3 / 0
ïîäåëèòüñÿ
Ââåðõ Âíèç