Tray Icon Wrapper
A complete C++ tray-icon wrapper — create, update, and destroy a Shell_NotifyIcon entry with a right-click menu and TaskbarCreated recovery.
Everything you need for a system-tray presence in one self-contained pair of files. LoadTrayIcon registers a hidden message-only window class, creates the popup window that owns the icon, fills out the NOTIFYICONDATA (icon, tooltip, callback message), calls Shell_NotifyIcon(NIM_ADD), builds the About/Exit popup menu, and runs the message loop. The window procedure handles the two classic cases people always miss: right-click shows the menu via TrackPopupMenuEx at the cursor position, and the registered TaskbarCreated broadcast re-adds the icon after Explorer restarts. UpdateTrayCaption/UpdateTrayIcon/UpdateTray cover live changes, and DestroyTrayIcon removes it cleanly on exit.
#ifndef _TRAYCLASS_H
#define _TRAYCLASS_H
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#define ID_ABOUT WM_USER + 1
#define ID_EXIT WM_USER + 2
#define WM_SHELLNOTIFY WM_USER + 5
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
extern char *sTrayClassName;
extern HINSTANCE hInstance;
extern HMENU FileMenu;
extern HMENU MainMenu;
extern NOTIFYICONDATA Note;
extern POINT pt;
extern UINT uTaskbarCreated;
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
BOOL LoadTrayIcon(WNDPROC WndProc, int iIcon, char *sCaption);
void UpdateTrayCaption(char *sCaption);
void UpdateTrayIcon(int iIcon);
void UpdateTray(int iIcon, char *sCaption);
BOOL DestroyTrayIcon(void);
LRESULT CALLBACK TrayIconProc(HWND xWindowHandle, UINT xMessage, WPARAM wParam, LPARAM lParam);
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#endif
The loader:
#include <Windows.H>
#include <ShellAPI.H>
#include "TrayIcon.H"
char *sTrayClassName = NULL;
HINSTANCE hInstance = NULL;
HMENU FileMenu = NULL;
HMENU MainMenu = NULL;
NOTIFYICONDATA Note;
POINT pt;
UINT uTaskbarCreated = -1;
BOOL LoadTrayIcon(WNDPROC WndProc, int iIcon, char *sCaption)
{
WNDCLASS Wc;
MSG Msg;
if(FindWindow(sTrayClassName, NULL))
{
return FALSE;
}
Wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
Wc.lpfnWndProc = WndProc;
Wc.cbClsExtra = 0;
Wc.cbWndExtra = 0;
Wc.hInstance = hInstance;
Wc.hIcon = LoadIcon(NULL, IDI_WINLOGO);
Wc.hCursor = LoadCursor(NULL, IDC_ARROW);
Wc.hbrBackground = GetSysColorBrush(COLOR_BTNFACE);
Wc.lpszMenuName = NULL;
Wc.lpszClassName = sTrayClassName;
RegisterClass(&Wc);
HWND MainFORM = CreateWindow(sTrayClassName, sCaption, WS_POPUP|WS_SYSMENU, 0, 0, 0, 0, NULL, (HMENU)NULL, hInstance, NULL);
Note.cbSize = sizeof(NOTIFYICONDATA);
Note.hWnd = MainFORM;
Note.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(iIcon) );
Note.uFlags = NIF_ICON | NIF_TIP | NIF_MESSAGE;
Note.uCallbackMessage = WM_SHELLNOTIFY;
strcpy(Note.szTip, sCaption);
Shell_NotifyIcon(NIM_ADD, &Note);
MainMenu = CreateMenu();
FileMenu = CreateMenu();
AppendMenu(FileMenu, MF_STRING, ID_ABOUT, "&About");
AppendMenu(FileMenu, MF_STRING, ID_EXIT, "E&xit");
InsertMenu(MainMenu, 0, MF_POPUP, (UINT)FileMenu, "invisible menu");
while(GetMessage(&Msg, NULL, 0, 0))
{
if( !IsWindow(MainFORM) || !IsDialogMessage(MainFORM, &Msg) )
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
}
return TRUE;
}
void UpdateTrayCaption(char *sCaption)
{
strcpy(Note.szTip, sCaption);
Shell_NotifyIcon(NIM_MODIFY, &Note);
}
void UpdateTrayIcon(int iIcon)
{
Note.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(iIcon));
Shell_NotifyIcon(NIM_MODIFY, &Note);
}
void UpdateTray(int iIcon, char *sCaption)
{
Note.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(iIcon));
strcpy(Note.szTip, sCaption);
Shell_NotifyIcon(NIM_MODIFY, &Note);
}
BOOL DestroyTrayIcon(void)
{
return Shell_NotifyIcon(NIM_DELETE, &Note);
}
And the window procedure with the two must-have handlers:
LRESULT CALLBACK TrayIconProc(HWND hHandle, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
if(uMsg == WM_COMMAND)
{
if(LOWORD(wParam) == ID_ABOUT)
{
MessageBox(hHandle, "This is the about data.", "Tray Icon", 0);
}
if(LOWORD(wParam) == ID_EXIT)
{
DestroyWindow(hHandle);
}
return TRUE;
}
if(uMsg == WM_CREATE)
{
uTaskbarCreated = RegisterWindowMessage("TaskbarCreated");
return TRUE;
}
if(uMsg == WM_SHELLNOTIFY)
{
if(lParam == WM_RBUTTONDOWN)
{
GetCursorPos(&pt);
SetForegroundWindow(hHandle);
TrackPopupMenuEx(FileMenu, TPM_LEFTALIGN| TPM_RIGHTBUTTON, pt.x,pt.y, hHandle, NULL);
PostMessage(hHandle, WM_NULL, 0, 0);
}
if(lParam == WM_LBUTTONDOWN)
{
MessageBox(hHandle, "Left Click", "Tray Icon", 0);
}
return TRUE;
}
if(uMsg == WM_DESTROY)
{
UnregisterClass(sTrayClassName, hInstance);
Shell_NotifyIconA(NIM_DELETE, &Note);
PostQuitMessage(0);
return TRUE;
}
if(uMsg == WM_CLOSE)
{
DestroyWindow(hHandle);
return TRUE;
}
if(uMsg == uTaskbarCreated)
{
Shell_NotifyIcon(NIM_ADD, &Note);
return TRUE;
}
return DefWindowProc(hHandle, uMsg, wParam, lParam);
}
Details worth keeping: SetForegroundWindow before TrackPopupMenuEx so the menu dismisses correctly when the user clicks elsewhere, the WM_NULL post to flush the menu state, and the NIM_DELETE in WM_DESTROY so no ghost icon survives process exit.