Click here to Skip to main content
Click here to Skip to main content

FindFirstChangeNotification & Shell_NotifyIcon together... again

, 10 Oct 2007
Rate this:
Please Sign up or sign in to vote.
A brief description on how to monitor folder changes and get notification via the taskbar's "icon tray"

Introduction

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:

  1. How to monitor a folder without having to employ a CPU-intensive query every couple of minutes.
  2. 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:

while (1)
{
    check folder(s) for any new file(s) since last-checked date/time
    if any exist
        report them
    endif
    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; // UNC names are supported
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)
    ... // provide some sort of indication to the user

... In a Separate Thread

Since 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 WaitForMultipleObjects() instead:

HANDLE aHandles[2];
aHandles[0] = hChange;
aHandles[1] = hEvent; // member belonging to primary thread

// sleep until a file change notification wakes this thread or
// m_event becomes set indicating it's time to end
BOOL bContinue = TRUE;
while (bContinue)
{
    if (WaitForMultipleObjects(2, aHandles, FALSE, INFINITE) - WAIT_OBJECT_0 == 0)
    {
        // perform some sort of notification here

        FindNextChangeNotification(hChange);
    }
    else // exit loop, as m_event has became signalled
        bContinue = FALSE;
}

// close the file change notification handle
FindCloseChangeNotification(hChange);

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; // member belonging to primary thread
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; // for displaying an "info" icon
m_niData.uFlags           = NIF_ICON | NIF_MESSAGE | NIF_TIP;
AfxLoadString(IDS_MSG_FAXWATCH, m_niData.szTip, sizeof(m_niData.szTip));
Shell_NotifyIcon(NIM_ADD, &m_niData);

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!

If 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));
    _tcscpy(m_niData.szInfo, *pstrText);

    Shell_NotifyIcon(NIM_MODIFY, &m_niData);

    delete pstrText;

    return 0;
}

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:

  1. The UI will be restored.
  2. The change notification request will be cancelled.
  3. The icon will be removed from the taskbar notification area.

Code for these looks like:

void RestoreWindow( void )
{
    // signal ThreadFunc() to end
    m_event.SetEvent();

    ShowWindow(SW_RESTORE);
    Shell_NotifyIcon(NIM_DELETE, &m_niData);

    // wait for ThreadFunc() to end
    WaitForSingleObject(&m_event, INFINITE);
}

Cleanup

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:

  1. Signal the thread to stop.
  2. 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)
{
    // signal and wait for ThreadFunc() to end
    m_event.SetEvent();
    WaitForSingleObject(&m_event, INFINITE);

    delete m_pWinThread;
}

To remove the icon from the taskbar notification area, simply call Shell_NotifyIcon() with the NIM_DELETE action.

Notes

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.

Extras

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"));
...
ON_REGISTERED_MESSAGE(UDM_TASKBARCREATED, OnTaskbarCreated)
...
LRESULT OnTaskbarCreated( WPARAM, LPARAM )
{
    // we don't want to add the icon to the taskbar notification area
    // if it was not already there
    if (! IsWindowVisible())
        Shell_NotifyIcon(NIM_ADD, &m_niData);

    return 0;
}

References

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:

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

DavidCrow
Software Developer (Senior) Pinnacle Business Systems
United States United States

The page you are looking for might have been removed, had its name changed, or is temporarily unavailable.
 
HTTP 404 - File not found
Internet Information Services

Comments and Discussions

 
GeneralMy vote of 1 Pinmembersirisakmax5-Feb-11 2:04 
GeneralMy vote of 1 PinmemberMax++5-Feb-11 1:54 
GeneralI need same code in VB.net PinmemberSiddiq-ur-Rahman Khurram4-Aug-08 20:04 
I need same code in VB.net. can anyone help.
Thanks in advance.
GeneralHi PinmemberVitali Halershtein20-Dec-07 1:33 
GeneralThanks PinmemberWes Aday11-Oct-07 4:11 
Generalgot my 5 PinmemberNiiiissssshhhhhuuuuu11-Oct-07 0:01 
GeneralVery nice! PinmvpHans Dietrich10-Oct-07 11:45 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140814.1 | Last Updated 10 Oct 2007
Article Copyright 2007 by DavidCrow
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid