10

Is there a way to guarantee that your system tray icon is removed?

To add the system tray icon you do:

Shell_NotifyIcon(NIM_ADD, &m_tnd);

To remove the system tray icon you do:

Shell_NotifyIcon(NIM_DELETE, &m_tnd);

What I want to know: what if you application crashes? The icon stays in your system tray until you mouse over. Is there a way to guarantee that the icon will be removed, even when the application crashes? I would prefer not to use structured exception handling for various reasons.

Another case that I want to handle is when the process is killed, but doesn't necessarily crash.

Jon Seigel
  • 12,251
  • 8
  • 58
  • 92
Brian R. Bondy
  • 339,232
  • 124
  • 596
  • 636

8 Answers8

4

Another thing most programmers forget to check for is if the explorer restarts/crashes. Its nice if the application handle this and recreate its own icon.

Just check for Message WM_TASKBARCREATED and recreate the icon.

Stefan
  • 11,423
  • 8
  • 50
  • 75
2

You could have a separate, simpler (and thus presumably more robust) program which monitors your application. This program could actually launch your program and then monitor the process. Yeah, this is a very ugly solution.

Brian
  • 25,523
  • 18
  • 82
  • 173
  • Also this external program can manage the tray icon, and the main program communicate its status using notifications, if they don't reach withing a timeout the icon is hidden, sort of keep alive message. – Ismael Jan 20 '09 at 20:27
2

Personally I would use a Vectored Exception Handler. Yes, it's based on SEH, but you don't have to deal with all the different stack that you might need to unwind.

TerminateProcess() is must more destructive. You really can't guard yourself against that; when it happens your process is dead. No ore instructions are processed, so it does not matter what code there is in your application.

An external application wouldn't really help, would it? It too could crash, or be killed.

MSalters
  • 173,980
  • 10
  • 155
  • 350
1

There are many ways to ensure the call to Shell_NotifyIcon(NIM_DELETE, &m_tnd); in C++ for the case of the application crhashing; using a RAII wrapper over the NOTIFYICONDATA you're using will do the work, for example:

struct NID
{
    NID() : icon_data() { icon_data.cbSize = sizeof(icon_data); }
    ~NID() { Shell_NotifyIcon(NIM_DELETE, &icon_data); }
    void Show(HWND w) { icon_data.hWnd = w; Shell_NotifyIcon(NIM_ADD, &icon_data); }
    NOTIFYICONDATA icon_data;
};

This is a simplified version of the wrapper but it will illustrate the main idea: if you create an instance of NID in static storage it will be initialized before the WinMain or main call and its destructor will be called on the program cleanup, even if this cleanup is due an abnormal termination.

So, we can use this NOTIFYICONDATA resource wrapped in struct NID this way:

NID nid; // <--- automatic storage duration, cleared after WinMain return
         // even if it returns normal or abnormally

int CALLBACK WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
    try
    {
        // GetMessage(&message, NULL, 0, 0) loop ...
        // ...
        // use nid.icon_data as you please
    }
    catch (...)
    {
        // something bad happened...
    }

    return 0;
}

The example above calls the ~NID() when the program terminates (after an exception or after closing the program), the destructor will call Shell_NotifyIcon(NIM_DELETE, &icon_data); and the icon is deleted from the notification area; this code covers the normal termination and the exception termination, you can read more about this topic in this good answer from NPE:

As for the kill the process case there's no simple way to do this.

I've already tested that std::atexit and std::at_quick_exit functions aren't called after killing the program through the task manager so I guess that you must hook the termination call... it seems a pretty complex task but is explained in this answer from BSH:

When a process is terminated (not closed) nothing realy can be done unless you start do some hooking, either by hooking TerminateProcess or NtTerminateProcess in the Task Manger process

Hope it helps (though is an answer 6 years later lol)

Community
  • 1
  • 1
PaperBirdMaster
  • 12,806
  • 9
  • 48
  • 94
1

Hmm, you can always have an external monitor process call SendMessage with the WM_PAINT message to the system tray window (which you would have to do based on the class of the window). That should remove the icon which is no longer valid.

casperOne
  • 73,706
  • 19
  • 184
  • 253
0

You have to handle the applications exit when crashing some way or another or the icon will not disapear.

Check this out if it can be any help: http://www.codeproject.com/KB/shell/ashsystray.aspx

Stefan
  • 11,423
  • 8
  • 50
  • 75
0

You can use SetUnhandledExceptionFilter to catch the crash. I normally use it to create a crash dump file so that the crash can be debugged, but there's no reason you can't so some simple cleanup like removing tray icons.

Ferruccio
  • 98,941
  • 38
  • 226
  • 299
-1

Does not directly address your problem, but this was a very helpful work around for me:

I wanted to avoid confusing system-tray states. So for me, it was sufficient to 'refresh' the notification tray on startup. This was trickier than I first thought, but the following demonstrates a SendMessage solution that simulates a user-mouse-over cleanup that doesn't involve needing to actually move the user's cursor around.

Note that on Windows 7 machines the name Notification Area should be replaced with User Promoted Notification Area.

phillipwei
  • 1,243
  • 2
  • 12
  • 25
  • 1
    This breaks if the user has the icons hidden with the "arrow" in win XP or above. What are you going to do - programmatically try to click on the arrow and then move the mouse over each icon in the popup - repeatedly clicking the arrow if the popup disappears? And what about when a new version of Windows comes out? This "arrow" functionality was introduced in XP, changed in Vista, and for all I know will change again in Windows 8. You'd have to hard-code for each operating systems and your app will break on new versions. This is a very unsupportable method! – James Johnston Nov 04 '11 at 14:16