Click here to Skip to main content
15,887,214 members
Articles / Programming Languages / C++
Article

Basic use of Shell_NotifyIcon in Win32

Rate me:
Please Sign up or sign in to vote.
4.81/5 (51 votes)
15 Aug 20033 min read 459.7K   16.2K   87   99
Starting your app minimized on the sytem tray.

Introduction

Recently I was browsing CodeProject, looking for a simple example of how to manage system tray icons. Ooops sorry, I shouldn't call them tray icons as MSDN documentation so carefully points out:

"The taskbar notification area is sometimes erroneously called the tray."

In spite of this, I will insist on calling a spade a shovel and refer to the "taskbar notification area" as the "tray" and "taskbar notification area icons" as "tray icons". Anyway back to the point, why another article about tray icons?

  • I've never written a Code Project article before and this seemed pretty easy.
  • This isn't another wrapper, but simple straight forward example of Shell_NotifyIcon with a minimum amount of code.
  • I wanted an example written in pure Win32 code.
  • I wanted to address some of the common problems and questions I've seen posted in other articles on the subject.

The basics

Adding, modifying, hiding and deleting tray icons is accomplished in two steps:

  1. Initialize a NOTIFYICONDATA structure
  2. Call Shell_NotifyIcon

Initialize a NOTIFYICONDATA structure

// zero the structure - note: Some Windows funtions
// require this but I can't be bothered to remember
// which ones do and which ones don't.


    NOTIFYICONDATA niData; 
    ZeroMemory(&niData,sizeof(NOTIFYICONDATA));


// get Shell32 version number and set the size of the
// structure note: the MSDN documentation about this is
// a little dubious(see bolow) and I'm not at all sure
// if the code bellow is correct


    ULONGLONG ullVersion =
        GetDllVersion(_T("Shell32.dll"));

    if(ullVersion >= MAKEDLLVERULL(6,0,0,0))
        niData.cbSize = sizeof(NOTIFYICONDATA);

    else if(ullVersion >= MAKEDLLVERULL(5,0,0,0))
        niData.cbSize = NOTIFYICONDATA_V2_SIZE;

    else niData.cbSize = NOTIFYICONDATA_V1_SIZE;


// the ID number can be any UINT you choose and will
// be used to identify your icon in later calls to
// Shell_NotifyIcon


    niData.uID = MY_TRAY_ICON_ID;


// state which structure members are valid
// here you can also choose the style of tooltip
// window if any - specifying a balloon window:
// NIF_INFO is a little more complicated 


    niData.uFlags = NIF_ICON|NIF_MESSAGE|NIF_TIP;


// load the icon note: you should destroy the icon
// after the call to Shell_NotifyIcon


    niData.hIcon =
        (HICON)LoadImage( hInstance,
            MAKEINTRESOURCE(IDI_MY_ICON),
            IMAGE_ICON,
            GetSystemMetrics(SM_CXSMICON),
            GetSystemMetrics(SM_CYSMICON),
            LR_DEFAULTCOLOR);


// set the window you want to recieve event messages


    niData.hWnd = hWnd;


// set the message to send
// note: the message value should be in the
// range of WM_APP through 0xBFFF


    niData.uCallbackMessage = MY_TRAY_ICON_MESSAGE;

Call Shell_NotifyIcon

// NIM_ADD adds a new tray icon
    Shell_NotifyIcon(NIM_ADD,&niData);

Stealth dialog

I've seen more than a few posts asking how to begin a dialog app minimized to the system tray, hence the name Stealth Dialog. This can be accomplished simply by first creating a modeless dialog:

HWND hWnd = CreateDialog( hInstance,
    MAKEINTRESOURCE(MY_DIALOG),
    NULL,
    (DLGPROC)MyDlgProc );

Then use Shell_NotifyIcon as shown above to add your icon to the tray. Do not call ShowWindow.

Menus and messages

Messages from the tray will go to the window specified by the hWnd member of the NOTIFYICONDATA struct and the message ID is specified by the uCallbackMessage member (see above). The specific message is in the LPARAM.

INT_PTR CALLBACK MyDlgProc(HWND hWnd, UINT message,
    WPARAM wParam, LPARAM lParam)
{
    switch(message)
    {
    case MY_TRAY_ICON_MESSAGE:
        switch(lParam)
        {
        case WM_LBUTTONDBLCLK:
            ShowWindow(hWnd, SW_RESTORE);
            break;
        case WM_RBUTTONDOWN:
        case WM_CONTEXTMENU:
            ShowContextMenu(hWnd);
        }
        break;
    case...

If you implement a context menu, messages are received through WM_COMMAND and the menu item ID is contained in the low-order word of the WPARAM.

case WM_COMMAND:
    switch (LOWORD(wParam))
    {
    case MY_MENU_MSG1:
        ...
        break;
    case MY_MENU_MSG2:
    ...

Important: If you implement a context menu, it's vital that you set your window to the foreground before calling TrackPopupMenu to ensure the menu closes properly.

void ShowContextMenu(HWND hWnd)
{
    ...

    HMENU hMenu;

// create or load a menu

    ...

    SetForegroundWindow(hWnd);
    TrackPopupMenu(hMenu, ...

Cleaning up

Sometime before your app closes you should remove your tray icon by calling Shell_NotifyIcon with the NIM_DELETE flag.

case WM_DESTROY:
    Shell_NotifyIcon(NIM_DELETE,&niData);

Notes:

The MSDN documentation says about the cbSize member of the NOTIFYICONDATA structure:

"You can keep your application compatible with all Shell32.dll versions while still using the current header files by setting the size of the NOTIFYICONDATA structure appropriately. Before initializing the structure, use the DllGetVersion function to determine which Shell32.dll version is installed on the system. If it is version 5.0 or greater, initialize the cbSize member with:

nid.cbSize = sizeof(NOTIFYICONDATA);

Setting cbSize to this value enables all the version 5.0 and 6.0 enhancements. For earlier versions, the size of the pre-6.0 structure is given by the NOTIFYICONDATA_V2_SIZE constant and the pre-5.0 structure is given by the NOTIFYICONDATA_V1_SIZE constant. Initialize the cbSize member with:

nid.cbSize = NOTIFYICONDATA_V2_SIZE;

Using this value for cbSize enables your application to use NOTIFYICONDATA with earlier Shell32.dll versions, although without the version 6.0 enhancements:"

Now maybe it's my neighbors Turkish tobacco or maybe I'm just not catching on here, but there seems to be an overlapping conflict between "5.0 or greater" and "pre-6.0".

Anyway, if anybody can shed any light on this or anything else they care to shed light on, please shed.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
China China
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionPlease provide any license for this article Pin
WolfgangRe7-Dec-21 3:55
WolfgangRe7-Dec-21 3:55 
QuestionDlgProc will always evaluate as 'true' [-Waddress] Pin
EDENY26-Nov-18 9:47
EDENY26-Nov-18 9:47 
Questionstealthdialog link error Pin
rac800613-Nov-14 11:35
rac800613-Nov-14 11:35 
AnswerRe: stealthdialog link error Pin
Abraxas2316-Nov-14 13:28
Abraxas2316-Nov-14 13:28 
AnswerRe: stealthdialog link error Pin
EDENY29-Nov-18 6:56
EDENY29-Nov-18 6:56 
QuestionWhere is the Library for this? Pin
Member 102597227-Sep-13 20:46
Member 102597227-Sep-13 20:46 
GeneralMy vote of 4 Pin
TELunus20-Jan-13 17:56
TELunus20-Jan-13 17:56 
GeneralMy vote of 5 Pin
noobody211-Jul-10 20:53
noobody211-Jul-10 20:53 
QuestionIcon Disappears [modified] Pin
ligoten21-Oct-08 14:05
ligoten21-Oct-08 14:05 
GeneralThanks for the sample Pin
don.leri30-Sep-08 8:28
don.leri30-Sep-08 8:28 
GeneralThanks for the useful article Pin
jianwu9-Sep-08 20:59
jianwu9-Sep-08 20:59 
GeneralNo window at all Pin
Jasper9118-Jul-07 11:14
Jasper9118-Jul-07 11:14 
GeneralRe: No window at all Pin
Abraxas2319-Jul-07 13:39
Abraxas2319-Jul-07 13:39 
GeneralYou can remove the DllVersion stuff. Also, a NIF_TIP problem. Pin
mnbv09878-Feb-07 18:56
mnbv09878-Feb-07 18:56 
A couple of comments.

If you simply set the size to NOTIFYICONDATA_V1_SIZE, then the system will treat it as a V1 NOTIFYICONDATA structure. While you won't have the 5.0 and 6.0 shell features available, that's not a problem as the code in this example does not use them anyway. You'd do:

NOTIFYICONDATA niData; 
ZeroMemory(&niData,sizeof(NOTIFYICONDATA));
niData.cbSize = NOTIFYICONDATA_V1_SIZE;
niData.uID = MY_TRAY_ICON_ID;
niData.uFlags = NIF_ICON|NIF_MESSAGE;
niData.hIcon = (HICON)LoadImage(...);
niData.hWnd = hWnd;
niData.uCallbackMessage = MY_TRAY_ICON_MESSAGE;


And that will do it. That will also allow your application to run correctly on earlier versions of Windows, as NOTIFYICON_V1_SIZE was the old structure size. After that, call Shell_NotifyIcon() as per usual.

Also this writeup makes use of NIF_TIP but does not set the tool tip string. If you do not want a tool tip, then leave NIF_TIP out of niData.uFlags. However, if you do want a tool tip, include NIF_TIP as per the example, but don't forget to set the tool tip string:

::strcpy(niData.szTip, "The tool tip string.");


The max length for the tool tip string was 64 bytes (63 ANSI chars + terminator) for the V1 version of the structure; so if you're using NOTIFYICONDATA_V1_SIZE, you'll want to not exceed that length. Note that you don't want to use sizeof(niData.szTip) to determine the max length... if you are on a system using version 5.0 or greater, niData.szTip will be larger than 64 bytes, which disagrees with your NOTIFYICONDATA_V1_SIZE.

One other point to make that is missing in this writeup (although you can read about it in the Shell_NotifyIcon documentation): When deleting an icon with NIM_DELETE, only the cbSize, hWnd and uID members need to be set in the NOTIFYICONDATA structure, and uFlags can be 0. The code in this example works because that is the case; but if your application is repacking data into the structure to NIM_DELETE it, you need not set any of the other members besides those three.

Also note that NOTIFYICONDATA_V1_SIZE is not defined on pre-5.0 machines, so while your code may run, if your goal is to compile the code on those machines, you will have issues using those constants (so be sure to check _WIN32_IE as described in the example). Applications normally use the old interface by default anyways. As a matter of fact, the docs imply that your application is responsible for setting _WIN32_IE to the version you want to use.
GeneralRe: You can remove the DllVersion stuff. Also, a NIF_TIP problem. Pin
Abraxas2313-Mar-07 20:04
Abraxas2313-Mar-07 20:04 
GeneralMany Thanks Pin
ARSanthosh6-Dec-06 13:22
ARSanthosh6-Dec-06 13:22 
GeneralGood article, I used your code in my project Pin
Didier Stevens16-Oct-06 9:51
Didier Stevens16-Oct-06 9:51 
GeneralDefinately something to look at. Pin
James R. L. X. Z.10-Oct-06 20:09
James R. L. X. Z.10-Oct-06 20:09 
GeneralRe: Definately something to look at. [modified] Pin
2boxers15-Oct-08 2:27
2boxers15-Oct-08 2:27 
Generalreally good, many thanks to you Pin
ikohl1-Oct-06 4:19
ikohl1-Oct-06 4:19 
QuestionHow to keep my tray icon stay after explorer reboot? Pin
Coolcute26-Sep-06 4:15
Coolcute26-Sep-06 4:15 
QuestionNot catching MY_TRAY_ICON_MESSAGE Pin
paratracker18-Aug-06 20:29
paratracker18-Aug-06 20:29 
AnswerRe: Not catching MY_TRAY_ICON_MESSAGE Pin
Abraxas2320-Aug-06 14:07
Abraxas2320-Aug-06 14:07 
QuestionHow to keep tray icon active all the time? Pin
Madhu Kavali3-Aug-06 8:31
Madhu Kavali3-Aug-06 8:31 
Questionstealthdialog with wamp is hanging [modified] Pin
mrdynalink2-Aug-06 10:20
mrdynalink2-Aug-06 10:20 

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

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