My office recently adopted a paperless option for incoming faxes. Basically, what that means is a stand-alone computer sits between the regular fax machine and the wall jack to intercept all incoming faxes. Those are dumped into a folder on another network computer. It's up to everyone in the office to periodically look in that folder for any faxes. While not entirely taxing to my mental faculties, this can be a small burden to remember when other, more important issues are at hand. So, over the weekend I put together a small utility whose sole purpose in life is to monitor that folder, and give me a reminder to check it when a new fax arrives.
Yes, other, more elaborate programs exist, but I was interested in something very simple, as well as something that could be turned into a tutorial. My goal with this was not to reinvent what others have done here at The Code Project, but rather peel away the fluff and just present the basics. Plugging a VC++ class into a project and interacting with it using only one or two interfaces is all well and good, but I wanted something else. I will attempt to not duplicate anything from those articles.
Two items presented in this article are:
- How to monitor a folder without having to employ a CPU-intensive query every couple of minutes.
- How to place an icon in the "taskbar notification area".
I've also included the utility itself, although it is more of a byproduct rather than the focal point of this article.
Monitoring a Folder
I can remember back some 17 years ago when I was programming in a Unix environment, us "lab rats" were always trying to figure out some way to do this, that, or the other with the system. One of our endeavors was to monitor a folder on the network for activity. While the solution was straight forward, it was not the most efficient. It would have looked something like:
check folder(s) for any new file(s) since last-checked date/time
if any exist
sleep for so many minutes
This worked great, and led to many other discoveries as a result. The only "problem" was that it required us to ask the OS if any changes had been made to the folder(s) being watched. While not as CPU-intensive as creating a Pixar movie, a more efficient solution would not come along until years later with the introduction of the FindFirstChangeNotification() function.
The utility has a very simple interface. It asks for the folder to monitor, and whether or not to monitor sub-folders. Clicking the OK button gets everything started. Since all I was interested in was whether a change had taken place or not, and not what had actually changed, I simply called the function like:
CString strFolder; HANDLE hChange =
FindFirstChangeNotification(strFolder, FALSE, FILE_NOTIFY_CHANGE_FILE_NAME);
Yes, I could have used ReadDirectoryChangesW() to get the names of the files that were changed, but since those names are created by the fax software, and they do not indicate who the faxes were intended for, they would have been meaningless to me anyway.
Anyway, at this point, a "change notification handle" has been created that can be passed on to the OS so that it can notify us, with little to no CPU usage, when a change occurs:
if (WaitForSingleObject(hChange, INFINITE) == WAIT_OBJECT_0)
... In a Separate Thread
WaitForSingleObject() does not return until one of two conditions is met, the UI will become non-responsive. To address this, creation of a secondary thread is necessary. In the controlling function for the secondary thread is where the call to
FindFirstChangeNotification() occurs. Since we'll need a way of cancelling the change notification and/or exiting the application, we'll need to add another event to the mix. I use a
CEvent object for this, and call
aHandles = hChange;
aHandles = hEvent;
BOOL bContinue = TRUE;
if (WaitForMultipleObjects(2, aHandles, FALSE, INFINITE) - WAIT_OBJECT_0 == 0)
else bContinue = FALSE;
Adding an Icon to the "Tray"
Back around 1997, I was reading an MSJ article by Paul DiLascia about putting icons in the Windows 95 "system tray" and just had to laugh. After he described how to do it, he finished by saying, "Go ahead, do it. Add a tray icon to your app, stare at it, feel good, show it to your friends, have a party. Then take it out. Because most apps have no need for them. Unless...all tray icons will do is contribute to screen pollution." I tend to keep a real clean desktop, so I can see how this could easily become a problem!
The "taskbar notification area" (a.k.a. "system tray") serves a useful purpose if used responsibly. Some folks, however, treat it just like the Quick Launch toolbar. Yuck!
Once the change notification has been requested, the program can now hide its UI and add an icon to the taskbar notification area. Doing so is simply a matter of calling Shell_NotifyIcon() with the appropriate arguments:
NOTIFYICONDATA m_niData; m_niData.cbSize = sizeof(m_niData);
m_niData.hIcon = m_hIcon;
m_niData.uCallbackMessage = UDM_TRAY_ICON;
m_niData.hWnd = GetSafeHwnd();
m_niData.dwInfoFlags = NIIF_INFO; m_niData.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
AfxLoadString(IDS_MSG_FAXWATCH, m_niData.szTip, sizeof(m_niData.szTip));
Notifying the User that a Change has Occurred
When I first started on this project, I employed the use of a class from Jeff Prosise called CPopupText that would pop up a little bubble with some text in it indicating that a change had been detected. Nothing fancy, but it worked. The only thing I had to change was the location of the text such that it appeared above the icon in the taskbar notification area. It wasn't until I started adding tray-related code that I discovered this functionality was actually built in to the shell. It's amazing what jewels you find when digging through header files. Sweet!
WaitForMultipleObjects() returns a value indicating that the change notification handle has been signalled, we need to let the user know. However, since the notification is in the secondary thread, we need to let the primary thread know since it owns the UI. This means that stack-based data cannot be sent back and forth between the two. We'll need to use memory on the heap instead:
CString *pstrText = new CString(strFolder);
::PostMessage(hWnd, UDM_SET_NOTIFY_TEXT, 0, (LPARAM) pstrText);
The primary thread responds to this message in the following manner:
LRESULT OnSetNotifyText( WPARAM, LPARAM lParam )
CString *pstrText = (CString *) lParam;
m_niData.uFlags |= NIF_INFO;
AfxLoadString(IDS_MSG_INFOTITLE, m_niData.szInfoTitle, sizeof(m_niData.szInfoTitle));
Now the primary thread can render the text while the secondary thread can go back to monitoring as quickly as possible. If you ever find yourself in a situation where not all change notifications are being signalled for folders on other machines, you might verify that
FindNextChangeNotification() is being called as soon as possible after
WaitForMultipleObjects() returns. Other situations that may cause remote change notifications to not get signalled are if the folder contains symbolic links, or if multiple processes are all monitoring the same folder. In the latter case, the redirector may try and optimize this by only opening one handle to the remote folder, thus only one change notification would get signalled. See here for a possible solution.
Restoring the UI
If the icon in the taskbar notification area is double-clicked, three things will happen:
- The UI will be restored.
- The change notification request will be cancelled.
- The icon will be removed from the taskbar notification area.
Code for these looks like:
void RestoreWindow( void )
The application can be exited if the window is visible, or if the window is not visible and an icon is in the taskbar notification area. Regardless, the
OnDestroy() method is called. We need to do two things there:
- Signal the thread to stop.
- Remove the icon from the taskbar notification area.
If the secondary thread has ever been created, we need to signal the event that will cause that thread to stop, and then actually wait for it to stop:
if (m_pWinThread != NULL)
To remove the icon from the taskbar notification area, simply call
Shell_NotifyIcon() with the
A nice addition to the text displayed in the "bubble" would be a hyperlink. That way, when the user gets notified that a change has occurred in a folder, a simple click would open that folder in Windows Explorer.
Have you ever had the Explorer process just get mad and quit, and when it comes back, some of the icons in the taskbar notification area stay gone? After the taskbar has been created, Windows will broadcast a notification to all top-level Windows in the form of a registered window message called
TaskbarCreated. We can respond to this notification and add our icon back to the taskbar notification area like:
static const UINT UDM_TASKBARCREATED = RegisterWindowMessage(_T("TaskbarCreated"));
LRESULT OnTaskbarCreated( WPARAM, LPARAM )
if (! IsWindowVisible())
While there are several other examples of this on The Code Project and around the Web, many of them are just code snippets with nary a comment or an explanation. Hopefully, I've provided somewhat useful information with this one. Those that are worthy of mention include: